Fix[BE]: make projectflock kandang API and dto clean

This commit is contained in:
aguhh18
2025-11-06 21:25:15 +07:00
parent 663d5129bb
commit 954cccd564
5 changed files with 115 additions and 214 deletions
@@ -9,20 +9,29 @@ import (
flockBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
kandangBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
locationBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
userBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
)
// === DTO Structs (ordered) ===
type ProductWarehouseDTO struct {
Id uint `json:"id"`
Product *productDTO.ProductBaseDTO `json:"product,omitempty"`
Warehouse *warehouseDTO.WarehouseBaseDTO `json:"warehouse,omitempty"`
}
type ChickinBaseDTO struct {
Id uint `json:"id"`
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
ChickInDate time.Time `json:"chick_in_date"`
ProductWarehouseId uint `json:"product_warehouse_id"`
UsageQty float64 `json:"usage_qty"`
PendingUsageQty float64 `json:"pending_usage_qty"`
Notes string `json:"notes"`
Id uint `json:"id"`
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
ChickInDate time.Time `json:"chick_in_date"`
ProductWarehouseId uint `json:"product_warehouse_id"`
ProductWarehouse *ProductWarehouseDTO `json:"product_warehouse,omitempty"`
UsageQty float64 `json:"usage_qty"`
PendingUsageQty float64 `json:"pending_usage_qty"`
Notes string `json:"notes"`
}
type ProjectFlockDTO struct {
@@ -159,11 +168,18 @@ func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
// If relation is not loaded but ID is available, use the ID
projectFlockKandangId = e.ProjectFlockKandangId
}
var productWarehouse *ProductWarehouseDTO
if e.ProductWarehouse != nil && e.ProductWarehouse.Id != 0 {
productWarehouse = toProductWarehouseDTO(e.ProductWarehouse)
}
return ChickinBaseDTO{
Id: e.Id,
ProjectFlockKandangId: projectFlockKandangId,
ChickInDate: e.ChickInDate,
ProductWarehouseId: e.ProductWarehouseId,
ProductWarehouse: productWarehouse,
UsageQty: e.UsageQty,
PendingUsageQty: e.PendingUsageQty,
Notes: e.Notes,
@@ -242,3 +258,25 @@ func ToChickinDetailDTOs(e []entity.ProjectChickin) []ChickinDetailDTO {
}
return result
}
// === Helper Functions ===
// ToProductWarehouseDTO adalah exported helper untuk mapping ProductWarehouse (shared logic)
func ToProductWarehouseDTO(pw *entity.ProductWarehouse) *ProductWarehouseDTO {
if pw == nil {
return nil
}
product := productDTO.ToProductBaseDTO(pw.Product)
warehouse := warehouseDTO.ToWarehouseBaseDTO(pw.Warehouse)
return &ProductWarehouseDTO{
Id: pw.Id,
Product: &product,
Warehouse: &warehouse,
}
}
func toProductWarehouseDTO(pw *entity.ProductWarehouse) *ProductWarehouseDTO {
return ToProductWarehouseDTO(pw)
}
@@ -47,7 +47,7 @@ func (u *ProjectFlockKandangController) GetAll(c *fiber.Ctx) error {
data := make([]dto.ProjectFlockKandangListDTO, 0)
for _, result := range results {
data = append(data, dto.ToProjectFlockKandangListDTO(result.Entity))
data = append(data, dto.ToProjectFlockKandangListDTO(result))
}
return c.Status(fiber.StatusOK).
@@ -83,6 +83,6 @@ func (u *ProjectFlockKandangController) GetOne(c *fiber.Ctx) error {
Code: fiber.StatusOK,
Status: "success",
Message: "Get projectFlockKandang successfully",
Data: dto.ToProjectFlockKandangListDTOWithAvailableQty(*result, availableQtys),
Data: dto.ToProjectFlockKandangDetailDTOWithAvailableQty(*result, availableQtys),
})
}
@@ -19,7 +19,9 @@ import (
// === DTO Structs (ordered) ===
type ProjectFlockKandangBaseDTO struct {
Id uint `json:"id"`
Id uint `json:"id"`
KandangId uint `json:"kandang_id"`
ProjectFlockId uint `json:"project_flock_id"`
}
type ProjectFlockDTO struct {
@@ -55,23 +57,17 @@ type AvailableQtyDTO struct {
type ProjectFlockKandangListDTO struct {
ProjectFlockKandangBaseDTO
ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"`
Kandang *KandangDTO `json:"kandang,omitempty"`
Chickins []chickinDTO.ChickinBaseDTO `json:"chickins,omitempty"`
AvailableQtys []AvailableQtyDTO `json:"available_qtys,omitempty"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
CreatedAt time.Time `json:"created_at"`
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"`
ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"`
Kandang *KandangDTO `json:"kandang,omitempty"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
CreatedAt time.Time `json:"created_at"`
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"`
}
type ProjectFlockKandangDetailDTO struct {
ProjectFlockKandangListDTO
}
// Wrapper untuk GetAll dengan available quantities sudah included
type ProjectFlockKandangWithAvailableQtysDTO struct {
Entity entity.ProjectFlockKandang
AvailableQtys []map[string]interface{}
Chickins []chickinDTO.ChickinBaseDTO `json:"chickins,omitempty"`
AvailableQtys []AvailableQtyDTO `json:"available_qtys,omitempty"`
}
// === Mapper Functions (ordered) ===
@@ -101,23 +97,27 @@ func toProjectFlockDTO(pf *projectFlockDTO.ProjectFlockListDTO) *ProjectFlockDTO
}
}
func ToProjectFlockKandangListDTOWithAvailableQty(e entity.ProjectFlockKandang, availableQtysRaw []map[string]interface{}) ProjectFlockKandangListDTO {
func ToProjectFlockKandangDetailDTOWithAvailableQty(e entity.ProjectFlockKandang, availableQtyMap map[uint]float64) ProjectFlockKandangDetailDTO {
var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO
if e.ProjectFlock.Id != 0 {
mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock)
projectFlockSummary = &mapped
}
return ProjectFlockKandangListDTO{
listDTO := ProjectFlockKandangListDTO{
ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e),
ProjectFlock: toProjectFlockDTO(projectFlockSummary),
Kandang: toKandangDTO(e.Kandang),
Chickins: toChickinDTOs(e.Chickins),
AvailableQtys: toAvailableQtyDTOsFromRaw(availableQtysRaw),
CreatedAt: e.CreatedAt,
CreatedUser: toCreatedUserDTO(e.ProjectFlock),
Approval: toApprovalDTO(e),
}
return ProjectFlockKandangDetailDTO{
ProjectFlockKandangListDTO: listDTO,
Chickins: toChickinDTOs(e.Chickins),
AvailableQtys: toAvailableQtyDTOsFromMap(e.Chickins, availableQtyMap),
}
}
func toKandangDTO(kandang entity.Kandang) *KandangDTO {
@@ -151,8 +151,6 @@ func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKand
ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e),
ProjectFlock: toProjectFlockDTO(projectFlockSummary),
Kandang: toKandangDTO(e.Kandang),
Chickins: toChickinDTOs(e.Chickins),
AvailableQtys: toAvailableQtyDTOs(e.Chickins),
CreatedAt: e.CreatedAt,
CreatedUser: toCreatedUserDTO(e.ProjectFlock),
Approval: toApprovalDTO(e),
@@ -167,75 +165,6 @@ func ToProjectFlockKandangListDTOs(e []entity.ProjectFlockKandang) []ProjectFloc
return result
}
func ToProjectFlockKandangDetailDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDetailDTO {
return ProjectFlockKandangDetailDTO{
ProjectFlockKandangListDTO: ToProjectFlockKandangListDTO(e),
}
}
// === Helper Functions (ordered) ===
func toProductWarehouseDTO(pwData map[string]interface{}) *ProductWarehouseDTO {
if pwData == nil {
return nil
}
dto := &ProductWarehouseDTO{}
if id, ok := pwData["id"].(float64); ok {
dto.Id = uint(id)
} else if id, ok := pwData["id"].(uint); ok {
dto.Id = id
}
if pData, ok := pwData["product"].(map[string]interface{}); ok {
dto.Product = toProductDTO(pData)
}
if wData, ok := pwData["warehouse"].(map[string]interface{}); ok {
dto.Warehouse = toWarehouseDTO(wData)
}
return dto
}
func toProductDTO(pData map[string]interface{}) *productDTO.ProductBaseDTO {
if pData == nil {
return nil
}
product := &productDTO.ProductBaseDTO{}
if id, ok := pData["id"].(float64); ok {
product.Id = uint(id)
} else if id, ok := pData["id"].(uint); ok {
product.Id = id
}
if name, ok := pData["name"].(string); ok {
product.Name = name
}
return product
}
func toWarehouseDTO(wData map[string]interface{}) *warehouseDTO.WarehouseBaseDTO {
if wData == nil {
return nil
}
warehouse := &warehouseDTO.WarehouseBaseDTO{}
if id, ok := wData["id"].(float64); ok {
warehouse.Id = uint(id)
} else if id, ok := wData["id"].(uint); ok {
warehouse.Id = id
}
if name, ok := wData["name"].(string); ok {
warehouse.Name = name
}
if wType, ok := wData["type"].(string); ok {
warehouse.Type = wType
}
return warehouse
}
func toCreatedUserDTO(pf entity.ProjectFlock) *userDTO.UserBaseDTO {
if pf.CreatedUser.Id != 0 {
mapped := userDTO.ToUserBaseDTO(pf.CreatedUser)
@@ -261,78 +190,49 @@ func toChickinDTOs(chickins []entity.ProjectChickin) []chickinDTO.ChickinBaseDTO
return result
}
func toAvailableQtyDTOs(chickins []entity.ProjectChickin) []AvailableQtyDTO {
if len(chickins) == 0 {
return nil
}
availableQtyMap := make(map[uint]AvailableQtyDTO)
for _, ch := range chickins {
if ch.ProductWarehouse == nil || ch.ProductWarehouse.Quantity <= 0 {
continue
}
if _, exists := availableQtyMap[ch.ProductWarehouseId]; exists {
continue
}
pwDTO := &ProductWarehouseDTO{
Id: ch.ProductWarehouse.Id,
}
if ch.ProductWarehouse.Product.Id != 0 {
pwDTO.Product = &productDTO.ProductBaseDTO{
Id: ch.ProductWarehouse.Product.Id,
Name: ch.ProductWarehouse.Product.Name,
}
}
if ch.ProductWarehouse.Warehouse.Id != 0 {
pwDTO.Warehouse = &warehouseDTO.WarehouseBaseDTO{
Id: ch.ProductWarehouse.Warehouse.Id,
Name: ch.ProductWarehouse.Warehouse.Name,
Type: ch.ProductWarehouse.Warehouse.Type,
}
}
availableQtyMap[ch.ProductWarehouseId] = AvailableQtyDTO{
ProductWarehouse: pwDTO,
}
}
func toAvailableQtyDTOsFromMap(chickins []entity.ProjectChickin, availableQtyMap map[uint]float64) []AvailableQtyDTO {
if len(availableQtyMap) == 0 {
return nil
}
pwMap := make(map[uint]*entity.ProductWarehouse)
for _, chickin := range chickins {
if chickin.ProductWarehouse != nil && chickin.ProductWarehouse.Id != 0 {
pwMap[chickin.ProductWarehouseId] = chickin.ProductWarehouse
}
}
result := make([]AvailableQtyDTO, 0, len(availableQtyMap))
for _, v := range availableQtyMap {
result = append(result, v)
}
return result
}
func toAvailableQtyDTOsFromRaw(availableQtysRaw []map[string]interface{}) []AvailableQtyDTO {
if len(availableQtysRaw) == 0 {
return nil
}
result := make([]AvailableQtyDTO, len(availableQtysRaw))
for i, v := range availableQtysRaw {
pwData, ok := v["product_warehouse"].(map[string]interface{})
if !ok {
for pwId, availableQty := range availableQtyMap {
pw, exists := pwMap[pwId]
if !exists || pw == nil {
continue
}
pwDTO := toProductWarehouseDTO(pwData)
availableQty := 0.0
if qty, ok := v["available_qty"].(float64); ok {
availableQty = qty
}
pwDTO := ToProductWarehouseDTO(pw)
result[i] = AvailableQtyDTO{
result = append(result, AvailableQtyDTO{
AvailableQty: availableQty,
ProductWarehouse: pwDTO,
}
})
}
return result
}
func ToProductWarehouseDTO(pw *entity.ProductWarehouse) *ProductWarehouseDTO {
if pw == nil {
return nil
}
chickinPwDTO := chickinDTO.ToProductWarehouseDTO(pw)
if chickinPwDTO == nil {
return nil
}
return &ProductWarehouseDTO{
Id: chickinPwDTO.Id,
Product: chickinPwDTO.Product,
Warehouse: chickinPwDTO.Warehouse,
}
}
@@ -7,7 +7,6 @@ import (
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
dto "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/dto"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
@@ -19,10 +18,12 @@ import (
)
type ProjectFlockKandangService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.ProjectFlockKandangWithAvailableQtysDTO, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, []map[string]interface{}, error)
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, error)
}
// Note: map[uint]float64 adalah mapping dari ProductWarehouse ID ke calculated available quantity
type projectFlockKandangService struct {
Log *logrus.Logger
Validate *validator.Validate
@@ -45,18 +46,11 @@ func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository
}
}
func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.ProjectFlockKandangWithAvailableQtysDTO, int64, error) {
func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error) {
if err := s.Validate.Struct(params); err != nil {
return nil, 0, err
}
if params.Page <= 0 {
params.Page = 1
}
if params.Limit <= 0 {
params.Limit = 10
}
offset := (params.Page - 1) * params.Limit
projectFlockKandangs, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params)
@@ -85,24 +79,11 @@ func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Quer
}
}
}
results := make([]dto.ProjectFlockKandangWithAvailableQtysDTO, 0)
for i := range projectFlockKandangs {
availableQtys, err := s.getAvailableQuantities(c, &projectFlockKandangs[i])
if err != nil {
s.Log.Warnf("Failed to fetch available quantities for kandang %d: %+v", projectFlockKandangs[i].Kandang.Id, err)
availableQtys = nil
}
results = append(results, dto.ProjectFlockKandangWithAvailableQtysDTO{
Entity: projectFlockKandangs[i],
AvailableQtys: availableQtys,
})
}
return results, total, nil
return projectFlockKandangs, total, nil
}
func (s projectFlockKandangService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, []map[string]interface{}, error) {
func (s projectFlockKandangService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, error) {
projectFlockKandang, err := s.Repository.GetByID(c.Context(), id)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
@@ -122,16 +103,16 @@ func (s projectFlockKandangService) GetOne(c *fiber.Ctx, id uint) (*entity.Proje
}
}
availableQtys, err := s.getAvailableQuantities(c, projectFlockKandang)
availableQtyMap, err := s.getAvailableQuantities(c, projectFlockKandang)
if err != nil {
s.Log.Errorf("Failed to fetch available quantities for kandang %d: %+v", projectFlockKandang.Kandang.Id, err)
availableQtys = nil
availableQtyMap = nil
}
return projectFlockKandang, availableQtys, nil
return projectFlockKandang, availableQtyMap, nil
}
func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang) ([]map[string]interface{}, error) {
func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang) (map[uint]float64, error) {
if projectFlockKandang.Kandang.Id == 0 || s.WarehouseRepo == nil || s.ProductWarehouseRepo == nil {
return nil, nil
}
@@ -155,38 +136,16 @@ func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, project
return nil, nil
}
var result []map[string]interface{}
result := make(map[uint]float64)
for _, pw := range products {
availableQty, err := s.calculateAvailableQuantityForProductWarehouse(c, projectFlockKandang, &pw)
if err != nil {
s.Log.Warnf("Failed to calculate available quantity for product warehouse %d: %v", pw.Id, err)
}
if availableQty <= 0 {
continue
if availableQty > 0 {
result[pw.Id] = availableQty
}
productData := map[string]interface{}{
"id": pw.Product.Id,
"name": pw.Product.Name,
}
warehouseData := map[string]interface{}{
"id": pw.Warehouse.Id,
"name": pw.Warehouse.Name,
"type": pw.Warehouse.Type,
}
productWarehouseData := map[string]interface{}{
"id": pw.Id,
"product": productData,
"warehouse": warehouseData,
}
result = append(result, map[string]interface{}{
"available_qty": availableQty,
"product_warehouse": productWarehouseData,
})
}
return result, nil
@@ -189,6 +189,8 @@ func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint
Preload("Chickins").
Preload("Chickins.CreatedUser").
Preload("Chickins.ProductWarehouse").
Preload("Chickins.ProductWarehouse.Product").
Preload("Chickins.ProductWarehouse.Warehouse").
First(record, id).Error; err != nil {
return nil, err
}
@@ -210,6 +212,8 @@ func (r *projectFlockKandangRepositoryImpl) GetByProjectFlockAndKandang(ctx cont
Preload("Chickins").
Preload("Chickins.CreatedUser").
Preload("Chickins.ProductWarehouse").
Preload("Chickins.ProductWarehouse.Product").
Preload("Chickins.ProductWarehouse.Warehouse").
First(record).Error; err != nil {
return nil, err
}