add daily checklist module;adjust master data;adjust migration

This commit is contained in:
MacBook Air M1
2026-01-06 17:03:55 +07:00
parent b1996be24c
commit 3bd0602525
25 changed files with 1011 additions and 52 deletions
@@ -0,0 +1,2 @@
ALTER TABLE daily_checklists
DROP CONSTRAINT IF EXISTS daily_checklists_date_kandang_category_key;
@@ -0,0 +1,3 @@
ALTER TABLE daily_checklists
ADD CONSTRAINT daily_checklists_date_kandang_category_key
UNIQUE (date, kandang_id, category);
@@ -0,0 +1,2 @@
ALTER TABLE daily_checklists
ALTER COLUMN checklist_id SET NOT NULL;
@@ -0,0 +1,2 @@
ALTER TABLE daily_checklists
ALTER COLUMN checklist_id DROP NOT NULL;
@@ -0,0 +1,4 @@
ALTER TABLE daily_checklist_phases
DROP CONSTRAINT IF EXISTS fk_dcp_daily_checklist,
ADD CONSTRAINT fk_dcp_checklist
FOREIGN KEY (checklist_id) REFERENCES checklists(id) ON DELETE CASCADE;
@@ -0,0 +1,4 @@
ALTER TABLE daily_checklist_phases
DROP CONSTRAINT IF EXISTS fk_dcp_checklist,
ADD CONSTRAINT fk_dcp_daily_checklist
FOREIGN KEY (checklist_id) REFERENCES daily_checklists(id) ON DELETE CASCADE;
@@ -0,0 +1,4 @@
ALTER TABLE daily_checklist_activity_tasks
DROP CONSTRAINT IF EXISTS fk_dcat_daily_checklist,
ADD CONSTRAINT fk_dcat_checklist
FOREIGN KEY (checklist_id) REFERENCES checklists(id) ON DELETE CASCADE;
@@ -0,0 +1,4 @@
ALTER TABLE daily_checklist_activity_tasks
DROP CONSTRAINT IF EXISTS fk_dcat_checklist,
ADD CONSTRAINT fk_dcat_daily_checklist
FOREIGN KEY (checklist_id) REFERENCES daily_checklists(id) ON DELETE CASCADE;
@@ -0,0 +1,2 @@
ALTER TABLE daily_checklist_activity_task_assignments
DROP CONSTRAINT IF EXISTS daily_checklist_activity_task_assignments_task_employee_key;
@@ -0,0 +1,3 @@
ALTER TABLE daily_checklist_activity_task_assignments
ADD CONSTRAINT daily_checklist_activity_task_assignments_task_employee_key
UNIQUE (task_id, employee_id);
@@ -0,0 +1,8 @@
ALTER TABLE phase_activities
DROP COLUMN IF EXISTS deleted_at;
ALTER TABLE phases
DROP COLUMN IF EXISTS deleted_at;
ALTER TABLE employees
DROP COLUMN IF EXISTS deleted_at;
@@ -0,0 +1,8 @@
ALTER TABLE employees
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
ALTER TABLE phases
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
ALTER TABLE phase_activities
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
+81
View File
@@ -0,0 +1,81 @@
package entities
import "time"
type DailyChecklist struct {
Id uint `gorm:"primaryKey"`
KandangId uint `gorm:"not null"`
ChecklistId *uint
Date time.Time `gorm:"type:date;not null"`
Name *string `gorm:"type:varchar(255)"`
Status *string `gorm:"type:varchar(255)"`
Category string `gorm:"type:category_code;not null"`
TotalScore *int
DocumentPath *string
RejectReason *string
CreatedBy *uint
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
Checklist *Checklist `gorm:"foreignKey:ChecklistId;references:Id"`
Creator *User `gorm:"foreignKey:CreatedBy;references:Id"`
Tasks []DailyChecklistTask `gorm:"foreignKey:DailyChecklistId;references:Id"`
}
type DailyChecklistPhase struct {
Id uint `gorm:"primaryKey"`
ChecklistId uint `gorm:"not null"`
PhaseId uint `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
Checklist Checklist `gorm:"foreignKey:ChecklistId;references:Id"`
Phase Phases `gorm:"foreignKey:PhaseId;references:Id"`
}
type DailyChecklistActivityTask struct {
Id uint `gorm:"primaryKey"`
ChecklistId uint `gorm:"not null"`
PhaseId uint `gorm:"not null"`
PhaseActivityId uint `gorm:"not null"`
TimeType *string `gorm:"type:text"`
Notes *string `gorm:"type:text"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
Checklist DailyChecklist `gorm:"foreignKey:ChecklistId;references:Id"`
Phase Phases `gorm:"foreignKey:PhaseId;references:Id"`
PhaseActivity PhaseActivity `gorm:"foreignKey:PhaseActivityId;references:Id"`
Assignments []DailyChecklistActivityTaskAssignment `gorm:"foreignKey:TaskId;references:Id"`
}
type DailyChecklistActivityTaskAssignment struct {
Id uint `gorm:"primaryKey"`
TaskId uint `gorm:"not null"`
EmployeeId uint `gorm:"not null"`
Checked bool `gorm:"not null;default:false"`
Note *string `gorm:"type:text"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
Task DailyChecklistActivityTask `gorm:"foreignKey:TaskId;references:Id"`
Employee Employee `gorm:"foreignKey:EmployeeId;references:Id"`
}
type DailyChecklistTask struct {
Id uint `gorm:"primaryKey"`
DailyChecklistId uint `gorm:"not null"`
ChecklistId uint `gorm:"not null"`
ChecklistItemId *uint
IsCompleted bool `gorm:"not null;default:false"`
ScoreValue *int
Notes *string `gorm:"type:text"`
PhotoProof *string
Status *string
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DailyChecklist *DailyChecklist `gorm:"foreignKey:DailyChecklistId;references:Id"`
Checklist Checklist `gorm:"foreignKey:ChecklistId;references:Id"`
ChecklistItem *PhaseActivity `gorm:"foreignKey:ChecklistItemId;references:Id"`
}
+6 -1
View File
@@ -1,6 +1,10 @@
package entities package entities
import "time" import (
"time"
"gorm.io/gorm"
)
type Employee struct { type Employee struct {
Id uint `gorm:"primaryKey"` Id uint `gorm:"primaryKey"`
@@ -8,6 +12,7 @@ type Employee struct {
IsActive bool `gorm:"not null"` IsActive bool `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"` CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
EmployeeKandangs []EmployeeKandang `gorm:"foreignKey:EmployeeId;references:Id"` EmployeeKandangs []EmployeeKandang `gorm:"foreignKey:EmployeeId;references:Id"`
} }
+4 -2
View File
@@ -12,6 +12,7 @@ type Phases struct {
IsActive bool `gorm:"not null;default:true"` IsActive bool `gorm:"not null;default:true"`
Category string `gorm:"type:category_code;not null"` Category string `gorm:"type:category_code;not null"`
CreatedAt time.Time `gorm:"autoCreateTime"` CreatedAt time.Time `gorm:"autoCreateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Activities []PhaseActivity `gorm:"foreignKey:PhaseId;references:Id"` Activities []PhaseActivity `gorm:"foreignKey:PhaseId;references:Id"`
} }
@@ -24,8 +25,9 @@ type PhaseActivity struct {
TimeType *string `gorm:"type:text"` TimeType *string `gorm:"type:text"`
CreatedAt time.Time `gorm:"autoCreateTime"` CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Phase Phase `gorm:"foreignKey:PhaseId;references:Id"` Phase Phases `gorm:"foreignKey:PhaseId;references:Id"`
} }
type Checklist struct { type Checklist struct {
@@ -37,5 +39,5 @@ type Checklist struct {
UpdatedAt time.Time `gorm:"autoUpdateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Phase *Phase `gorm:"foreignKey:PhaseId;references:Id"` Phase *Phases `gorm:"foreignKey:PhaseId;references:Id"`
} }
@@ -0,0 +1,243 @@
package controller
import (
"math"
"strconv"
"gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/dto"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/services"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/validations"
"gitlab.com/mbugroup/lti-api.git/internal/response"
"github.com/gofiber/fiber/v2"
)
type DailyChecklistController struct {
DailyChecklistService service.DailyChecklistService
}
func NewDailyChecklistController(dailyChecklistService service.DailyChecklistService) *DailyChecklistController {
return &DailyChecklistController{
DailyChecklistService: dailyChecklistService,
}
}
func (u *DailyChecklistController) 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")
}
result, totalResults, err := u.DailyChecklistService.GetAll(c, query)
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.SuccessWithPaginate[dto.DailyChecklistListDTO]{
Code: fiber.StatusOK,
Status: "success",
Message: "Get all dailyChecklists successfully",
Meta: response.Meta{
Page: query.Page,
Limit: query.Limit,
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
TotalResults: totalResults,
},
Data: dto.ToDailyChecklistListDTOs(result),
})
}
func (u *DailyChecklistController) 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.DailyChecklistService.GetOne(c, uint(id))
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: "Get dailyChecklist successfully",
Data: dto.ToDailyChecklistListDTO(*result),
})
}
func (u *DailyChecklistController) 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.DailyChecklistService.CreateOne(c, req)
if err != nil {
return err
}
return c.Status(fiber.StatusCreated).
JSON(response.Success{
Code: fiber.StatusCreated,
Status: "success",
Message: "Create dailyChecklist successfully",
Data: dto.ToDailyChecklistListDTO(*result),
})
}
func (u *DailyChecklistController) 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.DailyChecklistService.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 dailyChecklist successfully",
Data: dto.ToDailyChecklistListDTO(*result),
})
}
func (u *DailyChecklistController) 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.DailyChecklistService.DeleteOne(c, uint(id)); err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Common{
Code: fiber.StatusOK,
Status: "success",
Message: "Delete dailyChecklist successfully",
})
}
func (u *DailyChecklistController) CreateDailyChecklistPhase(c *fiber.Ctx) error {
param := c.Params("idDailyChecklist")
id, err := strconv.Atoi(param)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid daily checklist id")
}
req := new(validation.AssignPhases)
if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
if err := u.DailyChecklistService.AssignPhases(c, uint(id), req); err != nil {
return err
}
return c.Status(fiber.StatusCreated).
JSON(response.Success{
Code: fiber.StatusCreated,
Status: "success",
Message: "Daily checklist phases saved successfully",
})
}
func (u *DailyChecklistController) CreateAssignment(c *fiber.Ctx) error {
param := c.Params("idDailyChecklist")
id, err := strconv.Atoi(param)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid daily checklist id")
}
req := new(validation.AssignTask)
if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
if err := u.DailyChecklistService.AssignTasks(c, uint(id), req); err != nil {
return err
}
return c.Status(fiber.StatusCreated).
JSON(response.Success{
Code: fiber.StatusCreated,
Status: "success",
Message: "Daily checklist assignments saved successfully",
})
}
func (u *DailyChecklistController) RemoveAssignment(c *fiber.Ctx) error {
dailyChecklistParam := c.Params("idDailyChecklist")
employeeParam := c.Params("idEmployee")
dailyChecklistID, err := strconv.Atoi(dailyChecklistParam)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid daily checklist id")
}
employeeID, err := strconv.Atoi(employeeParam)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid employee id")
}
if err := u.DailyChecklistService.RemoveAssignment(c, uint(dailyChecklistID), uint(employeeID)); err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Common{
Code: fiber.StatusOK,
Status: "success",
Message: "Assignment removed successfully",
})
}
func (u *DailyChecklistController) GetAllTasks(c *fiber.Ctx) error {
checklistParam := c.Query("checklist_id", "")
if checklistParam == "" {
return fiber.NewError(fiber.StatusBadRequest, "checklist_id is required")
}
checklistID, err := strconv.Atoi(checklistParam)
if err != nil || checklistID <= 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid checklist_id")
}
result, err := u.DailyChecklistService.GetTasks(c, uint(checklistID))
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: "Get daily checklist tasks successfully",
Data: result,
})
}
@@ -0,0 +1,76 @@
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 DailyChecklistRelationDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
}
type DailyChecklistListDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type DailyChecklistDetailDTO struct {
DailyChecklistListDTO
}
// === Mapper Functions ===
func ToDailyChecklistRelationDTO(e entity.DailyChecklist) DailyChecklistRelationDTO {
var name string
if e.Name != nil {
name = *e.Name
}
return DailyChecklistRelationDTO{
Id: e.Id,
Name: name,
}
}
func ToDailyChecklistListDTO(e entity.DailyChecklist) DailyChecklistListDTO {
var createdUser *userDTO.UserRelationDTO
// if e.CreatedUser.Id != 0 {
// mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
// createdUser = &mapped
// }
var name string
if e.Name != nil {
name = *e.Name
}
return DailyChecklistListDTO{
Id: e.Id,
Name: name,
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
CreatedUser: createdUser,
}
}
func ToDailyChecklistListDTOs(e []entity.DailyChecklist) []DailyChecklistListDTO {
result := make([]DailyChecklistListDTO, len(e))
for i, r := range e {
result[i] = ToDailyChecklistListDTO(r)
}
return result
}
func ToDailyChecklistDetailDTO(e entity.DailyChecklist) DailyChecklistDetailDTO {
return DailyChecklistDetailDTO{
DailyChecklistListDTO: ToDailyChecklistListDTO(e),
}
}
@@ -0,0 +1,27 @@
package dailyChecklists
import (
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
rDailyChecklist "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/repositories"
sDailyChecklist "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/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 DailyChecklistModule struct{}
func (DailyChecklistModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
dailyChecklistRepo := rDailyChecklist.NewDailyChecklistRepository(db)
phasesRepo := rPhases.NewPhasesRepository(db)
userRepo := rUser.NewUserRepository(db)
dailyChecklistService := sDailyChecklist.NewDailyChecklistService(dailyChecklistRepo, phasesRepo, validate)
userService := sUser.NewUserService(userRepo, validate)
DailyChecklistRoutes(router, userService, dailyChecklistService)
}
@@ -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 DailyChecklistRepository interface {
repository.BaseRepository[entity.DailyChecklist]
}
type DailyChecklistRepositoryImpl struct {
*repository.BaseRepositoryImpl[entity.DailyChecklist]
}
func NewDailyChecklistRepository(db *gorm.DB) DailyChecklistRepository {
return &DailyChecklistRepositoryImpl{
BaseRepositoryImpl: repository.NewBaseRepository[entity.DailyChecklist](db),
}
}
@@ -0,0 +1,35 @@
package dailyChecklists
import (
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/controllers"
dailyChecklist "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/services"
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
"github.com/gofiber/fiber/v2"
)
func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist.DailyChecklistService) {
ctrl := controller.NewDailyChecklistController(s)
route := v1.Group("/daily-checklists")
route.Use(m.Auth(u))
route.Get("/", ctrl.GetAll)
route.Post("/", ctrl.CreateOne)
// create task
route.Post("/phase/:idDailyChecklist", ctrl.CreateDailyChecklistPhase)
// create assigment
route.Post("/assignment/:idDailyChecklist", ctrl.CreateAssignment)
// remove assignment
route.Delete("/:idDailyChecklist/assignments/:idEmployee", ctrl.RemoveAssignment)
//get all tasks
route.Get("/tasks", ctrl.GetAllTasks)
route.Get("/:id", ctrl.GetOne)
route.Patch("/:id", ctrl.UpdateOne)
route.Delete("/:id", ctrl.DeleteOne)
}
@@ -0,0 +1,410 @@
package service
import (
"errors"
"strconv"
"strings"
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/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"
"gorm.io/gorm/clause"
)
type DailyChecklistService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.DailyChecklist, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.DailyChecklist, error)
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.DailyChecklist, error)
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.DailyChecklist, error)
DeleteOne(ctx *fiber.Ctx, id uint) error
AssignPhases(ctx *fiber.Ctx, id uint, req *validation.AssignPhases) error
AssignTasks(ctx *fiber.Ctx, id uint, req *validation.AssignTask) error
RemoveAssignment(ctx *fiber.Ctx, id uint, employeeID uint) error
GetTasks(ctx *fiber.Ctx, checklistID uint) ([]entity.DailyChecklistActivityTask, error)
}
type dailyChecklistService struct {
Log *logrus.Logger
Validate *validator.Validate
Repository repository.DailyChecklistRepository
PhaseRepo phaseRepo.PhasesRepository
}
func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate) DailyChecklistService {
return &dailyChecklistService{
Log: utils.Log,
Validate: validate,
Repository: repo,
PhaseRepo: phaseRepo,
}
}
func (s dailyChecklistService) withRelations(db *gorm.DB) *gorm.DB {
return db
}
func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.DailyChecklist, int64, error) {
if err := s.Validate.Struct(params); err != nil {
return nil, 0, err
}
offset := (params.Page - 1) * params.Limit
dailyChecklists, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
db = s.withRelations(db)
if params.Search != "" {
return db.Where("name LIKE ?", "%"+params.Search+"%")
}
return db.Order("created_at DESC").Order("updated_at DESC")
})
if err != nil {
s.Log.Errorf("Failed to get dailyChecklists: %+v", err)
return nil, 0, err
}
return dailyChecklists, total, nil
}
func (s dailyChecklistService) GetOne(c *fiber.Ctx, id uint) (*entity.DailyChecklist, error) {
dailyChecklist, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
}
if err != nil {
s.Log.Errorf("Failed get dailyChecklist by id: %+v", err)
return nil, err
}
return dailyChecklist, nil
}
func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.DailyChecklist, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err
}
date, err := time.Parse("2006-01-02", req.Date)
if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "invalid date format, use YYYY-MM-DD")
}
status := req.Status
category := req.Category
createBody := &entity.DailyChecklist{
KandangId: req.KandangId,
Date: date,
Category: category,
Status: &status,
}
err = s.Repository.DB().WithContext(c.Context()).Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "date"}, {Name: "kandang_id"}, {Name: "category"}},
DoUpdates: clause.Assignments(map[string]any{"status": status, "updated_at": time.Now()}),
}).Create(createBody).Error
if err != nil {
s.Log.Errorf("Failed to upsert dailyChecklist: %+v", err)
return nil, err
}
return s.GetOne(c, createBody.Id)
}
func (s dailyChecklistService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.DailyChecklist, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err
}
updateBody := make(map[string]any)
if req.Name != nil {
updateBody["name"] = *req.Name
}
if len(updateBody) == 0 {
return s.GetOne(c, id)
}
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
}
s.Log.Errorf("Failed to update dailyChecklist: %+v", err)
return nil, err
}
return s.GetOne(c, id)
}
func (s dailyChecklistService) 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, "DailyChecklist not found")
}
s.Log.Errorf("Failed to delete dailyChecklist: %+v", err)
return err
}
return nil
}
func (s dailyChecklistService) AssignPhases(c *fiber.Ctx, id uint, req *validation.AssignPhases) error {
if err := s.Validate.Struct(req); err != nil {
return err
}
if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
}
return err
}
phaseIDs, err := parsePhaseIDs(req.PhaseIDs)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if len(phaseIDs) > 0 {
phases, err := s.PhaseRepo.GetByIDs(c.Context(), phaseIDs, nil)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusBadRequest, "Phase not found")
}
return err
}
if len(phases) != len(phaseIDs) {
return fiber.NewError(fiber.StatusBadRequest, "Phase not found")
}
}
db := s.Repository.DB()
if err := db.WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
if err := tx.Where("checklist_id = ?", id).Delete(&entity.DailyChecklistPhase{}).Error; err != nil {
return err
}
if len(phaseIDs) == 0 {
return nil
}
records := make([]entity.DailyChecklistPhase, 0, len(phaseIDs))
for _, pid := range phaseIDs {
records = append(records, entity.DailyChecklistPhase{
ChecklistId: id,
PhaseId: pid,
})
}
if err := tx.Create(&records).Error; err != nil {
return err
}
if err := tx.Where("checklist_id = ?", id).Delete(&entity.DailyChecklistActivityTask{}).Error; err != nil {
return err
}
var activities []entity.PhaseActivity
if err := tx.Where("phase_id IN ?", phaseIDs).Find(&activities).Error; err != nil {
return err
}
activityRecords := make([]entity.DailyChecklistActivityTask, 0, len(activities))
for _, activity := range activities {
activityRecords = append(activityRecords, entity.DailyChecklistActivityTask{
ChecklistId: id,
PhaseId: activity.PhaseId,
PhaseActivityId: activity.Id,
TimeType: activity.TimeType,
})
}
if len(activityRecords) == 0 {
return nil
}
return tx.Create(&activityRecords).Error
}); err != nil {
s.Log.Errorf("Failed to assign phases to daily checklist: %+v", err)
return err
}
return nil
}
func (s dailyChecklistService) RemoveAssignment(c *fiber.Ctx, id uint, employeeID uint) error {
if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
}
return err
}
if employeeID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid employee id")
}
db := s.Repository.DB()
if err := db.WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
var tasks []entity.DailyChecklistActivityTask
if err := tx.Where("checklist_id = ?", id).Find(&tasks).Error; err != nil {
return err
}
if len(tasks) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "No activity tasks found for this checklist")
}
taskIDs := collectTaskIDs(tasks)
return tx.Where("task_id IN ? AND employee_id = ?", taskIDs, employeeID).
Delete(&entity.DailyChecklistActivityTaskAssignment{}).Error
}); err != nil {
s.Log.Errorf("Failed to remove assignment: %+v", err)
return err
}
return nil
}
func (s dailyChecklistService) GetTasks(c *fiber.Ctx, checklistID uint) ([]entity.DailyChecklistActivityTask, error) {
if checklistID == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "checklist_id is required")
}
if _, err := s.Repository.GetByID(c.Context(), checklistID, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
}
return nil, err
}
var tasks []entity.DailyChecklistActivityTask
if err := s.Repository.DB().WithContext(c.Context()).
Where("checklist_id = ?", checklistID).
Order("created_at ASC").
Find(&tasks).Error; err != nil {
s.Log.Errorf("Failed to get daily checklist tasks: %+v", err)
return nil, err
}
return tasks, nil
}
func parsePhaseIDs(raw string) ([]uint, error) {
parts := strings.Split(raw, ",")
result := make([]uint, 0, len(parts))
seen := make(map[uint]struct{})
for _, part := range parts {
value := strings.TrimSpace(part)
if value == "" {
continue
}
num, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return nil, errors.New("invalid phase id: " + value)
}
u := uint(num)
if _, ok := seen[u]; ok {
continue
}
seen[u] = struct{}{}
result = append(result, u)
}
return result, nil
}
func parseIDs(raw string) ([]uint, error) {
parts := strings.Split(raw, ",")
result := make([]uint, 0, len(parts))
seen := make(map[uint]struct{})
for _, part := range parts {
value := strings.TrimSpace(part)
if value == "" {
continue
}
num, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return nil, errors.New("invalid employee id: " + value)
}
u := uint(num)
if _, ok := seen[u]; ok {
continue
}
seen[u] = struct{}{}
result = append(result, u)
}
return result, nil
}
func collectTaskIDs(tasks []entity.DailyChecklistActivityTask) []uint {
result := make([]uint, len(tasks))
for i, task := range tasks {
result[i] = task.Id
}
return result
}
func (s dailyChecklistService) AssignTasks(c *fiber.Ctx, id uint, req *validation.AssignTask) error {
if err := s.Validate.Struct(req); err != nil {
return err
}
employeeIDs, err := parseIDs(req.EmployeeIDs)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if len(employeeIDs) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "employee_ids cannot be empty")
}
if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
}
return err
}
db := s.Repository.DB()
if err := db.WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
var tasks []entity.DailyChecklistActivityTask
if err := tx.Where("checklist_id = ?", id).Find(&tasks).Error; err != nil {
return err
}
if len(tasks) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "No activity tasks found for this checklist")
}
assignments := make([]entity.DailyChecklistActivityTaskAssignment, 0, len(tasks)*len(employeeIDs))
for _, task := range tasks {
for _, empID := range employeeIDs {
assignments = append(assignments, entity.DailyChecklistActivityTaskAssignment{
TaskId: task.Id,
EmployeeId: empID,
})
}
}
return tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "task_id"}, {Name: "employee_id"}},
DoUpdates: clause.Assignments(map[string]any{"updated_at": time.Now()}),
}).Create(&assignments).Error
}); err != nil {
s.Log.Errorf("Failed to assign tasks to daily checklist: %+v", err)
return err
}
return nil
}
@@ -0,0 +1,26 @@
package validation
type Create struct {
Date string `json:"date" validate:"required"`
KandangId uint `json:"kandang_id" validate:"required"`
Category string `json:"category" validate:"required"`
Status string `json:"status" validate:"required"`
}
type Update struct {
Name *string `json:"name,omitempty" validate:"omitempty"`
}
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"`
}
type AssignPhases struct {
PhaseIDs string `json:"phase_ids" validate:"required"`
}
type AssignTask struct {
EmployeeIDs string `json:"employee_ids" validate:"required"`
}
@@ -2,8 +2,6 @@ package service
import ( import (
"errors" "errors"
"fmt"
"strconv"
"strings" "strings"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
@@ -41,10 +39,8 @@ func NewEmployeesService(repo repository.EmployeesRepository, validate *validato
func (s employeesService) withRelations(db *gorm.DB) *gorm.DB { func (s employeesService) withRelations(db *gorm.DB) *gorm.DB {
return db. return db.
Preload("EmployeeKandangs.Kandang") Preload("EmployeeKandangs.Kandang").
// Preload("EmployeeKandangs.Kandang.Location"). Where("employees.deleted_at IS NULL")
// Preload("EmployeeKandangs.Kandang.Pic").
// Preload("EmployeeKandangs.Kandang.CreatedUser")
} }
func (s employeesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Employees, int64, error) { func (s employeesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Employees, int64, error) {
@@ -98,9 +94,9 @@ func (s *employeesService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
return nil, fiber.NewError(fiber.StatusBadRequest, "name cannot be empty") return nil, fiber.NewError(fiber.StatusBadRequest, "name cannot be empty")
} }
kandangIDs, err := parseKandangIDs(req.KandangIDs) kandangIDs := normalizeKandangIDs(req.KandangIDs)
if err != nil { if len(kandangIDs) == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, err.Error()) return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids must contain at least one valid id")
} }
if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB { if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB {
@@ -181,9 +177,9 @@ func (s employeesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
} }
if req.KandangIDs != nil { if req.KandangIDs != nil {
ids, err := parseKandangIDs(*req.KandangIDs) ids := normalizeKandangIDs(*req.KandangIDs)
if err != nil { if len(ids) == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, err.Error()) return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids must contain at least one valid id")
} }
kandangIDs = ids kandangIDs = ids
@@ -248,33 +244,22 @@ func (s employeesService) DeleteOne(c *fiber.Ctx, id uint) error {
return nil return nil
} }
func parseKandangIDs(raw string) ([]uint, error) { func normalizeKandangIDs(ids []uint) []uint {
parts := strings.Split(raw, ",") result := make([]uint, 0, len(ids))
ids := make([]uint, 0, len(parts))
seen := make(map[uint]struct{}) seen := make(map[uint]struct{})
for _, part := range parts { for _, id := range ids {
value := strings.TrimSpace(part) if id == 0 {
if value == "" {
continue continue
} }
parsed, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid kandang id: %s", value)
}
id := uint(parsed)
if _, ok := seen[id]; ok { if _, ok := seen[id]; ok {
continue continue
} }
seen[id] = struct{}{} seen[id] = struct{}{}
ids = append(ids, id) result = append(result, id)
} }
if len(ids) == 0 { return result
return nil, errors.New("kandang_ids must contain at least one valid id")
}
return ids, nil
} }
@@ -2,13 +2,13 @@ package validation
type Create struct { type Create struct {
Name string `json:"name" validate:"required_strict,min=3"` Name string `json:"name" validate:"required_strict,min=3"`
KandangIDs string `json:"kandang_ids" validate:"required"` KandangIDs []uint `json:"kandang_ids" validate:"required,min=1,dive,required"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
} }
type Update struct { type Update struct {
Name *string `json:"name,omitempty" validate:"omitempty"` Name *string `json:"name,omitempty" validate:"omitempty"`
KandangIDs *string `json:"kandang_ids,omitempty"` KandangIDs *[]uint `json:"kandang_ids,omitempty" validate:"omitempty,min=1,dive,required"`
IsActive *bool `json:"is_active,omitempty"` IsActive *bool `json:"is_active,omitempty"`
} }
+2
View File
@@ -11,6 +11,7 @@ import (
approvals "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals" approvals "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals"
closings "gitlab.com/mbugroup/lti-api.git/internal/modules/closings" closings "gitlab.com/mbugroup/lti-api.git/internal/modules/closings"
constants "gitlab.com/mbugroup/lti-api.git/internal/modules/constants" constants "gitlab.com/mbugroup/lti-api.git/internal/modules/constants"
dailyChecklists "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists"
expenses "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses" expenses "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses"
finance "gitlab.com/mbugroup/lti-api.git/internal/modules/finance" finance "gitlab.com/mbugroup/lti-api.git/internal/modules/finance"
inventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory" inventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory"
@@ -46,6 +47,7 @@ func Routes(app *fiber.App, db *gorm.DB) {
closings.ClosingModule{}, closings.ClosingModule{},
repports.RepportModule{}, repports.RepportModule{},
finance.FinanceModule{}, finance.FinanceModule{},
dailyChecklists.DailyChecklistModule{},
// MODULE REGISTRY // MODULE REGISTRY
} }