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"
|
||||
)
|
||||
const (
|
||||
P_ChickinsGetAll = "lti.production.chickins.list"
|
||||
P_ChickinsCreateOne = "lti.production.chickins.create"
|
||||
P_ChickinsGetOne = "lti.production.chickins.detail"
|
||||
P_ChickinsApproval = "lti.production.chickins.approve"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"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 {
|
||||
// query := &validation.Query{
|
||||
// Page: c.QueryInt("page", 1),
|
||||
// Limit: c.QueryInt("limit", 10),
|
||||
// ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)),
|
||||
// }
|
||||
func (u *ChickinController) GetAll(c *fiber.Ctx) error {
|
||||
query := &validation.Query{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)),
|
||||
}
|
||||
|
||||
// result, totalResults, err := u.ChickinService.GetAll(c, query)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
result, totalResults, err := u.ChickinService.GetAll(c, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// return c.Status(fiber.StatusOK).
|
||||
// JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{
|
||||
// Code: fiber.StatusOK,
|
||||
// Status: "success",
|
||||
// Message: "Get all chickins successfully",
|
||||
// Meta: response.Meta{
|
||||
// Page: query.Page,
|
||||
// Limit: query.Limit,
|
||||
// TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
// TotalResults: totalResults,
|
||||
// },
|
||||
// Data: dto.ToChickinListDTOs(result),
|
||||
// })
|
||||
// }
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all chickins successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToChickinListDTOs(result),
|
||||
})
|
||||
}
|
||||
|
||||
// func (u *ChickinController) GetOne(c *fiber.Ctx) error {
|
||||
// param := c.Params("id")
|
||||
|
||||
@@ -15,7 +15,7 @@ func ChickinRoutes(v1 fiber.Router, u user.UserService, s chickin.ChickinService
|
||||
route := v1.Group("/chickins")
|
||||
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.Get("/:id",m.RequirePermissions(m.P_ChickinsGetOne), ctrl.GetOne)
|
||||
// route.Patch("/:id", ctrl.UpdateOne)
|
||||
|
||||
@@ -173,6 +173,37 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
||||
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)
|
||||
for i := range recordings {
|
||||
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)
|
||||
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 {
|
||||
return nil, 0, stateErr
|
||||
}
|
||||
@@ -1308,6 +1339,82 @@ func (s *recordingService) evaluatePopulationMutationState(ctx context.Context,
|
||||
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) {
|
||||
if transfer == nil || transfer.Id == 0 {
|
||||
return false, nil
|
||||
|
||||
+120
@@ -17,6 +17,8 @@ type TransferLayingRepository interface {
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetLatestApprovedBySourceKandang(ctx context.Context, sourceProjectFlockKandangID 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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
ListStyle bool
|
||||
QueryParams []parameterMeta
|
||||
ExampleResponse any
|
||||
Exclude bool
|
||||
}
|
||||
|
||||
@@ -187,6 +188,13 @@ func buildOpenAPIDocument(routes []normalizedRoute) map[string]any {
|
||||
}
|
||||
|
||||
openAPIPath := toOpenAPIPath(route.Path)
|
||||
responseContent := map[string]any{
|
||||
"schema": successSchema(meta),
|
||||
}
|
||||
if meta.ExampleResponse != nil {
|
||||
responseContent["example"] = meta.ExampleResponse
|
||||
}
|
||||
|
||||
operation := map[string]any{
|
||||
"summary": meta.Summary,
|
||||
"description": meta.Description,
|
||||
@@ -195,9 +203,7 @@ func buildOpenAPIDocument(routes []normalizedRoute) map[string]any {
|
||||
"200": map[string]any{
|
||||
"description": "Successful response",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": successSchema(meta),
|
||||
},
|
||||
"application/json": responseContent,
|
||||
},
|
||||
},
|
||||
"401": map[string]any{
|
||||
@@ -777,6 +783,31 @@ func describeRoute(route normalizedRoute) routeMeta {
|
||||
{Name: "limit", In: "query", Description: "Page size.", Example: 10},
|
||||
{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":
|
||||
meta.QueryParams = []parameterMeta{
|
||||
{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: "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":
|
||||
meta.QueryParams = []parameterMeta{
|
||||
{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: "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":
|
||||
meta.QueryParams = []parameterMeta{
|
||||
{Name: "page", In: "query", Description: "Page number.", Example: 1},
|
||||
|
||||
Reference in New Issue
Block a user