mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
fix: resolve dashboard OpenAPI integration issues
- FCRs & Transfer to Laying: add ExampleResponse field to routeMeta and inject example payloads into OpenAPI 200 responses for list and detail endpoints so dashboard consumers have concrete response shapes to work with - Chick In: enable GET /api/production/chickins/ list endpoint (was commented out); add P_ChickinsGetAll permission constant and wire it into the route; add OpenAPI spec entry with query params and example - Recording GET all: fix N+1 query bottleneck (2-3s response time) by pre-fetching approved transfer maps per PFK ID in two batch queries before the per-recording loop; add evaluatePopulationMutationStateFromCaches that uses the pre-fetched maps and caches hasAnyRecordingOnTransferTargets results by transfer ID — reducing per-page query count from ~20-40 to ~10-12 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -207,6 +207,7 @@ const (
|
|||||||
P_Finances_Transaction_DeleteOne = "lti.finance.transactions.delete"
|
P_Finances_Transaction_DeleteOne = "lti.finance.transactions.delete"
|
||||||
)
|
)
|
||||||
const (
|
const (
|
||||||
|
P_ChickinsGetAll = "lti.production.chickins.list"
|
||||||
P_ChickinsCreateOne = "lti.production.chickins.create"
|
P_ChickinsCreateOne = "lti.production.chickins.create"
|
||||||
P_ChickinsGetOne = "lti.production.chickins.detail"
|
P_ChickinsGetOne = "lti.production.chickins.detail"
|
||||||
P_ChickinsApproval = "lti.production.chickins.approve"
|
P_ChickinsApproval = "lti.production.chickins.approve"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/dto"
|
||||||
@@ -21,32 +22,32 @@ func NewChickinController(chickinService service.ChickinService) *ChickinControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (u *ChickinController) GetAll(c *fiber.Ctx) error {
|
func (u *ChickinController) GetAll(c *fiber.Ctx) error {
|
||||||
// query := &validation.Query{
|
query := &validation.Query{
|
||||||
// Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
// Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
// ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)),
|
ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)),
|
||||||
// }
|
}
|
||||||
|
|
||||||
// result, totalResults, err := u.ChickinService.GetAll(c, query)
|
result, totalResults, err := u.ChickinService.GetAll(c, query)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return err
|
return err
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
// JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{
|
JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{
|
||||||
// Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
// Status: "success",
|
Status: "success",
|
||||||
// Message: "Get all chickins successfully",
|
Message: "Get all chickins successfully",
|
||||||
// Meta: response.Meta{
|
Meta: response.Meta{
|
||||||
// Page: query.Page,
|
Page: query.Page,
|
||||||
// Limit: query.Limit,
|
Limit: query.Limit,
|
||||||
// TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
// TotalResults: totalResults,
|
TotalResults: totalResults,
|
||||||
// },
|
},
|
||||||
// Data: dto.ToChickinListDTOs(result),
|
Data: dto.ToChickinListDTOs(result),
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
|
|
||||||
// func (u *ChickinController) GetOne(c *fiber.Ctx) error {
|
// func (u *ChickinController) GetOne(c *fiber.Ctx) error {
|
||||||
// param := c.Params("id")
|
// param := c.Params("id")
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ func ChickinRoutes(v1 fiber.Router, u user.UserService, s chickin.ChickinService
|
|||||||
route := v1.Group("/chickins")
|
route := v1.Group("/chickins")
|
||||||
route.Use(m.Auth(u))
|
route.Use(m.Auth(u))
|
||||||
|
|
||||||
// route.Get("/", ctrl.GetAll)
|
route.Get("/", m.RequirePermissions(m.P_ChickinsGetAll), ctrl.GetAll)
|
||||||
route.Post("/",m.RequirePermissions(m.P_ChickinsCreateOne), ctrl.CreateOne)
|
route.Post("/", m.RequirePermissions(m.P_ChickinsCreateOne), ctrl.CreateOne)
|
||||||
route.Get("/:id",m.RequirePermissions(m.P_ChickinsGetOne), ctrl.GetOne)
|
route.Get("/:id",m.RequirePermissions(m.P_ChickinsGetOne), ctrl.GetOne)
|
||||||
// route.Patch("/:id", ctrl.UpdateOne)
|
// route.Patch("/:id", ctrl.UpdateOne)
|
||||||
route.Delete("/:id", ctrl.DeleteOne)
|
route.Delete("/:id", ctrl.DeleteOne)
|
||||||
|
|||||||
@@ -173,6 +173,37 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-fetch transfer maps by category to avoid N+1 per-recording queries.
|
||||||
|
growingPFKIDs := make([]uint, 0, len(pfkIDs))
|
||||||
|
layingPFKIDs := make([]uint, 0, len(pfkIDs))
|
||||||
|
seenCat := make(map[uint]bool, len(pfkIDs))
|
||||||
|
for i := range recordings {
|
||||||
|
pfkID := recordings[i].ProjectFlockKandangId
|
||||||
|
if pfkID == 0 || seenCat[pfkID] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenCat[pfkID] = true
|
||||||
|
cat := ""
|
||||||
|
if recordings[i].ProjectFlockKandang != nil && recordings[i].ProjectFlockKandang.ProjectFlock.Id != 0 {
|
||||||
|
cat = strings.ToUpper(strings.TrimSpace(recordings[i].ProjectFlockKandang.ProjectFlock.Category))
|
||||||
|
}
|
||||||
|
switch cat {
|
||||||
|
case string(utils.ProjectFlockCategoryGrowing):
|
||||||
|
growingPFKIDs = append(growingPFKIDs, pfkID)
|
||||||
|
case string(utils.ProjectFlockCategoryLaying):
|
||||||
|
layingPFKIDs = append(layingPFKIDs, pfkID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceTransferByPFK, err := s.TransferLayingRepo.GetLatestApprovedBySourceKandangs(c.Context(), growingPFKIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
targetTransferByPFK, err := s.TransferLayingRepo.GetLatestApprovedByTargetKandangs(c.Context(), layingPFKIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
hasTargetRecordingCache := make(map[uint]bool)
|
||||||
|
|
||||||
cutOverChickinAvailability := make(map[uint]bool)
|
cutOverChickinAvailability := make(map[uint]bool)
|
||||||
for i := range recordings {
|
for i := range recordings {
|
||||||
if recordings[i].ProjectFlockKandangId != 0 && !recordings[i].RecordDatetime.IsZero() {
|
if recordings[i].ProjectFlockKandangId != 0 && !recordings[i].RecordDatetime.IsZero() {
|
||||||
@@ -192,7 +223,7 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
rate := recordingutil.ComputeDepletionRate(prev, current, totalChick)
|
rate := recordingutil.ComputeDepletionRate(prev, current, totalChick)
|
||||||
recordings[i].DepletionRate = &rate
|
recordings[i].DepletionRate = &rate
|
||||||
|
|
||||||
populationCanChange, transferExecuted, isTransition, isLaying, _, _, stateErr := s.evaluatePopulationMutationState(c.Context(), &recordings[i])
|
populationCanChange, transferExecuted, isTransition, isLaying, _, _, stateErr := s.evaluatePopulationMutationStateFromCaches(c.Context(), &recordings[i], sourceTransferByPFK, targetTransferByPFK, hasTargetRecordingCache)
|
||||||
if stateErr != nil {
|
if stateErr != nil {
|
||||||
return nil, 0, stateErr
|
return nil, 0, stateErr
|
||||||
}
|
}
|
||||||
@@ -1308,6 +1339,82 @@ func (s *recordingService) evaluatePopulationMutationState(ctx context.Context,
|
|||||||
return populationCanChange, transferExecuted, isTransition, isLaying, transfer, transferDate, nil
|
return populationCanChange, transferExecuted, isTransition, isLaying, transfer, transferDate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// evaluatePopulationMutationStateFromCaches is identical to evaluatePopulationMutationState
|
||||||
|
// but uses pre-fetched transfer maps to avoid N+1 queries in list endpoints.
|
||||||
|
func (s *recordingService) evaluatePopulationMutationStateFromCaches(
|
||||||
|
ctx context.Context,
|
||||||
|
recording *entity.Recording,
|
||||||
|
sourceTransferByPFK map[uint]*entity.LayingTransfer,
|
||||||
|
targetTransferByPFK map[uint]*entity.LayingTransfer,
|
||||||
|
hasTargetRecordingCache map[uint]bool,
|
||||||
|
) (bool, bool, bool, bool, *entity.LayingTransfer, time.Time, error) {
|
||||||
|
if recording == nil || recording.ProjectFlockKandangId == 0 || s.TransferLayingRepo == nil {
|
||||||
|
return true, false, false, false, nil, time.Time{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
category, err := s.resolveRecordingCategory(ctx, recording)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to resolve recording category for population mutation check (recording=%d): %+v", recording.Id, err)
|
||||||
|
return true, false, false, false, nil, time.Time{}, fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi perubahan populasi recording")
|
||||||
|
}
|
||||||
|
|
||||||
|
var transfer *entity.LayingTransfer
|
||||||
|
switch category {
|
||||||
|
case strings.ToUpper(string(utils.ProjectFlockCategoryGrowing)):
|
||||||
|
transfer = sourceTransferByPFK[recording.ProjectFlockKandangId]
|
||||||
|
case strings.ToUpper(string(utils.ProjectFlockCategoryLaying)):
|
||||||
|
transfer = targetTransferByPFK[recording.ProjectFlockKandangId]
|
||||||
|
default:
|
||||||
|
return true, false, false, false, nil, time.Time{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if transfer == nil {
|
||||||
|
return true, false, false, false, nil, time.Time{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
transferDate := transferPhysicalMoveDate(transfer)
|
||||||
|
if transferDate.IsZero() {
|
||||||
|
return true, false, false, false, transfer, transferDate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
transferExecuted := transfer.ExecutedAt != nil && !transfer.ExecutedAt.IsZero()
|
||||||
|
recordDate := normalizeDateOnlyUTC(recording.RecordDatetime)
|
||||||
|
_, economicCutoffDate := transferRecordingWindow(transfer)
|
||||||
|
isTransition := !recordDate.Before(transferDate) && recordDate.Before(economicCutoffDate)
|
||||||
|
isLaying := !recordDate.Before(economicCutoffDate)
|
||||||
|
|
||||||
|
populationCanChange := true
|
||||||
|
if category == strings.ToUpper(string(utils.ProjectFlockCategoryGrowing)) {
|
||||||
|
populationCanChange = !(transferExecuted && !recordDate.Before(transferDate))
|
||||||
|
|
||||||
|
if transferExecuted && !recordDate.Before(transferDate) {
|
||||||
|
var hasTargetLayingRecording bool
|
||||||
|
if cached, ok := hasTargetRecordingCache[transfer.Id]; ok {
|
||||||
|
hasTargetLayingRecording = cached
|
||||||
|
} else {
|
||||||
|
hasTargetLayingRecording, err = s.hasAnyRecordingOnTransferTargets(ctx, transfer)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to resolve target laying recording state for transfer %d: %+v", transfer.Id, err)
|
||||||
|
return true, false, false, false, nil, time.Time{}, fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi status transisi recording")
|
||||||
|
}
|
||||||
|
hasTargetRecordingCache[transfer.Id] = hasTargetLayingRecording
|
||||||
|
}
|
||||||
|
if hasTargetLayingRecording {
|
||||||
|
isTransition = false
|
||||||
|
isLaying = true
|
||||||
|
} else {
|
||||||
|
today := normalizeDateOnlyUTC(time.Now().UTC())
|
||||||
|
if !today.Before(economicCutoffDate) {
|
||||||
|
isTransition = true
|
||||||
|
isLaying = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return populationCanChange, transferExecuted, isTransition, isLaying, transfer, transferDate, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *recordingService) hasAnyRecordingOnTransferTargets(ctx context.Context, transfer *entity.LayingTransfer) (bool, error) {
|
func (s *recordingService) hasAnyRecordingOnTransferTargets(ctx context.Context, transfer *entity.LayingTransfer) (bool, error) {
|
||||||
if transfer == nil || transfer.Id == 0 {
|
if transfer == nil || transfer.Id == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|||||||
+120
@@ -17,6 +17,8 @@ type TransferLayingRepository interface {
|
|||||||
IdExists(ctx context.Context, id uint) (bool, error)
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
GetLatestApprovedBySourceKandang(ctx context.Context, sourceProjectFlockKandangID uint) (*entity.LayingTransfer, error)
|
GetLatestApprovedBySourceKandang(ctx context.Context, sourceProjectFlockKandangID uint) (*entity.LayingTransfer, error)
|
||||||
GetLatestApprovedByTargetKandang(ctx context.Context, targetProjectFlockKandangID uint) (*entity.LayingTransfer, error)
|
GetLatestApprovedByTargetKandang(ctx context.Context, targetProjectFlockKandangID uint) (*entity.LayingTransfer, error)
|
||||||
|
GetLatestApprovedBySourceKandangs(ctx context.Context, pfkIDs []uint) (map[uint]*entity.LayingTransfer, error)
|
||||||
|
GetLatestApprovedByTargetKandangs(ctx context.Context, pfkIDs []uint) (map[uint]*entity.LayingTransfer, error)
|
||||||
|
|
||||||
// Tambah method baru untuk query dengan filter lengkap
|
// Tambah method baru untuk query dengan filter lengkap
|
||||||
GetAllWithFilters(ctx context.Context, offset int, limit int, params *GetAllFilterParams) ([]entity.LayingTransfer, int64, error)
|
GetAllWithFilters(ctx context.Context, offset int, limit int, params *GetAllFilterParams) ([]entity.LayingTransfer, int64, error)
|
||||||
@@ -242,3 +244,121 @@ func (r *TransferLayingRepositoryImpl) GetLatestApprovedByTargetKandang(ctx cont
|
|||||||
}
|
}
|
||||||
return &transfer, nil
|
return &transfer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pfkTransferIDRow struct {
|
||||||
|
SourcePFKID uint `gorm:"column:source_pfk_id"`
|
||||||
|
TransferID uint `gorm:"column:transfer_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TransferLayingRepositoryImpl) GetLatestApprovedBySourceKandangs(ctx context.Context, pfkIDs []uint) (map[uint]*entity.LayingTransfer, error) {
|
||||||
|
result := make(map[uint]*entity.LayingTransfer)
|
||||||
|
if len(pfkIDs) == 0 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows []pfkTransferIDRow
|
||||||
|
err := r.db.WithContext(ctx).Raw(`
|
||||||
|
SELECT DISTINCT ON (source_pfk_id) source_pfk_id, transfer_id
|
||||||
|
FROM (
|
||||||
|
SELECT id AS transfer_id, source_project_flock_kandang_id AS source_pfk_id
|
||||||
|
FROM laying_transfers
|
||||||
|
WHERE source_project_flock_kandang_id IN ?
|
||||||
|
AND deleted_at IS NULL
|
||||||
|
AND (
|
||||||
|
SELECT a.action FROM approvals a
|
||||||
|
WHERE a.approvable_type = ? AND a.approvable_id = id
|
||||||
|
ORDER BY a.id DESC LIMIT 1
|
||||||
|
) = ?
|
||||||
|
UNION ALL
|
||||||
|
SELECT lts.laying_transfer_id AS transfer_id, lts.source_project_flock_kandang_id AS source_pfk_id
|
||||||
|
FROM laying_transfer_sources lts
|
||||||
|
JOIN laying_transfers t ON t.id = lts.laying_transfer_id AND t.deleted_at IS NULL
|
||||||
|
WHERE lts.source_project_flock_kandang_id IN ?
|
||||||
|
AND lts.deleted_at IS NULL
|
||||||
|
AND (
|
||||||
|
SELECT a.action FROM approvals a
|
||||||
|
WHERE a.approvable_type = ? AND a.approvable_id = t.id
|
||||||
|
ORDER BY a.id DESC LIMIT 1
|
||||||
|
) = ?
|
||||||
|
) combined
|
||||||
|
ORDER BY source_pfk_id, transfer_id DESC
|
||||||
|
`,
|
||||||
|
pfkIDs, string(utils.ApprovalWorkflowTransferToLaying), string(entity.ApprovalActionApproved),
|
||||||
|
pfkIDs, string(utils.ApprovalWorkflowTransferToLaying), string(entity.ApprovalActionApproved),
|
||||||
|
).Scan(&rows).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
transferIDs := make([]uint, 0, len(rows))
|
||||||
|
pfkByTransfer := make(map[uint]uint, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
transferIDs = append(transferIDs, row.TransferID)
|
||||||
|
pfkByTransfer[row.TransferID] = row.SourcePFKID
|
||||||
|
}
|
||||||
|
|
||||||
|
var transfers []entity.LayingTransfer
|
||||||
|
if err := r.db.WithContext(ctx).Where("id IN ? AND deleted_at IS NULL", transferIDs).Find(&transfers).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range transfers {
|
||||||
|
if pfkID := pfkByTransfer[transfers[i].Id]; pfkID != 0 {
|
||||||
|
result[pfkID] = &transfers[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TransferLayingRepositoryImpl) GetLatestApprovedByTargetKandangs(ctx context.Context, pfkIDs []uint) (map[uint]*entity.LayingTransfer, error) {
|
||||||
|
result := make(map[uint]*entity.LayingTransfer)
|
||||||
|
if len(pfkIDs) == 0 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows []pfkTransferIDRow
|
||||||
|
err := r.db.WithContext(ctx).Raw(`
|
||||||
|
SELECT DISTINCT ON (source_pfk_id) source_pfk_id, transfer_id
|
||||||
|
FROM (
|
||||||
|
SELECT ltt.laying_transfer_id AS transfer_id, ltt.target_project_flock_kandang_id AS source_pfk_id
|
||||||
|
FROM laying_transfer_targets ltt
|
||||||
|
JOIN laying_transfers t ON t.id = ltt.laying_transfer_id AND t.deleted_at IS NULL
|
||||||
|
WHERE ltt.target_project_flock_kandang_id IN ?
|
||||||
|
AND ltt.deleted_at IS NULL
|
||||||
|
AND (
|
||||||
|
SELECT a.action FROM approvals a
|
||||||
|
WHERE a.approvable_type = ? AND a.approvable_id = t.id
|
||||||
|
ORDER BY a.id DESC LIMIT 1
|
||||||
|
) = ?
|
||||||
|
) combined
|
||||||
|
ORDER BY source_pfk_id, transfer_id DESC
|
||||||
|
`,
|
||||||
|
pfkIDs, string(utils.ApprovalWorkflowTransferToLaying), string(entity.ApprovalActionApproved),
|
||||||
|
).Scan(&rows).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
transferIDs := make([]uint, 0, len(rows))
|
||||||
|
pfkByTransfer := make(map[uint]uint, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
transferIDs = append(transferIDs, row.TransferID)
|
||||||
|
pfkByTransfer[row.TransferID] = row.SourcePFKID
|
||||||
|
}
|
||||||
|
|
||||||
|
var transfers []entity.LayingTransfer
|
||||||
|
if err := r.db.WithContext(ctx).Where("id IN ? AND deleted_at IS NULL", transferIDs).Find(&transfers).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range transfers {
|
||||||
|
if pfkID := pfkByTransfer[transfers[i].Id]; pfkID != 0 {
|
||||||
|
result[pfkID] = &transfers[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
+106
-3
@@ -51,6 +51,7 @@ type routeMeta struct {
|
|||||||
Security securityMode
|
Security securityMode
|
||||||
ListStyle bool
|
ListStyle bool
|
||||||
QueryParams []parameterMeta
|
QueryParams []parameterMeta
|
||||||
|
ExampleResponse any
|
||||||
Exclude bool
|
Exclude bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +188,13 @@ func buildOpenAPIDocument(routes []normalizedRoute) map[string]any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openAPIPath := toOpenAPIPath(route.Path)
|
openAPIPath := toOpenAPIPath(route.Path)
|
||||||
|
responseContent := map[string]any{
|
||||||
|
"schema": successSchema(meta),
|
||||||
|
}
|
||||||
|
if meta.ExampleResponse != nil {
|
||||||
|
responseContent["example"] = meta.ExampleResponse
|
||||||
|
}
|
||||||
|
|
||||||
operation := map[string]any{
|
operation := map[string]any{
|
||||||
"summary": meta.Summary,
|
"summary": meta.Summary,
|
||||||
"description": meta.Description,
|
"description": meta.Description,
|
||||||
@@ -195,9 +203,7 @@ func buildOpenAPIDocument(routes []normalizedRoute) map[string]any {
|
|||||||
"200": map[string]any{
|
"200": map[string]any{
|
||||||
"description": "Successful response",
|
"description": "Successful response",
|
||||||
"content": map[string]any{
|
"content": map[string]any{
|
||||||
"application/json": map[string]any{
|
"application/json": responseContent,
|
||||||
"schema": successSchema(meta),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"401": map[string]any{
|
"401": map[string]any{
|
||||||
@@ -777,6 +783,31 @@ func describeRoute(route normalizedRoute) routeMeta {
|
|||||||
{Name: "limit", In: "query", Description: "Page size.", Example: 10},
|
{Name: "limit", In: "query", Description: "Page size.", Example: 10},
|
||||||
{Name: "search", In: "query", Description: "Search keyword.", Example: "fcr"},
|
{Name: "search", In: "query", Description: "Search keyword.", Example: "fcr"},
|
||||||
}
|
}
|
||||||
|
meta.ExampleResponse = map[string]any{
|
||||||
|
"code": 200, "status": "success", "message": "Get all fcrs successfully",
|
||||||
|
"meta": map[string]any{"page": 1, "limit": 10, "total_pages": 1, "total_results": 1},
|
||||||
|
"data": []map[string]any{
|
||||||
|
{
|
||||||
|
"id": 1, "name": "FCR Broiler Standard",
|
||||||
|
"created_user": map[string]any{"id": 1, "name": "Admin"},
|
||||||
|
"created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case "/api/master-data/fcrs/:id":
|
||||||
|
meta.ExampleResponse = map[string]any{
|
||||||
|
"code": 200, "status": "success", "message": "Get fcr successfully",
|
||||||
|
"data": map[string]any{
|
||||||
|
"id": 1, "name": "FCR Broiler Standard",
|
||||||
|
"created_user": map[string]any{"id": 1, "name": "Admin"},
|
||||||
|
"created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z",
|
||||||
|
"fcr_standards": []map[string]any{
|
||||||
|
{"id": 1, "weight": 0.5, "fcr_number": 1.2, "mortality": 0.5},
|
||||||
|
{"id": 2, "weight": 1.0, "fcr_number": 1.35, "mortality": 0.3},
|
||||||
|
{"id": 3, "weight": 1.5, "fcr_number": 1.5, "mortality": 0.25},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
case "/api/master-data/flocks":
|
case "/api/master-data/flocks":
|
||||||
meta.QueryParams = []parameterMeta{
|
meta.QueryParams = []parameterMeta{
|
||||||
{Name: "page", In: "query", Description: "Page number.", Example: 1},
|
{Name: "page", In: "query", Description: "Page number.", Example: 1},
|
||||||
@@ -926,6 +957,31 @@ func describeRoute(route normalizedRoute) routeMeta {
|
|||||||
{Name: "project_flock_kandang_id", In: "query", Description: "Project flock kandang id.", Required: true, Example: 1, PostmanValue: "{{project_flock_kandang_id}}"},
|
{Name: "project_flock_kandang_id", In: "query", Description: "Project flock kandang id.", Required: true, Example: 1, PostmanValue: "{{project_flock_kandang_id}}"},
|
||||||
{Name: "record_date", In: "query", Description: "Recording date (YYYY-MM-DD).", Required: true, Example: "2026-01-01"},
|
{Name: "record_date", In: "query", Description: "Recording date (YYYY-MM-DD).", Required: true, Example: "2026-01-01"},
|
||||||
}
|
}
|
||||||
|
case "/api/production/chickins":
|
||||||
|
meta.QueryParams = []parameterMeta{
|
||||||
|
{Name: "page", In: "query", Description: "Page number.", Example: 1},
|
||||||
|
{Name: "limit", In: "query", Description: "Page size.", Example: 10},
|
||||||
|
{Name: "project_flock_kandang_id", In: "query", Description: "Project flock kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"},
|
||||||
|
}
|
||||||
|
meta.ExampleResponse = map[string]any{
|
||||||
|
"code": 200, "status": "success", "message": "Get all chickins successfully",
|
||||||
|
"meta": map[string]any{"page": 1, "limit": 10, "total_pages": 1, "total_results": 1},
|
||||||
|
"data": []map[string]any{
|
||||||
|
{
|
||||||
|
"id": 1, "project_flock_kandang_id": 1,
|
||||||
|
"chick_in_date": "2026-01-01T00:00:00Z",
|
||||||
|
"product_warehouse_id": 1,
|
||||||
|
"product_warehouse": map[string]any{
|
||||||
|
"id": 1,
|
||||||
|
"product": map[string]any{"id": 1, "name": "DOC Broiler"},
|
||||||
|
"warehouse": map[string]any{"id": 1, "name": "Gudang DOC"},
|
||||||
|
},
|
||||||
|
"usage_qty": 10000.0, "pending_usage_qty": 0.0, "notes": "",
|
||||||
|
"created_user": map[string]any{"id": 1, "name": "Admin"},
|
||||||
|
"created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
case "/api/production/transfer_layings":
|
case "/api/production/transfer_layings":
|
||||||
meta.QueryParams = []parameterMeta{
|
meta.QueryParams = []parameterMeta{
|
||||||
{Name: "page", In: "query", Description: "Page number.", Example: 1},
|
{Name: "page", In: "query", Description: "Page number.", Example: 1},
|
||||||
@@ -937,6 +993,53 @@ func describeRoute(route normalizedRoute) routeMeta {
|
|||||||
{Name: "flock_destination", In: "query", Description: "Comma separated destination flock ids.", Example: "3,4"},
|
{Name: "flock_destination", In: "query", Description: "Comma separated destination flock ids.", Example: "3,4"},
|
||||||
{Name: "status", In: "query", Description: "Comma separated status values.", Example: "DRAFT,APPROVED"},
|
{Name: "status", In: "query", Description: "Comma separated status values.", Example: "DRAFT,APPROVED"},
|
||||||
}
|
}
|
||||||
|
meta.ExampleResponse = map[string]any{
|
||||||
|
"code": 200, "status": "success", "message": "Get all transferLayings successfully",
|
||||||
|
"meta": map[string]any{"page": 1, "limit": 10, "total_pages": 1, "total_results": 1},
|
||||||
|
"data": []map[string]any{
|
||||||
|
{
|
||||||
|
"id": 1, "transfer_number": "TL-00001",
|
||||||
|
"transfer_date": "2026-01-15T00:00:00Z",
|
||||||
|
"economic_cutoff_date": "2026-01-20T00:00:00Z",
|
||||||
|
"effective_move_date": "2026-01-18T00:00:00Z",
|
||||||
|
"executed_at": nil, "notes": "",
|
||||||
|
"from_project_flock": map[string]any{"id": 1, "flock_name": "Flock A Period 1"},
|
||||||
|
"to_project_flock": map[string]any{"id": 2, "flock_name": "Flock B Period 1"},
|
||||||
|
"created_by": 1,
|
||||||
|
"created_user": map[string]any{"id": 1, "name": "Admin"},
|
||||||
|
"created_at": "2026-01-15T00:00:00Z",
|
||||||
|
"approval": map[string]any{"step_number": 1, "step_name": "Pengajuan", "action": nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case "/api/production/transfer_layings/:id":
|
||||||
|
meta.ExampleResponse = map[string]any{
|
||||||
|
"code": 200, "status": "success", "message": "Get transferLaying successfully",
|
||||||
|
"data": map[string]any{
|
||||||
|
"id": 1, "transfer_number": "TL-00001",
|
||||||
|
"transfer_date": "2026-01-15T00:00:00Z",
|
||||||
|
"economic_cutoff_date": "2026-01-20T00:00:00Z",
|
||||||
|
"effective_move_date": "2026-01-18T00:00:00Z",
|
||||||
|
"executed_at": nil, "notes": "",
|
||||||
|
"from_project_flock": map[string]any{"id": 1, "flock_name": "Flock A Period 1"},
|
||||||
|
"to_project_flock": map[string]any{"id": 2, "flock_name": "Flock B Period 1"},
|
||||||
|
"created_by": 1, "created_user": map[string]any{"id": 1, "name": "Admin"},
|
||||||
|
"created_at": "2026-01-15T00:00:00Z",
|
||||||
|
"approval": map[string]any{"step_number": 1, "step_name": "Pengajuan", "action": nil},
|
||||||
|
"sources": []map[string]any{
|
||||||
|
{
|
||||||
|
"source_project_flock_kandang": map[string]any{"id": 1, "kandang_id": 1, "project_flock_id": 1, "kandang": map[string]any{"id": 1, "name": "Kandang A"}},
|
||||||
|
"qty": 5000.0, "note": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"targets": []map[string]any{
|
||||||
|
{
|
||||||
|
"target_project_flock_kandang": map[string]any{"id": 2, "kandang_id": 2, "project_flock_id": 2, "kandang": map[string]any{"id": 2, "name": "Kandang B"}},
|
||||||
|
"qty": 5000.0, "note": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
case "/api/production/uniformities":
|
case "/api/production/uniformities":
|
||||||
meta.QueryParams = []parameterMeta{
|
meta.QueryParams = []parameterMeta{
|
||||||
{Name: "page", In: "query", Description: "Page number.", Example: 1},
|
{Name: "page", In: "query", Description: "Page number.", Example: 1},
|
||||||
|
|||||||
Reference in New Issue
Block a user