diff --git a/internal/modules/master/phase-activities/controllers/phase-activity.controller.go b/internal/modules/master/phase-activities/controllers/phase-activity.controller.go new file mode 100644 index 00000000..455ff1e4 --- /dev/null +++ b/internal/modules/master/phase-activities/controllers/phase-activity.controller.go @@ -0,0 +1,153 @@ +package controller + +import ( + "math" + "strconv" + + "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/validations" + "gitlab.com/mbugroup/lti-api.git/internal/response" + + "github.com/gofiber/fiber/v2" +) + +type PhaseActivityController struct { + PhaseActivityService service.PhaseActivityService +} + +func NewPhaseActivityController(phaseActivityService service.PhaseActivityService) *PhaseActivityController { + return &PhaseActivityController{ + PhaseActivityService: phaseActivityService, + } +} + +func (u *PhaseActivityController) GetAll(c *fiber.Ctx) error { + query := &validation.Query{ + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), + Search: c.Query("search", ""), + } + + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + + if phaseParam := c.Query("phase_id", ""); phaseParam != "" { + id, err := strconv.Atoi(phaseParam) + if err != nil || id <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "invalid phase_id") + } + temp := uint(id) + query.PhaseId = &temp + } + + result, totalResults, err := u.PhaseActivityService.GetAll(c, query) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.SuccessWithPaginate[dto.PhaseActivityListDTO]{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get all phaseActivitys successfully", + Meta: response.Meta{ + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), + TotalResults: totalResults, + }, + Data: dto.ToPhaseActivityListDTOs(result), + }) +} + +func (u *PhaseActivityController) GetOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + result, err := u.PhaseActivityService.GetOne(c, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get phaseActivity successfully", + Data: dto.ToPhaseActivityListDTO(*result), + }) +} + +func (u *PhaseActivityController) CreateOne(c *fiber.Ctx) error { + req := new(validation.Create) + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.PhaseActivityService.CreateOne(c, req) + if err != nil { + return err + } + + return c.Status(fiber.StatusCreated). + JSON(response.Success{ + Code: fiber.StatusCreated, + Status: "success", + Message: "Create phaseActivity successfully", + Data: dto.ToPhaseActivityListDTO(*result), + }) +} + +func (u *PhaseActivityController) UpdateOne(c *fiber.Ctx) error { + req := new(validation.Update) + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.PhaseActivityService.UpdateOne(c, req, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Update phaseActivity successfully", + Data: dto.ToPhaseActivityListDTO(*result), + }) +} + +func (u *PhaseActivityController) DeleteOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := u.PhaseActivityService.DeleteOne(c, uint(id)); err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Common{ + Code: fiber.StatusOK, + Status: "success", + Message: "Delete phaseActivity successfully", + }) +} diff --git a/internal/modules/master/phase-activities/dto/phase-activity.dto.go b/internal/modules/master/phase-activities/dto/phase-activity.dto.go new file mode 100644 index 00000000..ee5942d5 --- /dev/null +++ b/internal/modules/master/phase-activities/dto/phase-activity.dto.go @@ -0,0 +1,72 @@ +package dto + +import ( + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" +) + +// === DTO Structs === + +type PhaseActivityRelationDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type PhaseActivityListDTO struct { + Id uint `json:"id"` + PhaseId uint `json:"phase_id"` + Name string `json:"name"` + Description *string `json:"description"` + TimeType *string `json:"time_type"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type PhaseActivityDetailDTO struct { + PhaseActivityListDTO +} + +// === Mapper Functions === + +func ToPhaseActivityRelationDTO(e entity.PhaseActivity) PhaseActivityRelationDTO { + return PhaseActivityRelationDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToPhaseActivityListDTO(e entity.PhaseActivity) PhaseActivityListDTO { + var createdUser *userDTO.UserRelationDTO + // if e.CreatedUser.Id != 0 { + // mapped := userDTO.ToUserRelationDTO(e.CreatedUser) + // createdUser = &mapped + // } + + return PhaseActivityListDTO{ + Id: e.Id, + PhaseId: e.PhaseId, + Name: e.Name, + Description: e.Description, + TimeType: e.TimeType, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, + } +} + +func ToPhaseActivityListDTOs(e []entity.PhaseActivity) []PhaseActivityListDTO { + result := make([]PhaseActivityListDTO, len(e)) + for i, r := range e { + result[i] = ToPhaseActivityListDTO(r) + } + return result +} + +func ToPhaseActivityDetailDTO(e entity.PhaseActivity) PhaseActivityDetailDTO { + return PhaseActivityDetailDTO{ + PhaseActivityListDTO: ToPhaseActivityListDTO(e), + } +} diff --git a/internal/modules/master/phase-activities/module.go b/internal/modules/master/phase-activities/module.go new file mode 100644 index 00000000..22d25189 --- /dev/null +++ b/internal/modules/master/phase-activities/module.go @@ -0,0 +1,27 @@ +package phaseActivity + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + rPhaseActivity "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/repositories" + sPhaseActivity "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/services" + rPhases "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/repositories" + + rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" + sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" +) + +type PhaseActivityModule struct{} + +func (PhaseActivityModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + phaseActivityRepo := rPhaseActivity.NewPhaseActivityRepository(db) + phasesRepo := rPhases.NewPhasesRepository(db) + userRepo := rUser.NewUserRepository(db) + + phaseActivityService := sPhaseActivity.NewPhaseActivityService(phaseActivityRepo, phasesRepo, validate) + userService := sUser.NewUserService(userRepo, validate) + + PhaseActivityRoutes(router, userService, phaseActivityService) +} diff --git a/internal/modules/master/phase-activities/repositories/phase-activity.repository.go b/internal/modules/master/phase-activities/repositories/phase-activity.repository.go new file mode 100644 index 00000000..cc5eaae5 --- /dev/null +++ b/internal/modules/master/phase-activities/repositories/phase-activity.repository.go @@ -0,0 +1,21 @@ +package repository + +import ( + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + "gorm.io/gorm" +) + +type PhaseActivityRepository interface { + repository.BaseRepository[entity.PhaseActivity] +} + +type PhaseActivityRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.PhaseActivity] +} + +func NewPhaseActivityRepository(db *gorm.DB) PhaseActivityRepository { + return &PhaseActivityRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.PhaseActivity](db), + } +} diff --git a/internal/modules/master/phase-activities/route.go b/internal/modules/master/phase-activities/route.go new file mode 100644 index 00000000..6fcef558 --- /dev/null +++ b/internal/modules/master/phase-activities/route.go @@ -0,0 +1,23 @@ +package phaseActivity + +import ( + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/controllers" + phaseActivity "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/services" + user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" + + "github.com/gofiber/fiber/v2" +) + +func PhaseActivityRoutes(v1 fiber.Router, u user.UserService, s phaseActivity.PhaseActivityService) { + ctrl := controller.NewPhaseActivityController(s) + + route := v1.Group("/phase-activities") + route.Use(m.Auth(u)) + + route.Get("/", ctrl.GetAll) + route.Post("/", ctrl.CreateOne) + route.Get("/:id", ctrl.GetOne) + route.Patch("/:id", ctrl.UpdateOne) + route.Delete("/:id", ctrl.DeleteOne) +} diff --git a/internal/modules/master/phase-activities/services/phase-activity.service.go b/internal/modules/master/phase-activities/services/phase-activity.service.go new file mode 100644 index 00000000..3426eab4 --- /dev/null +++ b/internal/modules/master/phase-activities/services/phase-activity.service.go @@ -0,0 +1,167 @@ +package service + +import ( + "errors" + "strings" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/validations" + phaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/repositories" + "gitlab.com/mbugroup/lti-api.git/internal/utils" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +type PhaseActivityService interface { + GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.PhaseActivity, int64, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.PhaseActivity, error) + CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.PhaseActivity, error) + UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.PhaseActivity, error) + DeleteOne(ctx *fiber.Ctx, id uint) error +} + +type phaseActivityService struct { + Log *logrus.Logger + Validate *validator.Validate + Repository repository.PhaseActivityRepository + PhaseRepo phaseRepo.PhasesRepository +} + +func NewPhaseActivityService(repo repository.PhaseActivityRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate) PhaseActivityService { + return &phaseActivityService{ + Log: utils.Log, + Validate: validate, + Repository: repo, + PhaseRepo: phaseRepo, + } +} + +func (s phaseActivityService) withRelations(db *gorm.DB) *gorm.DB { + return db +} + +func (s phaseActivityService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.PhaseActivity, int64, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, 0, err + } + + offset := (params.Page - 1) * params.Limit + + phaseActivitys, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { + db = s.withRelations(db) + if params.Search != "" { + db = db.Where("name LIKE ?", "%"+params.Search+"%") + } + if params.PhaseId != nil { + db = db.Where("phase_id = ?", *params.PhaseId) + } + return db.Order("created_at DESC").Order("updated_at DESC") + }) + + if err != nil { + s.Log.Errorf("Failed to get phaseActivitys: %+v", err) + return nil, 0, err + } + return phaseActivitys, total, nil +} + +func (s phaseActivityService) GetOne(c *fiber.Ctx, id uint) (*entity.PhaseActivity, error) { + phaseActivity, err := s.Repository.GetByID(c.Context(), id, s.withRelations) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "PhaseActivity not found") + } + if err != nil { + s.Log.Errorf("Failed get phaseActivity by id: %+v", err) + return nil, err + } + return phaseActivity, nil +} + +func (s *phaseActivityService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.PhaseActivity, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + phase, err := s.PhaseRepo.GetByID(c.Context(), req.PhaseId, nil) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusBadRequest, "phase not found") + } + if err != nil { + s.Log.Errorf("Failed to get phase: %+v", err) + return nil, err + } + + name := strings.TrimSpace(req.Name) + if name == "" { + return nil, fiber.NewError(fiber.StatusBadRequest, "name cannot be empty") + } + + timeType := strings.TrimSpace(req.TimeType) + if timeType == "" { + return nil, fiber.NewError(fiber.StatusBadRequest, "time_type cannot be empty") + } + + createBody := &entity.PhaseActivity{ + PhaseId: phase.Id, + Name: name, + Description: req.Description, + TimeType: &timeType, + } + + if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { + s.Log.Errorf("Failed to create phaseActivity: %+v", err) + return nil, err + } + + return s.GetOne(c, createBody.Id) +} + +func (s phaseActivityService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.PhaseActivity, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + trimmedName := strings.TrimSpace(req.Name) + if trimmedName == "" { + return nil, fiber.NewError(fiber.StatusBadRequest, "name cannot be empty") + } + + trimmedTimeType := strings.TrimSpace(req.TimeType) + if trimmedTimeType == "" { + return nil, fiber.NewError(fiber.StatusBadRequest, "time_type cannot be empty") + } + + updateBody := map[string]any{ + "name": trimmedName, + "time_type": trimmedTimeType, + } + + if req.Description != nil { + updateBody["description"] = *req.Description + } + + if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "PhaseActivity not found") + } + s.Log.Errorf("Failed to update phaseActivity: %+v", err) + return nil, err + } + + return s.GetOne(c, id) +} + +func (s phaseActivityService) DeleteOne(c *fiber.Ctx, id uint) error { + if err := s.Repository.DeleteOne(c.Context(), id); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "PhaseActivity not found") + } + s.Log.Errorf("Failed to delete phaseActivity: %+v", err) + return err + } + return nil +} diff --git a/internal/modules/master/phase-activities/validations/phase-activity.validation.go b/internal/modules/master/phase-activities/validations/phase-activity.validation.go new file mode 100644 index 00000000..a2ab8e1b --- /dev/null +++ b/internal/modules/master/phase-activities/validations/phase-activity.validation.go @@ -0,0 +1,21 @@ +package validation + +type Create struct { + PhaseId uint `json:"phase_id" validate:"required"` + Name string `json:"name" validate:"required_strict,min=3"` + Description *string `json:"description,omitempty"` + TimeType string `json:"time_type" validate:"required"` +} + +type Update struct { + Name string `json:"name" validate:"required_strict,min=3"` + Description *string `json:"description,omitempty"` + TimeType string `json:"time_type" validate:"required"` +} + +type Query struct { + Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` + Search string `query:"search" validate:"omitempty,max=50"` + PhaseId *uint `query:"phase_id" validate:"omitempty"` +} diff --git a/internal/modules/master/phasess/services/phases.service.go b/internal/modules/master/phasess/services/phases.service.go index 863b369d..98e73bef 100644 --- a/internal/modules/master/phasess/services/phases.service.go +++ b/internal/modules/master/phasess/services/phases.service.go @@ -84,7 +84,7 @@ func (s *phasesService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity } if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB { - return db.Where("LOWER(name) = ?", strings.ToLower(req.Name)) + return db.Where("LOWER(name) = ? AND category = ?", strings.ToLower(req.Name), req.Category) }); err == nil { return nil, fiber.NewError(fiber.StatusBadRequest, "phase already exists") } else if !errors.Is(err, gorm.ErrRecordNotFound) { @@ -111,11 +111,20 @@ func (s phasesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) return nil, err } + existing, err := s.Repository.GetByID(c.Context(), id, nil) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Phases not found") + } + if err != nil { + s.Log.Errorf("Failed get phases by id: %+v", err) + return nil, err + } + updateBody := make(map[string]any) if req.Name != nil { if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB { - return db.Where("LOWER(name) = ? AND id <> ?", strings.ToLower(*req.Name), id) + return db.Where("LOWER(name) = ? AND category = ? AND id <> ?", strings.ToLower(*req.Name), existing.Category, id) }); err == nil { return nil, fiber.NewError(fiber.StatusBadRequest, "phase already exists") } else if !errors.Is(err, gorm.ErrRecordNotFound) { @@ -130,9 +139,6 @@ func (s phasesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) } if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusNotFound, "Phases not found") - } s.Log.Errorf("Failed to update phases: %+v", err) return nil, err } diff --git a/internal/modules/master/route.go b/internal/modules/master/route.go index e0a7b246..f9bc7b13 100644 --- a/internal/modules/master/route.go +++ b/internal/modules/master/route.go @@ -16,13 +16,14 @@ import ( kandangs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs" locations "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations" nonstocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks" + phaseActivitys "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities" + phasess "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess" productcategories "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories" productionStandards "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards" products "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products" suppliers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers" uoms "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms" warehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses" - phasess "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess" // MODULE IMPORTS ) @@ -46,6 +47,7 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida productionStandards.ProductionStandardModule{}, employeess.EmployeesModule{}, phasess.PhasesModule{}, + phaseActivitys.PhaseActivityModule{}, // MODULE REGISTRY }