Merge branch 'dev/daily-checklist' into 'development'

add module daily checklist and master data employee, phase

See merge request mbugroup/lti-api!134
This commit is contained in:
Hafizh A. Y.
2026-01-06 10:09:12 +00:00
47 changed files with 2784 additions and 1 deletions
@@ -0,0 +1,12 @@
DROP TABLE IF EXISTS daily_checklist_tasks;
DROP TABLE IF EXISTS daily_checklist_activity_task_assignees;
DROP TABLE IF EXISTS daily_checklist_activity_tasks;
DROP TABLE IF EXISTS daily_checklist_phases;
DROP TABLE IF EXISTS daily_checklists;
DROP TABLE IF EXISTS checklists;
DROP TABLE IF EXISTS phase_activities;
DROP TABLE IF EXISTS phases;
DROP TABLE IF EXISTS employee_kandangs;
DROP TABLE IF EXISTS employees;
DROP TYPE IF EXISTS category_code;
@@ -0,0 +1,194 @@
CREATE TYPE category_code AS ENUM (
'pullet_open',
'pullet_close',
'produksi_open',
'produksi_close'
);
-- MASTER TABLES
CREATE TABLE employees (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name varchar NOT NULL,
is_active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE employee_kandangs (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
employee_id bigint NOT NULL,
kandang_id bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT fk_employee_kandangs_employee
FOREIGN KEY (employee_id) REFERENCES employees(id)
ON DELETE CASCADE,
CONSTRAINT fk_employee_kandangs_kandang
FOREIGN KEY (kandang_id) REFERENCES kandangs(id)
ON DELETE CASCADE,
CONSTRAINT uq_employee_kandangs UNIQUE (employee_id, kandang_id)
);
-- PHASE & CHECKLIST
CREATE TABLE phases (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name varchar NOT NULL,
is_active boolean NOT NULL DEFAULT true,
category category_code NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE phase_activities (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
phase_id bigint NOT NULL,
name varchar NOT NULL,
description text,
time_type text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT fk_phase_activities_phase
FOREIGN KEY (phase_id) REFERENCES phases(id)
ON DELETE CASCADE
);
CREATE TABLE checklists (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name varchar NOT NULL,
description text,
phase_id bigint,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
CONSTRAINT fk_checklists_phase
FOREIGN KEY (phase_id) REFERENCES phases(id)
ON DELETE SET NULL
);
-- DAILY CHECKLISTS
CREATE TABLE daily_checklists (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kandang_id bigint NOT NULL,
checklist_id bigint NOT NULL,
date date NOT NULL,
name varchar,
status varchar,
category category_code NOT NULL,
total_score integer,
document_path varchar,
reject_reason text,
created_by bigint,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT fk_daily_checklists_kandang
FOREIGN KEY (kandang_id) REFERENCES kandangs(id)
ON DELETE CASCADE,
CONSTRAINT fk_daily_checklists_checklist
FOREIGN KEY (checklist_id) REFERENCES checklists(id)
ON DELETE RESTRICT,
CONSTRAINT fk_daily_checklists_created_by
FOREIGN KEY (created_by) REFERENCES users(id)
ON DELETE SET NULL
);
--RELASI CHECKLIST ⇄ PHASE
CREATE TABLE daily_checklist_phases (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
checklist_id bigint NOT NULL,
phase_id bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT fk_dcp_checklist
FOREIGN KEY (checklist_id) REFERENCES checklists(id)
ON DELETE CASCADE,
CONSTRAINT fk_dcp_phase
FOREIGN KEY (phase_id) REFERENCES phases(id)
ON DELETE CASCADE,
CONSTRAINT uq_daily_checklist_phases UNIQUE (checklist_id, phase_id)
);
--ACTIVITY TASKS & ASSIGNMENT
CREATE TABLE daily_checklist_activity_tasks (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
checklist_id bigint NOT NULL,
phase_id bigint NOT NULL,
phase_activity_id bigint NOT NULL,
time_type text,
notes text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT fk_dcat_checklist
FOREIGN KEY (checklist_id) REFERENCES checklists(id)
ON DELETE CASCADE,
CONSTRAINT fk_dcat_phase
FOREIGN KEY (phase_id) REFERENCES phases(id)
ON DELETE CASCADE,
CONSTRAINT fk_dcat_phase_activity
FOREIGN KEY (phase_activity_id) REFERENCES phase_activities(id)
ON DELETE CASCADE
);
CREATE TABLE daily_checklist_activity_task_assignments (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
task_id bigint NOT NULL,
employee_id bigint NOT NULL,
checked boolean NOT NULL DEFAULT false,
note text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT fk_assignment_task
FOREIGN KEY (task_id) REFERENCES daily_checklist_activity_tasks(id)
ON DELETE CASCADE,
CONSTRAINT fk_assignment_employee
FOREIGN KEY (employee_id) REFERENCES employees(id)
ON DELETE CASCADE
);
--DAILY CHECKLIST TASK RESULT
CREATE TABLE daily_checklist_tasks (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
daily_checklist_id bigint NOT NULL,
checklist_id bigint NOT NULL,
checklist_item_id bigint,
is_completed boolean NOT NULL DEFAULT false,
score_value integer,
notes text,
photo_proof varchar,
status varchar,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT fk_dct_daily
FOREIGN KEY (daily_checklist_id) REFERENCES daily_checklists(id)
ON DELETE CASCADE,
CONSTRAINT fk_dct_checklist
FOREIGN KEY (checklist_id) REFERENCES checklists(id)
ON DELETE CASCADE,
CONSTRAINT fk_dct_checklist_item
FOREIGN KEY (checklist_item_id) REFERENCES phase_activities(id)
ON DELETE SET NULL
);
@@ -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"`
}
+31
View File
@@ -0,0 +1,31 @@
package entities
import (
"time"
"gorm.io/gorm"
)
type Employee struct {
Id uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
IsActive bool `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
EmployeeKandangs []EmployeeKandang `gorm:"foreignKey:EmployeeId;references:Id"`
}
type Employees = Employee
type EmployeeKandang struct {
Id uint `gorm:"primaryKey"`
EmployeeId uint `gorm:"not null"`
KandangId uint `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
Employee Employee `gorm:"foreignKey:EmployeeId;references:Id"`
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
}
+43
View File
@@ -0,0 +1,43 @@
package entities
import (
"time"
"gorm.io/gorm"
)
type Phases struct {
Id uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
IsActive bool `gorm:"not null;default:true"`
Category string `gorm:"type:category_code;not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Activities []PhaseActivity `gorm:"foreignKey:PhaseId;references:Id"`
}
type PhaseActivity struct {
Id uint `gorm:"primaryKey"`
PhaseId uint `gorm:"not null"`
Name string `gorm:"not null"`
Description *string `gorm:"type:text"`
TimeType *string `gorm:"type:text"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Phase Phases `gorm:"foreignKey:PhaseId;references:Id"`
}
type Checklist struct {
Id uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
Description *string `gorm:"type:text"`
PhaseId *uint
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
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"`
}
@@ -0,0 +1,161 @@
package controller
import (
"math"
"strconv"
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/dto"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/services"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/validations"
"gitlab.com/mbugroup/lti-api.git/internal/response"
"github.com/gofiber/fiber/v2"
)
type EmployeesController struct {
EmployeesService service.EmployeesService
}
func NewEmployeesController(employeesService service.EmployeesService) *EmployeesController {
return &EmployeesController{
EmployeesService: employeesService,
}
}
func (u *EmployeesController) 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 kandangParam := c.Query("kandang_id", ""); kandangParam != "" {
id, err := strconv.Atoi(kandangParam)
if err != nil || id <= 0 {
return fiber.NewError(fiber.StatusBadRequest, "invalid kandang_id")
}
temp := uint(id)
query.KandangId = &temp
}
if activeParam := c.Query("is_active", ""); activeParam != "" {
value, err := strconv.ParseBool(activeParam)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "invalid is_active value")
}
query.IsActive = &value
}
result, totalResults, err := u.EmployeesService.GetAll(c, query)
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.SuccessWithPaginate[dto.EmployeesListDTO]{
Code: fiber.StatusOK,
Status: "success",
Message: "Get all employeess successfully",
Meta: response.Meta{
Page: query.Page,
Limit: query.Limit,
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
TotalResults: totalResults,
},
Data: dto.ToEmployeesListDTOs(result),
})
}
func (u *EmployeesController) 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.EmployeesService.GetOne(c, uint(id))
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: "Get employees successfully",
Data: dto.ToEmployeesListDTO(*result),
})
}
func (u *EmployeesController) 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.EmployeesService.CreateOne(c, req)
if err != nil {
return err
}
return c.Status(fiber.StatusCreated).
JSON(response.Success{
Code: fiber.StatusCreated,
Status: "success",
Message: "Create employees successfully",
Data: dto.ToEmployeesListDTO(*result),
})
}
func (u *EmployeesController) 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.EmployeesService.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 employees successfully",
Data: dto.ToEmployeesListDTO(*result),
})
}
func (u *EmployeesController) 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.EmployeesService.DeleteOne(c, uint(id)); err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Common{
Code: fiber.StatusOK,
Status: "success",
Message: "Delete employees successfully",
})
}
@@ -0,0 +1,70 @@
package dto
import (
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
)
// === DTO Structs ===
type EmployeesRelationDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
}
type EmployeesListDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
IsActive bool `json:"is_active"`
Kandangs []kandangDTO.KandangRelationDTO `json:"kandangs"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type EmployeesDetailDTO struct {
EmployeesListDTO
}
// === Mapper Functions ===
func ToEmployeesRelationDTO(e entity.Employees) EmployeesRelationDTO {
return EmployeesRelationDTO{
Id: e.Id,
Name: e.Name,
}
}
func ToEmployeesListDTO(e entity.Employees) EmployeesListDTO {
kandangs := make([]kandangDTO.KandangRelationDTO, 0, len(e.EmployeeKandangs))
for _, rel := range e.EmployeeKandangs {
if rel.Kandang.Id == 0 {
continue
}
kandangs = append(kandangs, kandangDTO.ToKandangRelationDTO(rel.Kandang))
}
return EmployeesListDTO{
Id: e.Id,
Name: e.Name,
IsActive: e.IsActive,
Kandangs: kandangs,
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
}
}
func ToEmployeesListDTOs(e []entity.Employees) []EmployeesListDTO {
result := make([]EmployeesListDTO, len(e))
for i, r := range e {
result[i] = ToEmployeesListDTO(r)
}
return result
}
func ToEmployeesDetailDTO(e entity.Employees) EmployeesDetailDTO {
return EmployeesDetailDTO{
EmployeesListDTO: ToEmployeesListDTO(e),
}
}
@@ -0,0 +1,25 @@
package employeess
import (
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
rEmployees "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/repositories"
sEmployees "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/services"
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
)
type EmployeesModule struct{}
func (EmployeesModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
employeesRepo := rEmployees.NewEmployeesRepository(db)
userRepo := rUser.NewUserRepository(db)
employeesService := sEmployees.NewEmployeesService(employeesRepo, validate)
userService := sUser.NewUserService(userRepo, validate)
EmployeesRoutes(router, userService, employeesService)
}
@@ -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 EmployeesRepository interface {
repository.BaseRepository[entity.Employees]
}
type EmployeesRepositoryImpl struct {
*repository.BaseRepositoryImpl[entity.Employees]
}
func NewEmployeesRepository(db *gorm.DB) EmployeesRepository {
return &EmployeesRepositoryImpl{
BaseRepositoryImpl: repository.NewBaseRepository[entity.Employees](db),
}
}
@@ -0,0 +1,23 @@
package employeess
import (
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/controllers"
employees "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/services"
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
"github.com/gofiber/fiber/v2"
)
func EmployeesRoutes(v1 fiber.Router, u user.UserService, s employees.EmployeesService) {
ctrl := controller.NewEmployeesController(s)
route := v1.Group("/employees")
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)
}
@@ -0,0 +1,265 @@
package service
import (
"errors"
"strings"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/validations"
"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 EmployeesService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Employees, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.Employees, error)
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Employees, error)
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Employees, error)
DeleteOne(ctx *fiber.Ctx, id uint) error
}
type employeesService struct {
Log *logrus.Logger
Validate *validator.Validate
Repository repository.EmployeesRepository
}
func NewEmployeesService(repo repository.EmployeesRepository, validate *validator.Validate) EmployeesService {
return &employeesService{
Log: utils.Log,
Validate: validate,
Repository: repo,
}
}
func (s employeesService) withRelations(db *gorm.DB) *gorm.DB {
return db.
Preload("EmployeeKandangs.Kandang").
Where("employees.deleted_at IS NULL")
}
func (s employeesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Employees, int64, error) {
if err := s.Validate.Struct(params); err != nil {
return nil, 0, err
}
offset := (params.Page - 1) * params.Limit
employeess, 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("employees.name LIKE ?", "%"+params.Search+"%")
}
if params.KandangId != nil {
db = db.Joins("JOIN employee_kandangs ek ON ek.employee_id = employees.id").
Where("ek.kandang_id = ?", *params.KandangId)
}
if params.IsActive != nil {
db = db.Where("employees.is_active = ?", *params.IsActive)
}
return db.Order("employees.created_at DESC").Order("employees.updated_at DESC")
})
if err != nil {
s.Log.Errorf("Failed to get employeess: %+v", err)
return nil, 0, err
}
return employeess, total, nil
}
func (s employeesService) GetOne(c *fiber.Ctx, id uint) (*entity.Employees, error) {
employees, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Employees not found")
}
if err != nil {
s.Log.Errorf("Failed get employees by id: %+v", err)
return nil, err
}
return employees, nil
}
func (s *employeesService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Employees, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err
}
name := strings.TrimSpace(req.Name)
if name == "" {
return nil, fiber.NewError(fiber.StatusBadRequest, "name cannot be empty")
}
kandangIDs := normalizeKandangIDs(req.KandangIDs)
if len(kandangIDs) == 0 {
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 {
return db.Where("LOWER(name) = ?", strings.ToLower(name))
}); err == nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "employee already exists")
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Failed checking employee uniqueness: %+v", err)
return nil, err
}
createBody := &entity.Employees{
Name: name,
IsActive: req.IsActive,
}
if err := s.Repository.DB().Transaction(func(tx *gorm.DB) error {
repoTx := s.Repository.WithTx(tx)
if err := repoTx.CreateOne(c.Context(), createBody, nil); err != nil {
return err
}
relations := make([]entity.EmployeeKandang, 0, len(kandangIDs))
for _, kandangID := range kandangIDs {
relations = append(relations, entity.EmployeeKandang{
EmployeeId: createBody.Id,
KandangId: kandangID,
})
}
if len(relations) > 0 {
if err := tx.WithContext(c.Context()).Create(&relations).Error; err != nil {
return err
}
}
return nil
}); err != nil {
s.Log.Errorf("Failed to create employees: %+v", err)
return nil, err
}
return s.GetOne(c, createBody.Id)
}
func (s employeesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Employees, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err
}
updateBody := make(map[string]any)
var (
kandangIDs []uint
needKandangUpdate bool
)
if req.Name != nil {
trimmed := strings.TrimSpace(*req.Name)
if trimmed == "" {
return nil, fiber.NewError(fiber.StatusBadRequest, "name cannot be empty")
}
if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB {
return db.Where("LOWER(name) = ? AND id <> ?", strings.ToLower(trimmed), id)
}); err == nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "employee already exists")
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Failed checking employee uniqueness: %+v", err)
return nil, err
}
updateBody["name"] = trimmed
}
if req.IsActive != nil {
updateBody["is_active"] = *req.IsActive
}
if req.KandangIDs != nil {
ids := normalizeKandangIDs(*req.KandangIDs)
if len(ids) == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids must contain at least one valid id")
}
kandangIDs = ids
needKandangUpdate = true
}
if len(updateBody) == 0 && !needKandangUpdate {
return s.GetOne(c, id)
}
if err := s.Repository.DB().Transaction(func(tx *gorm.DB) error {
repoTx := s.Repository.WithTx(tx)
if len(updateBody) > 0 {
if err := repoTx.PatchOne(c.Context(), id, updateBody, nil); err != nil {
return err
}
}
if needKandangUpdate {
if err := tx.WithContext(c.Context()).
Where("employee_id = ?", id).
Delete(&entity.EmployeeKandang{}).Error; err != nil {
return err
}
relations := make([]entity.EmployeeKandang, 0, len(kandangIDs))
for _, kandangID := range kandangIDs {
relations = append(relations, entity.EmployeeKandang{
EmployeeId: id,
KandangId: kandangID,
})
}
if len(relations) > 0 {
if err := tx.WithContext(c.Context()).Create(&relations).Error; err != nil {
return err
}
}
}
return nil
}); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Employees not found")
}
s.Log.Errorf("Failed to update employees: %+v", err)
return nil, err
}
return s.GetOne(c, id)
}
func (s employeesService) 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, "Employees not found")
}
s.Log.Errorf("Failed to delete employees: %+v", err)
return err
}
return nil
}
func normalizeKandangIDs(ids []uint) []uint {
result := make([]uint, 0, len(ids))
seen := make(map[uint]struct{})
for _, id := range ids {
if id == 0 {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
result = append(result, id)
}
return result
}
@@ -0,0 +1,21 @@
package validation
type Create struct {
Name string `json:"name" validate:"required_strict,min=3"`
KandangIDs []uint `json:"kandang_ids" validate:"required,min=1,dive,required"`
IsActive bool `json:"is_active"`
}
type Update struct {
Name *string `json:"name,omitempty" validate:"omitempty"`
KandangIDs *[]uint `json:"kandang_ids,omitempty" validate:"omitempty,min=1,dive,required"`
IsActive *bool `json:"is_active,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"`
KandangId *uint `query:"kandang_id" validate:"omitempty"`
IsActive *bool `query:"is_active" validate:"omitempty"`
}
@@ -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",
})
}
@@ -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),
}
}
@@ -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)
}
@@ -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),
}
}
@@ -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)
}
@@ -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
}
@@ -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"`
}
@@ -0,0 +1,148 @@
package controller
import (
"math"
"strconv"
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/dto"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/services"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/validations"
"gitlab.com/mbugroup/lti-api.git/internal/response"
"github.com/gofiber/fiber/v2"
)
type PhasesController struct {
PhasesService service.PhasesService
}
func NewPhasesController(phasesService service.PhasesService) *PhasesController {
return &PhasesController{
PhasesService: phasesService,
}
}
func (u *PhasesController) 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 category := c.Query("category", ""); category != "" {
query.Category = &category
}
result, totalResults, err := u.PhasesService.GetAll(c, query)
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.SuccessWithPaginate[dto.PhasesListDTO]{
Code: fiber.StatusOK,
Status: "success",
Message: "Get all phasess successfully",
Meta: response.Meta{
Page: query.Page,
Limit: query.Limit,
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
TotalResults: totalResults,
},
Data: dto.ToPhasesListDTOs(result),
})
}
func (u *PhasesController) 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.PhasesService.GetOne(c, uint(id))
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: "Get phases successfully",
Data: dto.ToPhasesListDTO(*result),
})
}
func (u *PhasesController) 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.PhasesService.CreateOne(c, req)
if err != nil {
return err
}
return c.Status(fiber.StatusCreated).
JSON(response.Success{
Code: fiber.StatusCreated,
Status: "success",
Message: "Create phases successfully",
Data: dto.ToPhasesListDTO(*result),
})
}
func (u *PhasesController) 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.PhasesService.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 phases successfully",
Data: dto.ToPhasesListDTO(*result),
})
}
func (u *PhasesController) 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.PhasesService.DeleteOne(c, uint(id)); err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Common{
Code: fiber.StatusOK,
Status: "success",
Message: "Delete phases successfully",
})
}
@@ -0,0 +1,68 @@
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 PhasesRelationDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
}
type PhasesListDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
IsActive bool `json:"is_active"`
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
CreatedAt time.Time `json:"created_at"`
}
type PhasesDetailDTO struct {
PhasesListDTO
}
// === Mapper Functions ===
func ToPhasesRelationDTO(e entity.Phases) PhasesRelationDTO {
return PhasesRelationDTO{
Id: e.Id,
Name: e.Name,
}
}
func ToPhasesListDTO(e entity.Phases) PhasesListDTO {
var createdUser *userDTO.UserRelationDTO
// if e.CreatedUser.Id != 0 {
// mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
// createdUser = &mapped
// }
return PhasesListDTO{
Id: e.Id,
Name: e.Name,
Category: e.Category,
IsActive: e.IsActive,
CreatedAt: e.CreatedAt,
CreatedUser: createdUser,
}
}
func ToPhasesListDTOs(e []entity.Phases) []PhasesListDTO {
result := make([]PhasesListDTO, len(e))
for i, r := range e {
result[i] = ToPhasesListDTO(r)
}
return result
}
func ToPhasesDetailDTO(e entity.Phases) PhasesDetailDTO {
return PhasesDetailDTO{
PhasesListDTO: ToPhasesListDTO(e),
}
}
+25
View File
@@ -0,0 +1,25 @@
package phases
import (
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
rPhases "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/repositories"
sPhases "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/services"
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
)
type PhasesModule struct{}
func (PhasesModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
phasesRepo := rPhases.NewPhasesRepository(db)
userRepo := rUser.NewUserRepository(db)
phasesService := sPhases.NewPhasesService(phasesRepo, validate)
userService := sUser.NewUserService(userRepo, validate)
PhasesRoutes(router, userService, phasesService)
}
@@ -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 PhasesRepository interface {
repository.BaseRepository[entity.Phases]
}
type PhasesRepositoryImpl struct {
*repository.BaseRepositoryImpl[entity.Phases]
}
func NewPhasesRepository(db *gorm.DB) PhasesRepository {
return &PhasesRepositoryImpl{
BaseRepositoryImpl: repository.NewBaseRepository[entity.Phases](db),
}
}
+23
View File
@@ -0,0 +1,23 @@
package phases
import (
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/controllers"
phases "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/services"
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
"github.com/gofiber/fiber/v2"
)
func PhasesRoutes(v1 fiber.Router, u user.UserService, s phases.PhasesService) {
ctrl := controller.NewPhasesController(s)
route := v1.Group("/phases")
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)
}
@@ -0,0 +1,158 @@
package service
import (
"errors"
"strings"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/validations"
"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 PhasesService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Phases, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.Phases, error)
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Phases, error)
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Phases, error)
DeleteOne(ctx *fiber.Ctx, id uint) error
}
type phasesService struct {
Log *logrus.Logger
Validate *validator.Validate
Repository repository.PhasesRepository
}
func NewPhasesService(repo repository.PhasesRepository, validate *validator.Validate) PhasesService {
return &phasesService{
Log: utils.Log,
Validate: validate,
Repository: repo,
}
}
func (s phasesService) withRelations(db *gorm.DB) *gorm.DB {
return db
}
func (s phasesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Phases, int64, error) {
if err := s.Validate.Struct(params); err != nil {
return nil, 0, err
}
offset := (params.Page - 1) * params.Limit
phasess, 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+"%")
}
if params.Category != nil {
db = db.Where("category = ?", *params.Category)
}
return db.Order("created_at DESC")
})
if err != nil {
s.Log.Errorf("Failed to get phasess: %+v", err)
return nil, 0, err
}
return phasess, total, nil
}
func (s phasesService) GetOne(c *fiber.Ctx, id uint) (*entity.Phases, error) {
phases, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
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
}
return phases, nil
}
func (s *phasesService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Phases, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err
}
if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB {
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) {
s.Log.Errorf("Failed checking phase uniqueness: %+v", err)
return nil, err
}
createBody := &entity.Phases{
Name: req.Name,
Category: req.Category,
IsActive: true,
}
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
s.Log.Errorf("Failed to create phases: %+v", err)
return nil, err
}
return s.GetOne(c, createBody.Id)
}
func (s phasesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Phases, error) {
if err := s.Validate.Struct(req); err != nil {
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 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) {
s.Log.Errorf("Failed checking phase uniqueness: %+v", err)
return nil, err
}
updateBody["name"] = strings.TrimSpace(*req.Name)
}
if len(updateBody) == 0 {
return s.GetOne(c, id)
}
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
s.Log.Errorf("Failed to update phases: %+v", err)
return nil, err
}
return s.GetOne(c, id)
}
func (s phasesService) 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, "Phases not found")
}
s.Log.Errorf("Failed to delete phases: %+v", err)
return err
}
return nil
}
@@ -0,0 +1,17 @@
package validation
type Create struct {
Name string `json:"name" validate:"required_strict,min=3"`
Category string `json:"category" 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"`
Category *string `query:"category" validate:"omitempty"`
}
+7 -1
View File
@@ -10,17 +10,20 @@ import (
areas "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas"
banks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/banks"
customers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers"
employeess "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees"
fcrs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs"
flocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks"
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"
productionStandards "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards"
// MODULE IMPORTS
)
@@ -42,6 +45,9 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida
banks.BankModule{},
flocks.FlockModule{},
productionStandards.ProductionStandardModule{},
employeess.EmployeesModule{},
phasess.PhasesModule{},
phaseActivitys.PhaseActivityModule{},
// MODULE REGISTRY
}
+2
View File
@@ -11,6 +11,7 @@ import (
approvals "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals"
closings "gitlab.com/mbugroup/lti-api.git/internal/modules/closings"
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"
finance "gitlab.com/mbugroup/lti-api.git/internal/modules/finance"
inventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory"
@@ -46,6 +47,7 @@ func Routes(app *fiber.App, db *gorm.DB) {
closings.ClosingModule{},
repports.RepportModule{},
finance.FinanceModule{},
dailyChecklists.DailyChecklistModule{},
// MODULE REGISTRY
}