fix(BE): kandang capacity and fix err response

This commit is contained in:
Hafizh A. Y
2025-11-14 16:43:01 +07:00
parent 80c84210b8
commit 17d3042586
15 changed files with 129 additions and 84 deletions
Vendored
BIN
View File
Binary file not shown.
+40 -8
View File
@@ -3,6 +3,8 @@ package validation
import ( import (
"errors" "errors"
"fmt" "fmt"
"reflect"
"strings"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
) )
@@ -21,34 +23,41 @@ var customMessages = map[string]string{
"alphanum": "Field %s must contain only alphanumeric characters", "alphanum": "Field %s must contain only alphanumeric characters",
"oneof": "Invalid value for field %s", "oneof": "Invalid value for field %s",
"password": "Field %s must be at least 8 characters, contain uppercase, lowercase, number, and special character", "password": "Field %s must be at least 8 characters, contain uppercase, lowercase, number, and special character",
"gt": "Invalid %s, must be greater than %s",
} }
func CustomErrorMessages(err error) map[string]string { func CustomErrorMessages(err error) (string, map[string]string) {
var validationErrors validator.ValidationErrors var validationErrors validator.ValidationErrors
if errors.As(err, &validationErrors) { if errors.As(err, &validationErrors) {
return generateErrorMessages(validationErrors) return generateErrorMessages(validationErrors)
} }
return nil return "", nil
} }
func generateErrorMessages(validationErrors validator.ValidationErrors) map[string]string { func generateErrorMessages(validationErrors validator.ValidationErrors) (string, map[string]string) {
errorsMap := make(map[string]string) errorsMap := make(map[string]string)
for _, err := range validationErrors { var firstMessage string
for i, err := range validationErrors {
fieldName := err.StructNamespace() fieldName := err.StructNamespace()
tag := err.Tag() tag := err.Tag()
customMessage := customMessages[tag] customMessage := customMessages[tag]
var msg string
if customMessage != "" { if customMessage != "" {
errorsMap[fieldName] = formatErrorMessage(customMessage, err, tag) msg = formatErrorMessage(customMessage, err, tag)
} else { } else {
errorsMap[fieldName] = defaultErrorMessage(err) msg = defaultErrorMessage(err)
}
errorsMap[fieldName] = msg
if i == 0 {
firstMessage = msg
} }
} }
return errorsMap return firstMessage, errorsMap
} }
func formatErrorMessage(customMessage string, err validator.FieldError, tag string) string { func formatErrorMessage(customMessage string, err validator.FieldError, tag string) string {
if tag == "min" || tag == "max" || tag == "len" { if tag == "min" || tag == "max" || tag == "len" || tag == "gt" {
return fmt.Sprintf(customMessage, err.Field(), err.Param()) return fmt.Sprintf(customMessage, err.Field(), err.Param())
} }
return fmt.Sprintf(customMessage, err.Field()) return fmt.Sprintf(customMessage, err.Field())
@@ -61,6 +70,16 @@ func defaultErrorMessage(err validator.FieldError) string {
func Validator() *validator.Validate { func Validator() *validator.Validate {
validate := validator.New() validate := validator.New()
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
if jsonTag := getTagName(fld, "json"); jsonTag != "" {
return jsonTag
}
if queryTag := getTagName(fld, "query"); queryTag != "" {
return queryTag
}
return fld.Name
})
if err := validate.RegisterValidation("password", Password); err != nil { if err := validate.RegisterValidation("password", Password); err != nil {
return nil return nil
} }
@@ -72,3 +91,16 @@ func Validator() *validator.Validate {
} }
return validate return validate
} }
func getTagName(fld reflect.StructField, tag string) string {
value, ok := fld.Tag.Lookup(tag)
if !ok || value == "-" {
return ""
}
name := strings.Split(value, ",")[0]
if name == "" || name == "-" {
return ""
}
return name
}
BIN
View File
Binary file not shown.
@@ -0,0 +1,2 @@
ALTER TABLE kandangs
DROP COLUMN IF EXISTS capacity;
@@ -0,0 +1,2 @@
ALTER TABLE kandangs
ADD COLUMN capacity NUMERIC(15,3) NOT NULL;
+35 -42
View File
@@ -235,13 +235,14 @@ func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users
seeds := []struct { seeds := []struct {
Name string Name string
Status utils.KandangStatus Status utils.KandangStatus
Capacity float64
Location string Location string
PicKey string PicKey string
}{ }{
{Name: "Singaparna 1", Status: utils.KandangStatusNonActive, Location: "Singaparna", PicKey: "admin"}, {Name: "Singaparna 1", Status: utils.KandangStatusNonActive, Capacity: 50000, Location: "Singaparna", PicKey: "admin"},
{Name: "Singaparna 2", Status: utils.KandangStatusNonActive, Location: "Singaparna", PicKey: "admin"}, {Name: "Singaparna 2", Status: utils.KandangStatusNonActive, Capacity: 50000, Location: "Singaparna", PicKey: "admin"},
{Name: "Cikaum 1", Status: utils.KandangStatusNonActive, Location: "Cikaum", PicKey: "admin"}, {Name: "Cikaum 1", Status: utils.KandangStatusNonActive, Capacity: 50000, Location: "Cikaum", PicKey: "admin"},
{Name: "Cikaum 2", Status: utils.KandangStatusNonActive, Location: "Cikaum", PicKey: "admin"}, {Name: "Cikaum 2", Status: utils.KandangStatusNonActive, Capacity: 50000, Location: "Cikaum", PicKey: "admin"},
} }
result := make(map[string]uint, len(seeds)) result := make(map[string]uint, len(seeds))
@@ -571,52 +572,44 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories
Flags: []utils.FlagType{utils.FlagDOC}, Flags: []utils.FlagType{utils.FlagDOC},
}, },
{ {
Name: "Ayam Afkir", Name: "Ayam Afkir",
Brand: "-", Brand: "-",
Sku: "1", Sku: "1",
Uom: "Ekor", Uom: "Ekor",
Category: "Day Old Chick", Category: "Day Old Chick",
Price: 1, Price: 1,
}, },
{ {
Name: "Ayam Mati", Name: "Ayam Mati",
Brand: "-", Brand: "-",
Sku: "2", Sku: "2",
Uom: "Ekor", Uom: "Ekor",
Category: "Day Old Chick", Category: "Day Old Chick",
Price: 1, Price: 1,
}, },
{ {
Name: "Ayam Culling", Name: "Ayam Culling",
Brand: "-", Brand: "-",
Sku: "3", Sku: "3",
Uom: "Ekor", Uom: "Ekor",
Category: "Day Old Chick", Category: "Day Old Chick",
Price: 1, Price: 1,
}, },
{ {
Name: "Telur Konsumsi Baik", Name: "Telur Konsumsi Baik",
Brand: "-", Brand: "-",
Sku: "4", Sku: "4",
Uom: "Unit", Uom: "Unit",
Category: "Telur", Category: "Telur",
Price: 1, Price: 1,
}, },
{ {
Name: "Telur Pecah", Name: "Telur Pecah",
Brand: "-", Brand: "-",
Sku: "5", Sku: "5",
Uom: "Unit", Uom: "Unit",
Category: "Telur", Category: "Telur",
Price: 1, Price: 1,
}, },
{ {
Name: "281 SPECIAL STARTER", Name: "281 SPECIAL STARTER",
+13 -12
View File
@@ -7,17 +7,18 @@ import (
) )
type Kandang struct { type Kandang struct {
Id uint `gorm:"primaryKey"` Id uint `gorm:"primaryKey"`
Name string `gorm:"not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"` Name string `gorm:"not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"`
Status string `gorm:"type:varchar(50);not null"` Status string `gorm:"type:varchar(50);not null"`
LocationId uint `gorm:"not null"` LocationId uint `gorm:"not null"`
PicId uint `gorm:"not null"` Capacity float64 `gorm:"not null"`
CreatedBy uint `gorm:"not null"` PicId uint `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"` CreatedBy uint `gorm:"not null"`
UpdatedAt time.Time `gorm:"autoUpdateTime"` CreatedAt time.Time `gorm:"autoCreateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` UpdatedAt time.Time `gorm:"autoUpdateTime"`
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Location Location `gorm:"foreignKey:LocationId;references:Id"` CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
Pic User `gorm:"foreignKey:PicId;references:Id"` Location Location `gorm:"foreignKey:LocationId;references:Id"`
Pic User `gorm:"foreignKey:PicId;references:Id"`
ProjectFlockKandangs []ProjectFlockKandang `gorm:"foreignKey:KandangId;references:Id" json:"-"` ProjectFlockKandangs []ProjectFlockKandang `gorm:"foreignKey:KandangId;references:Id" json:"-"`
} }
@@ -14,6 +14,7 @@ type KandangBaseDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Status string `json:"status"` Status string `json:"status"`
Capacity float64 `json:"capacity"`
Location *locationDTO.LocationBaseDTO `json:"location"` Location *locationDTO.LocationBaseDTO `json:"location"`
Pic *userDTO.UserBaseDTO `json:"pic"` Pic *userDTO.UserBaseDTO `json:"pic"`
} }
@@ -48,6 +49,7 @@ func ToKandangBaseDTO(e entity.Kandang) KandangBaseDTO {
Id: e.Id, Id: e.Id,
Name: e.Name, Name: e.Name,
Status: e.Status, Status: e.Status,
Capacity: e.Capacity,
Location: location, Location: location,
Pic: pic, Pic: pic,
} }
+2 -2
View File
@@ -1,7 +1,7 @@
package kandangs package kandangs
import ( import (
m "gitlab.com/mbugroup/lti-api.git/internal/middleware" // m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/controllers" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/controllers"
kandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/services" kandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/services"
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
@@ -13,7 +13,7 @@ func KandangRoutes(v1 fiber.Router, u user.UserService, s kandang.KandangService
ctrl := controller.NewKandangController(s) ctrl := controller.NewKandangController(s)
route := v1.Group("/kandangs") route := v1.Group("/kandangs")
route.Use(m.Auth(u)) // route.Use(m.Auth(u))
route.Get("/", ctrl.GetAll) route.Get("/", ctrl.GetAll)
route.Post("/", ctrl.CreateOne) route.Post("/", ctrl.CreateOne)
@@ -134,6 +134,7 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
createBody := &entity.Kandang{ createBody := &entity.Kandang{
Name: req.Name, Name: req.Name,
LocationId: req.LocationId, LocationId: req.LocationId,
Capacity: req.Capacity,
Status: status, Status: status,
PicId: req.PicId, PicId: req.PicId,
CreatedBy: 1, CreatedBy: 1,
@@ -194,6 +195,10 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
updateBody["pic_id"] = *req.PicId updateBody["pic_id"] = *req.PicId
} }
if req.Capacity != nil {
updateBody["capacity"] = *req.Capacity
}
finalStatus := strings.ToUpper(existing.Status) finalStatus := strings.ToUpper(existing.Status)
if req.Status != nil { if req.Status != nil {
status := strings.ToUpper(*req.Status) status := strings.ToUpper(*req.Status)
@@ -1,19 +1,21 @@
package validation package validation
type Create struct { type Create struct {
Name string `json:"name" validate:"required_strict,min=3"` Name string `json:"name" validate:"required_strict,min=3"`
Status string `json:"status,omitempty" validate:"omitempty,min=3"` Status string `json:"status,omitempty" validate:"omitempty,min=3"`
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"` Capacity float64 `json:"capacity" validate:"required_strict,gt=0"`
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"` LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
ProjectFlockId *uint `json:"project_flock_id" validate:"omitempty,number,gt=0"` PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
ProjectFlockId *uint `json:"project_flock_id" validate:"omitempty,number,gt=0"`
} }
type Update struct { type Update struct {
Name *string `json:"name,omitempty" validate:"omitempty"` Name *string `json:"name,omitempty" validate:"omitempty"`
Status *string `json:"status,omitempty" validate:"omitempty,min=3"` Status *string `json:"status,omitempty" validate:"omitempty,min=3"`
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"` Capacity *float64 `json:"capacity" validate:"omitempty,gt=0"`
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"` LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
ProjectFlockId *uint `json:"project_flock_id,omitempty" validate:"omitempty,number,gt=0"` PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
ProjectFlockId *uint `json:"project_flock_id,omitempty" validate:"omitempty,number,gt=0"`
} }
type Query struct { type Query struct {
+4 -2
View File
@@ -15,7 +15,8 @@ type UomBaseDTO struct {
} }
type UomListDTO struct { type UomListDTO struct {
UomBaseDTO Id uint `json:"id"`
Name string `json:"name"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user"` CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
@@ -42,7 +43,8 @@ func ToUomListDTO(e entity.Uom) UomListDTO {
} }
return UomListDTO{ return UomListDTO{
UomBaseDTO: ToUomBaseDTO(e), Id: e.Id,
Name: e.Name,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt, UpdatedAt: e.UpdatedAt,
CreatedUser: createdUser, CreatedUser: createdUser,
@@ -67,6 +67,7 @@ type RecordingStockDTO struct {
} }
type RecordingEggDTO struct { type RecordingEggDTO struct {
Id uint `json:"id"`
ProductWarehouseId uint `json:"product_warehouse_id"` ProductWarehouseId uint `json:"product_warehouse_id"`
Qty int `json:"qty"` Qty int `json:"qty"`
ProductWarehouse *RecordingProductWarehouseDTO `json:"product_warehouse,omitempty"` ProductWarehouse *RecordingProductWarehouseDTO `json:"product_warehouse,omitempty"`
@@ -199,6 +200,7 @@ func ToRecordingEggDTOs(eggs []entity.RecordingEgg) []RecordingEggDTO {
result := make([]RecordingEggDTO, len(eggs)) result := make([]RecordingEggDTO, len(eggs))
for i, egg := range eggs { for i, egg := range eggs {
result[i] = RecordingEggDTO{ result[i] = RecordingEggDTO{
Id: egg.Id,
ProductWarehouseId: egg.ProductWarehouseId, ProductWarehouseId: egg.ProductWarehouseId,
Qty: egg.Qty, Qty: egg.Qty,
ProductWarehouse: toRecordingProductWarehouseDTO(&egg.ProductWarehouse), ProductWarehouse: toRecordingProductWarehouseDTO(&egg.ProductWarehouse),
+2 -2
View File
@@ -10,8 +10,8 @@ import (
) )
func ErrorHandler(c *fiber.Ctx, err error) error { func ErrorHandler(c *fiber.Ctx, err error) error {
if errorsMap := validation.CustomErrorMessages(err); len(errorsMap) > 0 { if message, errorsMap := validation.CustomErrorMessages(err); len(errorsMap) > 0 {
return response.Error(c, fiber.StatusBadRequest, "Bad Request", errorsMap) return response.Error(c, fiber.StatusBadRequest, message, nil)
} }
var fiberErr *fiber.Error var fiberErr *fiber.Error
+8 -6
View File
@@ -10,15 +10,16 @@ import (
// === DTO Structs === // === DTO Structs ===
type {{Pascal .Entity}}BaseDTO struct { type {{Pascal .Entity}}BaseDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} }
type {{Pascal .Entity}}ListDTO struct { type {{Pascal .Entity}}ListDTO struct {
{{Pascal .Entity}}BaseDTO Id uint `json:"id"`
Name string `json:"name"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user"` CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
type {{Pascal .Entity}}DetailDTO struct { type {{Pascal .Entity}}DetailDTO struct {
@@ -42,7 +43,8 @@ func To{{Pascal .Entity}}ListDTO(e entity.{{Pascal .Entity}}) {{Pascal .Entity}}
} }
return {{Pascal .Entity}}ListDTO{ return {{Pascal .Entity}}ListDTO{
{{Pascal .Entity}}BaseDTO: To{{Pascal .Entity}}BaseDTO(e), Id: e.Id,
Name: e.Name,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt, UpdatedAt: e.UpdatedAt,
CreatedUser: createdUser, CreatedUser: createdUser,