mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat[BE#US386]: add production standards module with CRUD operations
- Created database migration for production standards and related tables. - Implemented entities for ProductionStandard, ProductionStandardDetail, and StandardGrowthDetail. - Developed controller for handling production standard requests. - Added DTOs for data transfer between layers. - Implemented service layer for business logic related to production standards. - Created repository interfaces and implementations for data access. - Added validation for production standard requests. - Registered routes for production standards in the main application.
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type ProductionStandardController struct {
|
||||
ProductionStandardService service.ProductionStandardService
|
||||
}
|
||||
|
||||
func NewProductionStandardController(productionStandardService service.ProductionStandardService) *ProductionStandardController {
|
||||
return &ProductionStandardController{
|
||||
ProductionStandardService: productionStandardService,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *ProductionStandardController) GetAll(c *fiber.Ctx) error {
|
||||
query := &validation.Query{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
Search: c.Query("search", ""),
|
||||
ProjectCategory: c.Query("project_category", ""),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||
}
|
||||
|
||||
result, totalResults, err := u.ProductionStandardService.GetAll(c, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.ProductionStandardListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all productionStandards successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToProductionStandardListDTOs(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ProductionStandardController) 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.ProductionStandardService.GetOne(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get productionStandard successfully",
|
||||
Data: dto.ToProductionStandardDetailDTO(*result, result.StandardGrowthDetails, result.ProductionStandardDetails),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ProductionStandardController) 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.ProductionStandardService.CreateOne(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Create productionStandard successfully",
|
||||
Data: dto.ToProductionStandardDetailDTO(*result, result.StandardGrowthDetails, result.ProductionStandardDetails),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ProductionStandardController) 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.ProductionStandardService.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 productionStandard successfully",
|
||||
Data: dto.ToProductionStandardDetailDTO(*result, result.StandardGrowthDetails, result.ProductionStandardDetails),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ProductionStandardController) 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.ProductionStandardService.DeleteOne(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Common{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Delete productionStandard successfully",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
|
||||
type ProductionStandardListDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ProjectCategory string `json:"project_category"`
|
||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||
}
|
||||
|
||||
type ProductionStandardDetailDTO struct {
|
||||
ProductionStandardListDTO
|
||||
Details []WeeklyProductionStandardDTO `json:"details"`
|
||||
}
|
||||
|
||||
type GrowthStandardDetailDTO struct {
|
||||
Id uint `json:"id"`
|
||||
TargetMeanBW *float64 `json:"target_mean_bw"`
|
||||
MaxDepletion *float64 `json:"max_depletion"`
|
||||
MinUniformity float64 `json:"min_uniformity"`
|
||||
FeedIntake *float64 `json:"feed_intake"`
|
||||
}
|
||||
|
||||
type EggProductionStandardDetailDTO struct {
|
||||
Id uint `json:"id"`
|
||||
TargetHenDayProduction *float64 `json:"target_hen_day_production"`
|
||||
TargetHenHouseProduction *float64 `json:"target_hen_house_production"`
|
||||
TargetEggWeight *float64 `json:"target_egg_weight"`
|
||||
TargetEggMass *float64 `json:"target_egg_mass"`
|
||||
}
|
||||
|
||||
type WeeklyProductionStandardDTO struct {
|
||||
Week int `json:"week"`
|
||||
GrowthStandardDetail GrowthStandardDetailDTO `json:"growth_standard_detail"`
|
||||
EggProductionStandardDetailDTO *EggProductionStandardDetailDTO `json:"egg_production_standard_detail,omitempty"`
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToProductionStandardListDTO(e entity.ProductionStandard) ProductionStandardListDTO {
|
||||
var createdUser *userDTO.UserRelationDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return ProductionStandardListDTO{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
ProjectCategory: e.ProjectCategory,
|
||||
CreatedUser: createdUser,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProductionStandardListDTOs(e []entity.ProductionStandard) []ProductionStandardListDTO {
|
||||
result := make([]ProductionStandardListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToProductionStandardListDTO(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ToWeeklyProductionStandardDTO(e entity.StandardGrowthDetail) WeeklyProductionStandardDTO {
|
||||
return WeeklyProductionStandardDTO{
|
||||
Week: e.Week,
|
||||
GrowthStandardDetail: GrowthStandardDetailDTO{
|
||||
Id: e.Id,
|
||||
TargetMeanBW: e.TargetMeanBw,
|
||||
MaxDepletion: e.MaxDepletion,
|
||||
MinUniformity: e.MinUniformity,
|
||||
FeedIntake: e.FeedIntake,
|
||||
},
|
||||
EggProductionStandardDetailDTO: nil, // GROWING category - no egg production details
|
||||
}
|
||||
}
|
||||
|
||||
func ToWeeklyProductionStandardDTOWithDetails(growth entity.StandardGrowthDetail, detail entity.ProductionStandardDetail) WeeklyProductionStandardDTO {
|
||||
eggDetail := &EggProductionStandardDetailDTO{
|
||||
Id: detail.Id,
|
||||
TargetHenDayProduction: detail.TargetHenDayProduction,
|
||||
TargetHenHouseProduction: detail.TargetHenHouseProduction,
|
||||
TargetEggWeight: detail.TargetEggWeight,
|
||||
TargetEggMass: detail.TargetEggMass,
|
||||
}
|
||||
|
||||
return WeeklyProductionStandardDTO{
|
||||
Week: growth.Week,
|
||||
GrowthStandardDetail: GrowthStandardDetailDTO{
|
||||
Id: growth.Id,
|
||||
TargetMeanBW: growth.TargetMeanBw,
|
||||
MaxDepletion: growth.MaxDepletion,
|
||||
MinUniformity: growth.MinUniformity,
|
||||
FeedIntake: growth.FeedIntake,
|
||||
},
|
||||
EggProductionStandardDetailDTO: eggDetail, // LAYING category - with egg production details
|
||||
}
|
||||
}
|
||||
|
||||
func ToWeeklyProductionStandardDTOs(e []entity.StandardGrowthDetail) []WeeklyProductionStandardDTO {
|
||||
result := make([]WeeklyProductionStandardDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToWeeklyProductionStandardDTO(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ToWeeklyProductionStandardDTOsWithDetails(
|
||||
growthDetails []entity.StandardGrowthDetail,
|
||||
productionStandardDetails []entity.ProductionStandardDetail,
|
||||
) []WeeklyProductionStandardDTO {
|
||||
result := make([]WeeklyProductionStandardDTO, len(growthDetails))
|
||||
|
||||
// Create map for production standard details by week
|
||||
prodDetailMap := make(map[int]entity.ProductionStandardDetail)
|
||||
for _, detail := range productionStandardDetails {
|
||||
prodDetailMap[detail.Week] = detail
|
||||
}
|
||||
|
||||
// Map growth details and combine with production standard details
|
||||
for i, growth := range growthDetails {
|
||||
if prodDetail, exists := prodDetailMap[growth.Week]; exists {
|
||||
result[i] = ToWeeklyProductionStandardDTOWithDetails(growth, prodDetail)
|
||||
} else {
|
||||
result[i] = ToWeeklyProductionStandardDTO(growth)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func ToEggProductionStandardDetailDTO(e entity.ProductionStandardDetail) EggProductionStandardDetailDTO {
|
||||
return EggProductionStandardDetailDTO{
|
||||
TargetHenDayProduction: e.TargetHenDayProduction,
|
||||
TargetHenHouseProduction: e.TargetHenHouseProduction,
|
||||
TargetEggWeight: e.TargetEggWeight,
|
||||
TargetEggMass: e.TargetEggMass,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProductionStandardDetailDTO(
|
||||
standard entity.ProductionStandard,
|
||||
growthDetails []entity.StandardGrowthDetail,
|
||||
productionStandardDetails []entity.ProductionStandardDetail,
|
||||
) ProductionStandardDetailDTO {
|
||||
return ProductionStandardDetailDTO{
|
||||
ProductionStandardListDTO: ToProductionStandardListDTO(standard),
|
||||
Details: ToWeeklyProductionStandardDTOsWithDetails(growthDetails, productionStandardDetails),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package productionstandards
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
||||
sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services"
|
||||
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
)
|
||||
|
||||
type ProductionStandardModule struct{}
|
||||
|
||||
func (ProductionStandardModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db)
|
||||
productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
|
||||
standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
|
||||
productionStandardService := sProductionStandard.NewProductionStandardService(
|
||||
productionStandardRepo,
|
||||
productionStandardDetailRepo,
|
||||
standardGrowthDetailRepo,
|
||||
validate,
|
||||
)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
ProductionStandardRoutes(router, userService, productionStandardService)
|
||||
}
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ProductionStandardRepository interface {
|
||||
repository.BaseRepository[entity.ProductionStandard]
|
||||
GetAll(ctx context.Context, offset, limit int, modifier func(*gorm.DB) *gorm.DB) ([]entity.ProductionStandard, int64, error)
|
||||
GetByID(ctx context.Context, id uint, modifier func(*gorm.DB) *gorm.DB) (*entity.ProductionStandard, error)
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetByProjectCategory(ctx context.Context, projectCategory string) ([]entity.ProductionStandard, error)
|
||||
}
|
||||
|
||||
type ProductionStandardRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.ProductionStandard]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewProductionStandardRepository(db *gorm.DB) ProductionStandardRepository {
|
||||
return &ProductionStandardRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProductionStandard](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ProductionStandardRepositoryImpl) GetAll(ctx context.Context, offset, limit int, modifier func(*gorm.DB) *gorm.DB) ([]entity.ProductionStandard, int64, error) {
|
||||
var standards []entity.ProductionStandard
|
||||
var total int64
|
||||
|
||||
// Build base query
|
||||
q := r.db.WithContext(ctx).Model(&entity.ProductionStandard{})
|
||||
|
||||
// Apply modifier for filters
|
||||
if modifier != nil {
|
||||
q = modifier(q)
|
||||
}
|
||||
|
||||
// Count total
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Re-apply modifier and add preloads for Find
|
||||
q = r.db.WithContext(ctx).Model(&entity.ProductionStandard{})
|
||||
if modifier != nil {
|
||||
q = modifier(q)
|
||||
}
|
||||
q = q.Preload("CreatedUser")
|
||||
|
||||
// Find with offset and limit
|
||||
if err := q.Offset(offset).Limit(limit).Find(&standards).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return standards, total, nil
|
||||
}
|
||||
|
||||
func (r *ProductionStandardRepositoryImpl) GetByID(ctx context.Context, id uint, modifier func(*gorm.DB) *gorm.DB) (*entity.ProductionStandard, error) {
|
||||
var standard entity.ProductionStandard
|
||||
|
||||
q := r.db.WithContext(ctx).Model(&entity.ProductionStandard{})
|
||||
|
||||
// Apply modifier
|
||||
if modifier != nil {
|
||||
q = modifier(q)
|
||||
}
|
||||
|
||||
// Ensure CreatedUser is preloaded
|
||||
q = q.Preload("CreatedUser")
|
||||
|
||||
if err := q.First(&standard, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &standard, nil
|
||||
}
|
||||
|
||||
func (r *ProductionStandardRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||
return repository.ExistsByName[entity.ProductionStandard](ctx, r.db, name, excludeID)
|
||||
}
|
||||
|
||||
func (r *ProductionStandardRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.ProductionStandard](ctx, r.db, id)
|
||||
}
|
||||
|
||||
func (r *ProductionStandardRepositoryImpl) GetByProjectCategory(ctx context.Context, projectCategory string) ([]entity.ProductionStandard, error) {
|
||||
var standards []entity.ProductionStandard
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("CreatedUser").
|
||||
Where("project_category = ?", projectCategory).
|
||||
Where("deleted_at IS NULL").
|
||||
Find(&standards).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return standards, nil
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ProductionStandardDetailRepository interface {
|
||||
repository.BaseRepository[entity.ProductionStandardDetail]
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetByProductionStandardID(ctx context.Context, productionStandardId uint) ([]entity.ProductionStandardDetail, error)
|
||||
GetByStandardIDAndWeek(ctx context.Context, standardId uint, week int) (*entity.ProductionStandardDetail, error)
|
||||
DeleteByProductionStandardID(ctx context.Context, productionStandardId uint) error
|
||||
}
|
||||
|
||||
type ProductionStandardDetailRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.ProductionStandardDetail]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewProductionStandardDetailRepository(db *gorm.DB) ProductionStandardDetailRepository {
|
||||
return &ProductionStandardDetailRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProductionStandardDetail](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ProductionStandardDetailRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.ProductionStandardDetail](ctx, r.db, id)
|
||||
}
|
||||
|
||||
func (r *ProductionStandardDetailRepositoryImpl) GetByProductionStandardID(ctx context.Context, productionStandardId uint) ([]entity.ProductionStandardDetail, error) {
|
||||
var details []entity.ProductionStandardDetail
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("production_standard_id = ?", productionStandardId).
|
||||
Order("week ASC").
|
||||
Find(&details).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return details, nil
|
||||
}
|
||||
|
||||
func (r *ProductionStandardDetailRepositoryImpl) GetByStandardIDAndWeek(ctx context.Context, standardId uint, week int) (*entity.ProductionStandardDetail, error) {
|
||||
var detail entity.ProductionStandardDetail
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("production_standard_id = ?", standardId).
|
||||
Where("week = ?", week).
|
||||
First(&detail).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &detail, nil
|
||||
}
|
||||
|
||||
func (r *ProductionStandardDetailRepositoryImpl) DeleteByProductionStandardID(ctx context.Context, productionStandardId uint) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Where("production_standard_id = ?", productionStandardId).
|
||||
Delete(&entity.ProductionStandardDetail{}).Error
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type StandardGrowthDetailRepository interface {
|
||||
repository.BaseRepository[entity.StandardGrowthDetail]
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetByProductionStandardID(ctx context.Context, productionStandardId uint) ([]entity.StandardGrowthDetail, error)
|
||||
GetByStandardIDAndWeek(ctx context.Context, standardId uint, week int) (*entity.StandardGrowthDetail, error)
|
||||
DeleteByProductionStandardID(ctx context.Context, productionStandardId uint) error
|
||||
}
|
||||
|
||||
type StandardGrowthDetailRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.StandardGrowthDetail]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewStandardGrowthDetailRepository(db *gorm.DB) StandardGrowthDetailRepository {
|
||||
return &StandardGrowthDetailRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.StandardGrowthDetail](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *StandardGrowthDetailRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.StandardGrowthDetail](ctx, r.db, id)
|
||||
}
|
||||
|
||||
func (r *StandardGrowthDetailRepositoryImpl) GetByProductionStandardID(ctx context.Context, productionStandardId uint) ([]entity.StandardGrowthDetail, error) {
|
||||
var details []entity.StandardGrowthDetail
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("production_standard_id = ?", productionStandardId).
|
||||
Order("week ASC").
|
||||
Find(&details).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return details, nil
|
||||
}
|
||||
|
||||
func (r *StandardGrowthDetailRepositoryImpl) GetByStandardIDAndWeek(ctx context.Context, standardId uint, week int) (*entity.StandardGrowthDetail, error) {
|
||||
var detail entity.StandardGrowthDetail
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("production_standard_id = ?", standardId).
|
||||
Where("week = ?", week).
|
||||
First(&detail).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &detail, nil
|
||||
}
|
||||
|
||||
func (r *StandardGrowthDetailRepositoryImpl) DeleteByProductionStandardID(ctx context.Context, productionStandardId uint) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Where("production_standard_id = ?", productionStandardId).
|
||||
Delete(&entity.StandardGrowthDetail{}).Error
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package productionstandards
|
||||
|
||||
import (
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/controllers"
|
||||
productionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func ProductionStandardRoutes(v1 fiber.Router, u user.UserService, s productionStandard.ProductionStandardService) {
|
||||
ctrl := controller.NewProductionStandardController(s)
|
||||
|
||||
route := v1.Group("/production-standards")
|
||||
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,302 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/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 ProductionStandardService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProductionStandard, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProductionStandard, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProductionStandard, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProductionStandard, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
type productionStandardService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.ProductionStandardRepository
|
||||
ProductionStandardDetailRepo repository.ProductionStandardDetailRepository
|
||||
StandardGrowthDetailRepo repository.StandardGrowthDetailRepository
|
||||
}
|
||||
|
||||
func NewProductionStandardService(
|
||||
repo repository.ProductionStandardRepository,
|
||||
productionStandardDetailRepo repository.ProductionStandardDetailRepository,
|
||||
standardGrowthDetailRepo repository.StandardGrowthDetailRepository,
|
||||
validate *validator.Validate,
|
||||
) ProductionStandardService {
|
||||
return &productionStandardService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
ProductionStandardDetailRepo: productionStandardDetailRepo,
|
||||
StandardGrowthDetailRepo: standardGrowthDetailRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s productionStandardService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.
|
||||
Preload("CreatedUser").
|
||||
Preload("ProductionStandardDetails").
|
||||
Preload("StandardGrowthDetails")
|
||||
}
|
||||
|
||||
func (s productionStandardService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProductionStandard, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
productionStandards, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
if params.Search != "" {
|
||||
return db.Where("name LIKE ?", "%"+params.Search+"%")
|
||||
}
|
||||
if params.ProjectCategory != "" {
|
||||
return db.Where("project_category = ?", params.ProjectCategory)
|
||||
}
|
||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get productionStandards: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return productionStandards, total, nil
|
||||
}
|
||||
|
||||
func (s productionStandardService) GetOne(c *fiber.Ctx, id uint) (*entity.ProductionStandard, error) {
|
||||
productionStandard, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "ProductionStandard not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get productionStandard by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
return productionStandard, nil
|
||||
}
|
||||
|
||||
func (s *productionStandardService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProductionStandard, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nameExists, err := s.Repository.NameExists(c.Context(), req.Name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nameExists {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Production standard with name '%s' already exists", req.Name))
|
||||
}
|
||||
|
||||
var createdStandard *entity.ProductionStandard
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
standardRepoTx := repository.NewProductionStandardRepository(tx)
|
||||
productionStandardDetailRepoTx := repository.NewProductionStandardDetailRepository(tx)
|
||||
standardGrowthDetailRepoTx := repository.NewStandardGrowthDetailRepository(tx)
|
||||
|
||||
newStandard := &entity.ProductionStandard{
|
||||
Name: req.Name,
|
||||
ProjectCategory: req.ProjectCategory,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
if err := standardRepoTx.CreateOne(c.Context(), newStandard, nil); err != nil {
|
||||
return fmt.Errorf("failed to create production standard: %w", err)
|
||||
}
|
||||
|
||||
for _, detailReq := range req.Details {
|
||||
if detailReq.ProductionStandardUniformityDetails == nil {
|
||||
return fmt.Errorf("production_standard_uniformity_details is required in week %d", detailReq.Week)
|
||||
}
|
||||
|
||||
if req.ProjectCategory == string(utils.ProjectFlockCategoryLaying) {
|
||||
if detailReq.ProductionStandardDetails == nil {
|
||||
return fmt.Errorf("production_standard_details is required for LAYING category in week %d", detailReq.Week)
|
||||
}
|
||||
|
||||
productionStandardDetail := &entity.ProductionStandardDetail{
|
||||
ProductionStandardId: newStandard.Id,
|
||||
Week: detailReq.Week,
|
||||
TargetHenDayProduction: detailReq.ProductionStandardDetails.TargetHenDayProduction,
|
||||
TargetHenHouseProduction: detailReq.ProductionStandardDetails.TargetHenHouseProduction,
|
||||
TargetEggWeight: detailReq.ProductionStandardDetails.TargetEggWeight,
|
||||
TargetEggMass: detailReq.ProductionStandardDetails.TargetEggMass,
|
||||
}
|
||||
|
||||
if err := productionStandardDetailRepoTx.CreateOne(c.Context(), productionStandardDetail, nil); err != nil {
|
||||
return fmt.Errorf("failed to create production standard detail for week %d: %w", detailReq.Week, err)
|
||||
}
|
||||
}
|
||||
|
||||
standardGrowthDetail := &entity.StandardGrowthDetail{
|
||||
ProductionStandardId: newStandard.Id,
|
||||
Week: detailReq.Week,
|
||||
TargetMeanBw: detailReq.ProductionStandardUniformityDetails.TargetMeanBw,
|
||||
MaxDepletion: detailReq.ProductionStandardUniformityDetails.MaxDepletion,
|
||||
MinUniformity: detailReq.ProductionStandardUniformityDetails.MinUniformity,
|
||||
FeedIntake: detailReq.ProductionStandardUniformityDetails.FeedIntake,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
if err := standardGrowthDetailRepoTx.CreateOne(c.Context(), standardGrowthDetail, nil); err != nil {
|
||||
return fmt.Errorf("failed to create standard growth detail for week %d: %w", detailReq.Week, err)
|
||||
}
|
||||
}
|
||||
|
||||
createdStandard = newStandard
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to create production standard: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetOne(c, createdStandard.Id)
|
||||
}
|
||||
|
||||
func (s productionStandardService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProductionStandard, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var updatedStandard *entity.ProductionStandard
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
standardRepoTx := repository.NewProductionStandardRepository(tx)
|
||||
productionStandardDetailRepoTx := repository.NewProductionStandardDetailRepository(tx)
|
||||
standardGrowthDetailRepoTx := repository.NewStandardGrowthDetailRepository(tx)
|
||||
|
||||
existingStandard, err := standardRepoTx.GetByID(c.Context(), id, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "ProductionStandard not found")
|
||||
}
|
||||
return fmt.Errorf("failed to get production standard: %w", err)
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
if req.Name != nil {
|
||||
|
||||
nameExists, err := s.Repository.NameExists(c.Context(), *req.Name, &id)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to check existing production standard: %+v", err)
|
||||
return err
|
||||
}
|
||||
if nameExists {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Production standard with name '%s' already exists", *req.Name))
|
||||
}
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
if req.ProjectCategory != nil {
|
||||
updateBody["project_category"] = *req.ProjectCategory
|
||||
}
|
||||
|
||||
if len(updateBody) > 0 {
|
||||
if err := standardRepoTx.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
return fmt.Errorf("failed to update production standard: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Details != nil && len(req.Details) > 0 {
|
||||
|
||||
projectCategory := existingStandard.ProjectCategory
|
||||
if req.ProjectCategory != nil {
|
||||
projectCategory = *req.ProjectCategory
|
||||
}
|
||||
|
||||
if err := productionStandardDetailRepoTx.DeleteByProductionStandardID(c.Context(), id); err != nil {
|
||||
return fmt.Errorf("failed to delete old production standard details: %w", err)
|
||||
}
|
||||
if err := standardGrowthDetailRepoTx.DeleteByProductionStandardID(c.Context(), id); err != nil {
|
||||
return fmt.Errorf("failed to delete old standard growth details: %w", err)
|
||||
}
|
||||
|
||||
for _, detailReq := range req.Details {
|
||||
if detailReq.ProductionStandardUniformityDetails == nil {
|
||||
return fmt.Errorf("production_standard_uniformity_details is required in week %d", detailReq.Week)
|
||||
}
|
||||
|
||||
if projectCategory == "LAYING" {
|
||||
if detailReq.ProductionStandardDetails == nil {
|
||||
return fmt.Errorf("production_standard_details is required for LAYING category in week %d", detailReq.Week)
|
||||
}
|
||||
|
||||
productionStandardDetail := &entity.ProductionStandardDetail{
|
||||
ProductionStandardId: id,
|
||||
Week: detailReq.Week,
|
||||
TargetHenDayProduction: detailReq.ProductionStandardDetails.TargetHenDayProduction,
|
||||
TargetHenHouseProduction: detailReq.ProductionStandardDetails.TargetHenHouseProduction,
|
||||
TargetEggWeight: detailReq.ProductionStandardDetails.TargetEggWeight,
|
||||
TargetEggMass: detailReq.ProductionStandardDetails.TargetEggMass,
|
||||
}
|
||||
|
||||
if err := productionStandardDetailRepoTx.CreateOne(c.Context(), productionStandardDetail, nil); err != nil {
|
||||
return fmt.Errorf("failed to create production standard detail for week %d: %w", detailReq.Week, err)
|
||||
}
|
||||
}
|
||||
|
||||
standardGrowthDetail := &entity.StandardGrowthDetail{
|
||||
ProductionStandardId: id,
|
||||
Week: detailReq.Week,
|
||||
TargetMeanBw: detailReq.ProductionStandardUniformityDetails.TargetMeanBw,
|
||||
MaxDepletion: detailReq.ProductionStandardUniformityDetails.MaxDepletion,
|
||||
MinUniformity: detailReq.ProductionStandardUniformityDetails.MinUniformity,
|
||||
FeedIntake: detailReq.ProductionStandardUniformityDetails.FeedIntake,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
if err := standardGrowthDetailRepoTx.CreateOne(c.Context(), standardGrowthDetail, nil); err != nil {
|
||||
return fmt.Errorf("failed to create standard growth detail for week %d: %w", detailReq.Week, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatedStandard = existingStandard
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to update production standard: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetOne(c, updatedStandard.Id)
|
||||
}
|
||||
|
||||
func (s productionStandardService) 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, "ProductionStandard not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to delete productionStandard: %+v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package validation
|
||||
|
||||
type ProductionStandardDetailItem struct {
|
||||
TargetHenDayProduction *float64 `json:"target_hen_day_production" validate:"omitempty,gte=0"`
|
||||
TargetHenHouseProduction *float64 `json:"target_hen_house_production" validate:"omitempty,gte=0"`
|
||||
TargetEggWeight *float64 `json:"target_egg_weight" validate:"omitempty,gte=0"`
|
||||
TargetEggMass *float64 `json:"target_egg_mass" validate:"omitempty,gte=0"`
|
||||
}
|
||||
|
||||
type StandardGrowthDetailItem struct {
|
||||
TargetMeanBw *float64 `json:"target_mean_bw" validate:"omitempty,gte=0"`
|
||||
MaxDepletion *float64 `json:"max_depletion" validate:"omitempty,gte=0,lte=100"`
|
||||
MinUniformity float64 `json:"min_uniformity" validate:"required,gte=0,lte=100"`
|
||||
FeedIntake *float64 `json:"feed_intake" validate:"omitempty,gte=0"`
|
||||
}
|
||||
|
||||
type DetailItem struct {
|
||||
Week int `json:"week" validate:"required,gte=1"`
|
||||
ProductionStandardDetails *ProductionStandardDetailItem `json:"production_standard_details,omitempty"`
|
||||
ProductionStandardUniformityDetails *StandardGrowthDetailItem `json:"production_standard_uniformity_details" validate:"required"`
|
||||
}
|
||||
|
||||
|
||||
type Create struct {
|
||||
Name string `json:"name" validate:"required,min=3"`
|
||||
ProjectCategory string `json:"project_category" validate:"required,oneof=GROWING LAYING"`
|
||||
Details []DetailItem `json:"details" validate:"required,min=1,dive"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=3"`
|
||||
ProjectCategory *string `json:"project_category,omitempty" validate:"omitempty,oneof=GROWING LAYING"`
|
||||
Details []DetailItem `json:"details,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"`
|
||||
ProjectCategory string `query:"project_category" validate:"omitempty,oneof=GROWING LAYING"`
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
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
|
||||
)
|
||||
|
||||
@@ -40,6 +41,7 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida
|
||||
products.ProductModule{},
|
||||
banks.BankModule{},
|
||||
flocks.FlockModule{},
|
||||
productionStandards.ProductionStandardModule{},
|
||||
// MODULE REGISTRY
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user