mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 15:25:43 +00:00
FIX[BE]: period without autoincrement
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
DROP INDEX IF EXISTS project_flocks_flock_period_unique;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
CREATE UNIQUE INDEX project_flocks_flock_period_unique
|
||||||
|
ON project_flocks (flock_id, period)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
|
|
||||||
type ProjectFlock struct {
|
type ProjectFlock struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
FlockId uint `gorm:"not null"`
|
FlockId uint `gorm:"not null;uniqueIndex:idx_project_flocks_flock_period,priority:1"`
|
||||||
AreaId uint `gorm:"not null"`
|
AreaId uint `gorm:"not null"`
|
||||||
ProductCategoryId uint `gorm:"not null"`
|
ProductCategoryId uint `gorm:"not null"`
|
||||||
FcrId uint `gorm:"not null"`
|
FcrId uint `gorm:"not null"`
|
||||||
LocationId uint `gorm:"not null"`
|
LocationId uint `gorm:"not null"`
|
||||||
Period int `gorm:"not null"`
|
Period int `gorm:"not null;uniqueIndex:idx_project_flocks_flock_period,priority:2"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Recording struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Name string `gorm:"not null;uniqueIndex:idx_name,where:deleted_at IS NULL"`
|
||||||
|
CreatedBy uint `gorm:"not null"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
}
|
||||||
@@ -57,7 +57,6 @@ func (r *ProjectflockRepositoryImpl) GetMaxPeriodByFlock(ctx context.Context, fl
|
|||||||
var max int
|
var max int
|
||||||
if err := r.DB().WithContext(ctx).
|
if err := r.DB().WithContext(ctx).
|
||||||
Model(&entity.ProjectFlock{}).
|
Model(&entity.ProjectFlock{}).
|
||||||
Unscoped().
|
|
||||||
Where("flock_id = ?", flockID).
|
Where("flock_id = ?", flockID).
|
||||||
Select("COALESCE(MAX(period), 0)").
|
Select("COALESCE(MAX(period), 0)").
|
||||||
Scan(&max).Error; err != nil {
|
Scan(&max).Error; err != nil {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectflockService interface {
|
type ProjectflockService interface {
|
||||||
@@ -30,12 +31,12 @@ type projectflockService struct {
|
|||||||
Log *logrus.Logger
|
Log *logrus.Logger
|
||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
Repository repository.ProjectflockRepository
|
Repository repository.ProjectflockRepository
|
||||||
FlockRepo flockRepository.FlockRepository
|
FlockRepo flockRepository.FlockRepository
|
||||||
KandangRepo kandangRepository.KandangRepository
|
KandangRepo kandangRepository.KandangRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlockPeriodSummary struct {
|
type FlockPeriodSummary struct {
|
||||||
Flock entity.Flock
|
Flock entity.Flock
|
||||||
NextPeriod int
|
NextPeriod int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ func NewProjectflockService(
|
|||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
FlockRepo: flockRepo,
|
FlockRepo: flockRepo,
|
||||||
KandangRepo: kandangRepo,
|
KandangRepo: kandangRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,19 +128,33 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nextPeriod int
|
||||||
|
periodQuery := tx.Model(&entity.ProjectFlock{}).
|
||||||
|
Where("flock_id = ?", req.FlockId).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"})
|
||||||
|
if err := periodQuery.Select("COALESCE(MAX(period), 0)").Scan(&nextPeriod).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
s.Log.Errorf("Failed to determine next period for flock %d: %+v", req.FlockId, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to determine next period")
|
||||||
|
}
|
||||||
|
nextPeriod++
|
||||||
|
|
||||||
projectRepo := s.Repository.WithTx(tx)
|
projectRepo := s.Repository.WithTx(tx)
|
||||||
createBody := &entity.ProjectFlock{
|
createBody := &entity.ProjectFlock{
|
||||||
FlockId: req.FlockId,
|
FlockId: req.FlockId,
|
||||||
AreaId: req.AreaId,
|
AreaId: req.AreaId,
|
||||||
ProductCategoryId: req.ProductCategoryId,
|
ProductCategoryId: req.ProductCategoryId,
|
||||||
FcrId: req.FcrId,
|
FcrId: req.FcrId,
|
||||||
LocationId: req.LocationId,
|
LocationId: req.LocationId,
|
||||||
Period: req.Period,
|
Period: nextPeriod,
|
||||||
CreatedBy: 1,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, "Project flock period already exists")
|
||||||
|
}
|
||||||
s.Log.Errorf("Failed to create projectflock: %+v", err)
|
s.Log.Errorf("Failed to create projectflock: %+v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -353,7 +368,7 @@ func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, flockID uint) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &FlockPeriodSummary{
|
return &FlockPeriodSummary{
|
||||||
Flock: *flock,
|
Flock: *flock,
|
||||||
NextPeriod: maxPeriod + 1,
|
NextPeriod: maxPeriod + 1,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
FlockId uint `json:"flock_id" validate:"required_strict,number,gt=0"`
|
FlockId uint `json:"flock_id" validate:"required_strict,number,gt=0"`
|
||||||
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
||||||
ProductCategoryId uint `json:"product_category_id" validate:"required_strict,number,gt=0"`
|
ProductCategoryId uint `json:"product_category_id" validate:"required_strict,number,gt=0"`
|
||||||
FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"`
|
FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"`
|
||||||
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
||||||
Period int `json:"period" validate:"required_strict,number,gt=0"`
|
|
||||||
KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"`
|
KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
FlockId *uint `json:"flock_id,omitempty" validate:"omitempty,number,gt=0"`
|
FlockId *uint `json:"flock_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"`
|
AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
ProductCategoryId *uint `json:"product_category_id,omitempty" validate:"omitempty,number,gt=0"`
|
ProductCategoryId *uint `json:"product_category_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
FcrId *uint `json:"fcr_id,omitempty" validate:"omitempty,number,gt=0"`
|
FcrId *uint `json:"fcr_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/dto"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecordingController struct {
|
||||||
|
RecordingService service.RecordingService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordingController(recordingService service.RecordingService) *RecordingController {
|
||||||
|
return &RecordingController{
|
||||||
|
RecordingService: recordingService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
||||||
|
query := &validation.Query{
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
Search: c.Query("search", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, totalResults, err := u.RecordingService.GetAll(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.SuccessWithPaginate[dto.RecordingListDTO]{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get all recordings successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: query.Limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: dto.ToRecordingListDTOs(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) 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.RecordingService.GetOne(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get recording successfully",
|
||||||
|
Data: dto.ToRecordingListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) 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.RecordingService.CreateOne(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create recording successfully",
|
||||||
|
Data: dto.ToRecordingListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) 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.RecordingService.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 recording successfully",
|
||||||
|
Data: dto.ToRecordingListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) 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.RecordingService.DeleteOne(c, uint(id)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Common{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Delete recording successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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 RecordingBaseDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordingListDTO struct {
|
||||||
|
RecordingBaseDTO
|
||||||
|
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordingDetailDTO struct {
|
||||||
|
RecordingListDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Mapper Functions ===
|
||||||
|
|
||||||
|
func ToRecordingBaseDTO(e entity.Recording) RecordingBaseDTO {
|
||||||
|
return RecordingBaseDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
Name: e.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToRecordingListDTO(e entity.Recording) RecordingListDTO {
|
||||||
|
var createdUser *userDTO.UserBaseDTO
|
||||||
|
if e.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||||
|
createdUser = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return RecordingListDTO{
|
||||||
|
RecordingBaseDTO: ToRecordingBaseDTO(e),
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToRecordingListDTOs(e []entity.Recording) []RecordingListDTO {
|
||||||
|
result := make([]RecordingListDTO, len(e))
|
||||||
|
for i, r := range e {
|
||||||
|
result[i] = ToRecordingListDTO(r)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToRecordingDetailDTO(e entity.Recording) RecordingDetailDTO {
|
||||||
|
return RecordingDetailDTO{
|
||||||
|
RecordingListDTO: ToRecordingListDTO(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package recordings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
|
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
||||||
|
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecordingModule struct{}
|
||||||
|
|
||||||
|
func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
recordingRepo := rRecording.NewRecordingRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
|
recordingService := sRecording.NewRecordingService(recordingRepo, validate)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
RecordingRoutes(router, userService, recordingService)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 RecordingRepository interface {
|
||||||
|
repository.BaseRepository[entity.Recording]
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordingRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.Recording]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordingRepository(db *gorm.DB) RecordingRepository {
|
||||||
|
return &RecordingRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.Recording](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package recordings
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/controllers"
|
||||||
|
recording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RecordingRoutes(v1 fiber.Router, u user.UserService, s recording.RecordingService) {
|
||||||
|
ctrl := controller.NewRecordingController(s)
|
||||||
|
|
||||||
|
route := v1.Group("/recordings")
|
||||||
|
|
||||||
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
|
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||||
|
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||||
|
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||||
|
|
||||||
|
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,129 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/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 RecordingService interface {
|
||||||
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Recording, int64, error)
|
||||||
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.Recording, error)
|
||||||
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Recording, error)
|
||||||
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, error)
|
||||||
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type recordingService struct {
|
||||||
|
Log *logrus.Logger
|
||||||
|
Validate *validator.Validate
|
||||||
|
Repository repository.RecordingRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordingService(repo repository.RecordingRepository, validate *validator.Validate) RecordingService {
|
||||||
|
return &recordingService{
|
||||||
|
Log: utils.Log,
|
||||||
|
Validate: validate,
|
||||||
|
Repository: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("CreatedUser")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Recording, int64, error) {
|
||||||
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
|
recordings, 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 recordings: %+v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return recordings, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) GetOne(c *fiber.Ctx, id uint) (*entity.Recording, error) {
|
||||||
|
recording, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Recording not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed get recording by id: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return recording, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Recording, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
createBody := &entity.Recording{
|
||||||
|
Name: req.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create recording: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetOne(c, createBody.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, 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, "Recording not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to update recording: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetOne(c, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) 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, "Recording not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to delete recording: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
projectflocks "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks"
|
projectflocks "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks"
|
||||||
|
recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings"
|
||||||
// MODULE IMPORTS
|
// MODULE IMPORTS
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida
|
|||||||
|
|
||||||
allModules := []modules.Module{
|
allModules := []modules.Module{
|
||||||
projectflocks.ProjectflockModule{},
|
projectflocks.ProjectflockModule{},
|
||||||
|
recordings.RecordingModule{},
|
||||||
// MODULE REGISTRY
|
// MODULE REGISTRY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,11 @@ func TestProjectFlockSummary(t *testing.T) {
|
|||||||
kandangID := createKandang(t, app, "Kandang Summary", locationID, 1)
|
kandangID := createKandang(t, app, "Kandang Summary", locationID, 1)
|
||||||
|
|
||||||
createPayload := map[string]any{
|
createPayload := map[string]any{
|
||||||
"flock_id": flockID,
|
"flock_id": flockID,
|
||||||
"area_id": areaID,
|
"area_id": areaID,
|
||||||
"product_category_id": categoryID,
|
"product_category_id": categoryID,
|
||||||
"fcr_id": fcrID,
|
"fcr_id": fcrID,
|
||||||
"location_id": locationID,
|
"location_id": locationID,
|
||||||
"period": 1,
|
|
||||||
"kandang_ids": []uint{kandangID},
|
"kandang_ids": []uint{kandangID},
|
||||||
}
|
}
|
||||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", createPayload)
|
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", createPayload)
|
||||||
@@ -37,14 +36,9 @@ func TestProjectFlockSummary(t *testing.T) {
|
|||||||
|
|
||||||
var createResp struct {
|
var createResp struct {
|
||||||
Data struct {
|
Data struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
FlockId uint `json:"flock_id"`
|
Period int `json:"period"`
|
||||||
AreaId uint `json:"area_id"`
|
Flock struct {
|
||||||
ProductCategoryId uint `json:"product_category_id"`
|
|
||||||
FcrId uint `json:"fcr_id"`
|
|
||||||
LocationId uint `json:"location_id"`
|
|
||||||
Period int `json:"period"`
|
|
||||||
Flock struct {
|
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
} `json:"flock"`
|
} `json:"flock"`
|
||||||
@@ -82,18 +76,47 @@ func TestProjectFlockSummary(t *testing.T) {
|
|||||||
if err := json.Unmarshal(body, &createResp); err != nil {
|
if err := json.Unmarshal(body, &createResp); err != nil {
|
||||||
t.Fatalf("failed to parse create response: %v", err)
|
t.Fatalf("failed to parse create response: %v", err)
|
||||||
}
|
}
|
||||||
if createResp.Data.FlockId != flockID || createResp.Data.Flock.Name == "" {
|
if createResp.Data.Flock.Id != flockID || createResp.Data.Flock.Name == "" {
|
||||||
t.Fatalf("expected flock detail to be present, got %+v", createResp.Data.Flock)
|
t.Fatalf("expected flock detail to be present, got %+v", createResp.Data.Flock)
|
||||||
}
|
}
|
||||||
if createResp.Data.AreaId != areaID || createResp.Data.Area.Name == "" {
|
if createResp.Data.Area.Id != areaID || createResp.Data.Area.Name == "" {
|
||||||
t.Fatalf("expected area detail to be present, got %+v", createResp.Data.Area)
|
t.Fatalf("expected area detail to be present, got %+v", createResp.Data.Area)
|
||||||
}
|
}
|
||||||
if createResp.Data.LocationId != locationID || createResp.Data.Location.Name == "" {
|
if createResp.Data.Location.Id != locationID || createResp.Data.Location.Name == "" {
|
||||||
t.Fatalf("expected location detail to be present, got %+v", createResp.Data.Location)
|
t.Fatalf("expected location detail to be present, got %+v", createResp.Data.Location)
|
||||||
}
|
}
|
||||||
if len(createResp.Data.Kandangs) != 1 || createResp.Data.Kandangs[0].Id != kandangID {
|
if len(createResp.Data.Kandangs) != 1 || createResp.Data.Kandangs[0].Id != kandangID {
|
||||||
t.Fatalf("expected kandang detail to be present, got %+v", createResp.Data.Kandangs)
|
t.Fatalf("expected kandang detail to be present, got %+v", createResp.Data.Kandangs)
|
||||||
}
|
}
|
||||||
|
if createResp.Data.Period != 1 {
|
||||||
|
t.Fatalf("expected period 1 to be assigned automatically, got %d", createResp.Data.Period)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondKandangID := createKandang(t, app, "Kandang Summary 2", locationID, 1)
|
||||||
|
secondPayload := map[string]any{
|
||||||
|
"flock_id": flockID,
|
||||||
|
"area_id": areaID,
|
||||||
|
"product_category_id": categoryID,
|
||||||
|
"fcr_id": fcrID,
|
||||||
|
"location_id": locationID,
|
||||||
|
"kandang_ids": []uint{secondKandangID},
|
||||||
|
}
|
||||||
|
resp, body = doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", secondPayload)
|
||||||
|
if resp.StatusCode != fiber.StatusCreated {
|
||||||
|
t.Fatalf("expected 201 when creating second project flock, got %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
var createRespSecond struct {
|
||||||
|
Data struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &createRespSecond); err != nil {
|
||||||
|
t.Fatalf("failed to parse second create response: %v", err)
|
||||||
|
}
|
||||||
|
if createRespSecond.Data.Period != 2 {
|
||||||
|
t.Fatalf("expected second period to be 2, got %d", createRespSecond.Data.Period)
|
||||||
|
}
|
||||||
|
|
||||||
resp, body = doJSONRequest(t, app, http.MethodGet, "/api/production/project_flocks/flocks/"+uintToString(flockID)+"/periods", nil)
|
resp, body = doJSONRequest(t, app, http.MethodGet, "/api/production/project_flocks/flocks/"+uintToString(flockID)+"/periods", nil)
|
||||||
if resp.StatusCode != fiber.StatusOK {
|
if resp.StatusCode != fiber.StatusOK {
|
||||||
@@ -109,8 +132,31 @@ func TestProjectFlockSummary(t *testing.T) {
|
|||||||
t.Fatalf("failed to parse summary response: %v", err)
|
t.Fatalf("failed to parse summary response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if summary.Data.NextPeriod != 2 {
|
if summary.Data.NextPeriod != 3 {
|
||||||
t.Fatalf("expected next_period 2, got %d", summary.Data.NextPeriod)
|
t.Fatalf("expected next_period 3, got %d", summary.Data.NextPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, body = doJSONRequest(t, app, http.MethodDelete, "/api/production/project_flocks/"+uintToString(createResp.Data.Id), nil)
|
||||||
|
if resp.StatusCode != fiber.StatusOK {
|
||||||
|
t.Fatalf("expected 200 when deleting first project flock, got %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, body = doJSONRequest(t, app, http.MethodDelete, "/api/production/project_flocks/"+uintToString(createRespSecond.Data.Id), nil)
|
||||||
|
if resp.StatusCode != fiber.StatusOK {
|
||||||
|
t.Fatalf("expected 200 when deleting second project flock, got %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, body = doJSONRequest(t, app, http.MethodGet, "/api/production/project_flocks/flocks/"+uintToString(flockID)+"/periods", nil)
|
||||||
|
if resp.StatusCode != fiber.StatusOK {
|
||||||
|
t.Fatalf("expected 200 when fetching summary after delete, got %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &summary); err != nil {
|
||||||
|
t.Fatalf("failed to parse summary response after delete: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if summary.Data.NextPeriod != 1 {
|
||||||
|
t.Fatalf("expected next_period 1 after soft deletes, got %d", summary.Data.NextPeriod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user