From 7638c183f50eee0b20a6c0da8fcc46a3bf8417dc Mon Sep 17 00:00:00 2001 From: Adnan Zahir Date: Wed, 8 Apr 2026 15:13:31 +0700 Subject: [PATCH 1/8] codex/fix: dashboard independent recording values without uniformity --- .../dashboards/services/dashboard.service.go | 96 ++++++++++++------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/internal/modules/dashboards/services/dashboard.service.go b/internal/modules/dashboards/services/dashboard.service.go index 928205d2..4ad5cc8e 100644 --- a/internal/modules/dashboards/services/dashboard.service.go +++ b/internal/modules/dashboards/services/dashboard.service.go @@ -274,10 +274,10 @@ func (s dashboardService) buildPerformanceCharts(ctx context.Context, params *va cumFeed := 0.0 for _, week := range weeks { - rec := recordingMap[week] - uni := uniformityMap[week] - std := standardMap[week] - stdFcr := standardFcrMap[week] + rec, hasRec := recordingMap[week] + uni, hasUni := uniformityMap[week] + std, hasStd := standardMap[week] + stdFcr, hasStdFcr := standardFcrMap[week] weekEgg := weeklyEggMap[week] weekFeed := weeklyFeedMap[week] @@ -293,37 +293,69 @@ func (s dashboardService) buildPerformanceCharts(ctx context.Context, params *va actFcrCum = cumFeed / cumEgg } - bodyWeightDataset = append(bodyWeightDataset, map[string]interface{}{ - "week": week, - "body_weight": roundTo(uni.AverageWeight, 2), - "std_body_weight": roundTo(std.StdBodyWeight, 2), - }) + bodyWeightRow := map[string]interface{}{ + "week": week, + } + if hasUni { + bodyWeightRow["body_weight"] = roundTo(uni.AverageWeight, 2) + } + if hasStd { + bodyWeightRow["std_body_weight"] = roundTo(std.StdBodyWeight, 2) + } + if len(bodyWeightRow) > 1 { + bodyWeightDataset = append(bodyWeightDataset, bodyWeightRow) + } - performanceDataset = append(performanceDataset, map[string]interface{}{ - "week": week, - "act_laying": roundTo(rec.HenDay, 2), - "std_laying": roundTo(std.StdLaying, 2), - "act_egg_weight": roundTo(rec.EggWeight, 2), - "std_egg_weight": roundTo(std.StdEggWeight, 2), - "act_feed_intake": roundTo(rec.FeedIntake, 2), - "std_feed_intake": roundTo(std.StdFeedIntake, 2), - "act_uniformity": roundTo(uni.Uniformity, 2), - "std_uniformity": roundTo(std.StdUniformity, 2), - }) + performanceRow := map[string]interface{}{ + "week": week, + } + if hasRec { + performanceRow["act_laying"] = roundTo(rec.HenDay, 2) + performanceRow["act_egg_weight"] = roundTo(rec.EggWeight, 2) + performanceRow["act_feed_intake"] = roundTo(rec.FeedIntake, 2) + } + if hasUni { + performanceRow["act_uniformity"] = roundTo(uni.Uniformity, 2) + } + if hasStd { + performanceRow["std_laying"] = roundTo(std.StdLaying, 2) + performanceRow["std_egg_weight"] = roundTo(std.StdEggWeight, 2) + performanceRow["std_feed_intake"] = roundTo(std.StdFeedIntake, 2) + performanceRow["std_uniformity"] = roundTo(std.StdUniformity, 2) + } + if len(performanceRow) > 1 { + performanceDataset = append(performanceDataset, performanceRow) + } - fcrDataset = append(fcrDataset, map[string]interface{}{ - "week": week, - "act_fcr": roundTo(actFcr, 2), - "std_fcr": roundTo(stdFcr, 2), - "act_fcr_cum": roundTo(actFcrCum, 2), - "std_fcr_cum": roundTo(stdFcr, 2), - }) + fcrRow := map[string]interface{}{ + "week": week, + } + if weekEgg > 0 && weekFeed > 0 { + fcrRow["act_fcr"] = roundTo(actFcr, 2) + } + if cumEgg > 0 && cumFeed > 0 { + fcrRow["act_fcr_cum"] = roundTo(actFcrCum, 2) + } + if hasStdFcr { + fcrRow["std_fcr"] = roundTo(stdFcr, 2) + fcrRow["std_fcr_cum"] = roundTo(stdFcr, 2) + } + if len(fcrRow) > 1 { + fcrDataset = append(fcrDataset, fcrRow) + } - deplesiDataset = append(deplesiDataset, map[string]interface{}{ - "week": week, - "act_deplesi": roundTo(rec.CumDepletionRate, 2), - "std_deplesi": roundTo(std.StdDepletion, 2), - }) + deplesiRow := map[string]interface{}{ + "week": week, + } + if hasRec { + deplesiRow["act_deplesi"] = roundTo(rec.CumDepletionRate, 2) + } + if hasStd { + deplesiRow["std_deplesi"] = roundTo(std.StdDepletion, 2) + } + if len(deplesiRow) > 1 { + deplesiDataset = append(deplesiDataset, deplesiRow) + } } qualityRows, err := s.Repository.GetEggQualityWeeklyMetrics(ctx, startDate, endExclusive, filter) From ca698ff2ae59b31daa090740bbf1a948698ae8bd Mon Sep 17 00:00:00 2001 From: Adnan Zahir Date: Tue, 14 Apr 2026 13:09:47 +0700 Subject: [PATCH 2/8] codex/fix: uniformity week calculation --- .../dashboard_stats.repository.go | 45 ++++-- .../repositories/uniformity.repository.go | 2 + .../services/uniformity.service.go | 148 +++++++++++------- .../services/uniformity_week_test.go | 78 +++++++++ .../validations/uniformity.validation.go | 16 +- .../repports/services/repport.service.go | 23 ++- 6 files changed, 236 insertions(+), 76 deletions(-) create mode 100644 internal/modules/production/uniformities/services/uniformity_week_test.go diff --git a/internal/modules/dashboards/repositories/dashboard_stats.repository.go b/internal/modules/dashboards/repositories/dashboard_stats.repository.go index 363e6aa5..a7743c37 100644 --- a/internal/modules/dashboards/repositories/dashboard_stats.repository.go +++ b/internal/modules/dashboards/repositories/dashboard_stats.repository.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "gitlab.com/mbugroup/lti-api.git/internal/config" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -104,6 +105,15 @@ func applyDashboardFilters(db *gorm.DB, filters *validation.DashboardFilter) *go return db } +func dashboardUniformityWeekExpr() string { + return fmt.Sprintf(`CASE + WHEN u.uniform_date IS NULL OR pc.chick_in_date IS NULL THEN 0 + WHEN u.uniform_date::date < pc.chick_in_date THEN 0 + WHEN UPPER(pf.category) = 'LAYING' THEN (((u.uniform_date::date - pc.chick_in_date)::int) / 7) + %d + ELSE (((u.uniform_date::date - pc.chick_in_date)::int) / 7) + 1 + END`, config.LayingWeekStart()) +} + func (r *DashboardRepositoryImpl) GetRecordingWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]RecordingWeeklyMetric, error) { var rows []RecordingWeeklyMetric @@ -139,20 +149,29 @@ func (r *DashboardRepositoryImpl) GetRecordingWeeklyMetrics(ctx context.Context, func (r *DashboardRepositoryImpl) GetUniformityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]UniformityWeeklyMetric, error) { var rows []UniformityWeeklyMetric + weekExpr := dashboardUniformityWeekExpr() db := r.DB().WithContext(ctx). Table("project_flock_kandang_uniformity AS u"). - Select(`u.week AS week, + Select(fmt.Sprintf(`%s AS week, COALESCE(AVG(u.uniformity), 0) AS uniformity, - COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`). + COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`, weekExpr)). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = u.project_flock_kandang_id"). + Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). + Joins(`JOIN ( + SELECT project_flock_kandang_id, MIN(chick_in_date)::date AS chick_in_date + FROM project_chickins + WHERE deleted_at IS NULL + GROUP BY project_flock_kandang_id + ) AS pc ON pc.project_flock_kandang_id = u.project_flock_kandang_id`). Where("u.uniform_date IS NOT NULL"). - Where("u.uniform_date >= ? AND u.uniform_date < ?", start, end) + Where("u.uniform_date >= ? AND u.uniform_date < ?", start, end). + Where("u.uniform_date::date >= pc.chick_in_date") db = applyDashboardFilters(db, filters) - if err := db.Group("u.week").Order("u.week ASC").Scan(&rows).Error; err != nil { + if err := db.Group("week").Order("week ASC").Scan(&rows).Error; err != nil { return nil, err } @@ -518,23 +537,31 @@ func (r *DashboardRepositoryImpl) GetComparisonWeeklyUniformityMetrics(ctx conte } var rows []ComparisonUniformityMetric + weekExpr := dashboardUniformityWeekExpr() db := r.DB().WithContext(ctx). Table("project_flock_kandang_uniformity AS u"). - Select(fmt.Sprintf(`u.week AS week, + Select(fmt.Sprintf(`%s AS week, %s AS series_id, COALESCE(AVG(u.uniformity), 0) AS uniformity, - COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`, seriesExpr)). + COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`, weekExpr, seriesExpr)). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = u.project_flock_kandang_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Joins("JOIN locations AS loc ON loc.id = k.location_id"). + Joins(`JOIN ( + SELECT project_flock_kandang_id, MIN(chick_in_date)::date AS chick_in_date + FROM project_chickins + WHERE deleted_at IS NULL + GROUP BY project_flock_kandang_id + ) AS pc ON pc.project_flock_kandang_id = u.project_flock_kandang_id`). Where("u.uniform_date IS NOT NULL"). - Where("u.uniform_date >= ? AND u.uniform_date < ?", start, end) + Where("u.uniform_date >= ? AND u.uniform_date < ?", start, end). + Where("u.uniform_date::date >= pc.chick_in_date") db = applyDashboardFilters(db, filters) - groupBy := fmt.Sprintf("u.week, %s", groupExpr) - orderBy := fmt.Sprintf("u.week ASC, %s", orderExpr) + groupBy := fmt.Sprintf("week, %s", groupExpr) + orderBy := fmt.Sprintf("week ASC, %s", orderExpr) if err := db.Group(groupBy).Order(orderBy).Scan(&rows).Error; err != nil { return nil, err } diff --git a/internal/modules/production/uniformities/repositories/uniformity.repository.go b/internal/modules/production/uniformities/repositories/uniformity.repository.go index 8453bf84..e13e882e 100644 --- a/internal/modules/production/uniformities/repositories/uniformity.repository.go +++ b/internal/modules/production/uniformities/repositories/uniformity.repository.go @@ -42,7 +42,9 @@ func (r *UniformityRepositoryImpl) GetAllWithFilters(ctx context.Context, offset func (r *UniformityRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db. + Preload("ProjectFlockKandang.ProjectFlock"). Preload("ProjectFlockKandang.ProjectFlock.Location"). + Preload("ProjectFlockKandang.Chickins"). Preload("ProjectFlockKandang.Kandang.Location") } } diff --git a/internal/modules/production/uniformities/services/uniformity.service.go b/internal/modules/production/uniformities/services/uniformity.service.go index 7de39ef8..143f4e93 100644 --- a/internal/modules/production/uniformities/services/uniformity.service.go +++ b/internal/modules/production/uniformities/services/uniformity.service.go @@ -106,6 +106,7 @@ func (s uniformityService) GetAll(c *fiber.Ctx, params *validation.Query) ([]ent s.Log.Errorf("Failed to get uniformitys: %+v", err) return nil, 0, err } + s.normalizeUniformityWeeks(uniformitys) if err := s.attachLatestApprovals(c.Context(), uniformitys); err != nil { return nil, 0, err } @@ -125,6 +126,7 @@ func (s uniformityService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlockKa s.Log.Errorf("Failed get uniformity by id: %+v", err) return nil, err } + s.normalizeUniformityWeek(uniformity) if err := s.attachLatestApproval(c.Context(), uniformity); err != nil { return nil, err } @@ -135,6 +137,23 @@ func (s uniformityService) GetSummary(c *fiber.Ctx, id uint) (*entity.ProjectFlo return s.GetOne(c, id) } +func (s *uniformityService) normalizeUniformityWeeks(items []entity.ProjectFlockKandangUniformity) { + for i := range items { + s.normalizeUniformityWeek(&items[i]) + } +} + +func (s *uniformityService) normalizeUniformityWeek(item *entity.ProjectFlockKandangUniformity) { + if item == nil || item.UniformDate == nil { + return + } + computedWeek, err := s.computeUniformityWeekForPFK(&item.ProjectFlockKandang, *item.UniformDate) + if err != nil || computedWeek <= 0 { + return + } + item.Week = computedWeek +} + func (s uniformityService) GetStandard(c *fiber.Ctx, uniformity *entity.ProjectFlockKandangUniformity) (*utypes.UniformityStandard, error) { if uniformity == nil { return nil, nil @@ -372,24 +391,18 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file } return nil, err } - category := strings.TrimSpace(pfk.ProjectFlock.Category) - if s.ProductionStandardRepo != nil && pfk.ProjectFlock.ProductionStandardId != 0 { - if standard, err := s.ProductionStandardRepo.GetByID(c.Context(), pfk.ProjectFlock.ProductionStandardId, nil); err == nil { - if strings.TrimSpace(standard.ProjectCategory) != "" { - category = standard.ProjectCategory - } - } + computedWeek, err := s.computeUniformityWeekForPFK(pfk, uniformDate) + if err != nil { + return nil, err } - weekBase := 1 - isLayingCategory := strings.EqualFold(category, string(utils.ProjectFlockCategoryLaying)) - if isLayingCategory { - weekBase = config.LayingWeekStart() - } - if req.Week < weekBase { - if isLayingCategory { - return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("week must start from %d for laying projects", weekBase)) - } - return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects") + isGrowingCategory := strings.EqualFold(strings.TrimSpace(pfk.ProjectFlock.Category), string(utils.ProjectFlockCategoryGrowing)) + if req.Week > 0 && req.Week != computedWeek { + s.Log.WithFields(logrus.Fields{ + "project_flock_kandang_id": req.ProjectFlockKandangId, + "uniform_date": uniformDate.Format("2006-01-02"), + "requested_week": req.Week, + "computed_week": computedWeek, + }).Warn("Uniformity week mismatch detected; using computed week") } var latestWeek int @@ -400,17 +413,11 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file Scan(&latestWeek).Error; err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate uniformity week sequence") } - if latestWeek == 0 && req.Week != weekBase { - if isLayingCategory { - return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("week must start from %d for laying projects", weekBase)) - } - return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects") - } - if latestWeek > 0 && req.Week > latestWeek+1 { + if latestWeek > 0 && computedWeek > latestWeek+1 { return nil, fiber.NewError(fiber.StatusBadRequest, "week must be sequential without skipping") } - if err := s.ensureUniqueUniformity(c.Context(), 0, req.ProjectFlockKandangId, req.Week); err != nil { + if err := s.ensureUniqueUniformity(c.Context(), 0, req.ProjectFlockKandangId, computedWeek); err != nil { return nil, err } @@ -438,7 +445,7 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file createBody := &entity.ProjectFlockKandangUniformity{ Uniformity: calculation.Uniformity, - Week: req.Week, + Week: computedWeek, Cv: calculation.Cv, ChickQtyOfWeight: calculation.ChickQtyOfWeight, MeanUp: calculation.MeanUp, @@ -467,7 +474,7 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file ); err != nil { return err } - if strings.EqualFold(category, string(utils.ProjectFlockCategoryGrowing)) { + if isGrowingCategory { if err := s.updateGrowingFcrForWeek(tx, createBody.ProjectFlockKandangId, createBody.Week, calculation.MeanUp); err != nil { return err } @@ -536,9 +543,6 @@ func (s uniformityService) UpdateOne(c *fiber.Ctx, req *validation.Update, id ui } updateBody["project_flock_kandang_id"] = *req.ProjectFlockKandangId } - if req.Week != nil { - updateBody["week"] = *req.Week - } if req.Date != nil || req.ProjectFlockKandangId != nil || req.Week != nil { current, err := s.Repository.GetByID(c.Context(), id, nil) @@ -552,15 +556,11 @@ func (s uniformityService) UpdateOne(c *fiber.Ctx, req *validation.Update, id ui if targetDate == nil { targetDate = current.UniformDate } - targetWeek := current.Week - if req.Week != nil { - targetWeek = *req.Week - } targetPFKID := current.ProjectFlockKandangId if req.ProjectFlockKandangId != nil { targetPFKID = *req.ProjectFlockKandangId } - if targetPFKID != 0 && targetWeek > 0 { + if targetPFKID != 0 && targetDate != nil { pfk, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetPFKID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -568,28 +568,21 @@ func (s uniformityService) UpdateOne(c *fiber.Ctx, req *validation.Update, id ui } return nil, err } - category := strings.TrimSpace(pfk.ProjectFlock.Category) - if s.ProductionStandardRepo != nil && pfk.ProjectFlock.ProductionStandardId != 0 { - if standard, err := s.ProductionStandardRepo.GetByID(c.Context(), pfk.ProjectFlock.ProductionStandardId, nil); err == nil { - if strings.TrimSpace(standard.ProjectCategory) != "" { - category = standard.ProjectCategory - } - } + computedWeek, err := s.computeUniformityWeekForPFK(pfk, *targetDate) + if err != nil { + return nil, err } - weekBase := 1 - isLayingCategory := strings.EqualFold(category, string(utils.ProjectFlockCategoryLaying)) - if isLayingCategory { - weekBase = config.LayingWeekStart() + if req.Week != nil && *req.Week != computedWeek { + s.Log.WithFields(logrus.Fields{ + "uniformity_id": id, + "project_flock_kandang_id": targetPFKID, + "uniform_date": targetDate.Format("2006-01-02"), + "requested_week": *req.Week, + "computed_week": computedWeek, + }).Warn("Uniformity week mismatch detected on update; using computed week") } - if targetWeek < weekBase { - if isLayingCategory { - return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("week must start from %d for laying projects", weekBase)) - } - return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects") - } - } - if targetDate != nil { - if err := s.ensureUniqueUniformity(c.Context(), id, targetPFKID, targetWeek); err != nil { + updateBody["week"] = computedWeek + if err := s.ensureUniqueUniformity(c.Context(), id, targetPFKID, computedWeek); err != nil { return nil, err } } @@ -734,6 +727,51 @@ func (s *uniformityService) ensureUniqueUniformity(ctx context.Context, id uint, return nil } +func (s *uniformityService) computeUniformityWeekForPFK(pfk *entity.ProjectFlockKandang, uniformDate time.Time) (int, error) { + if pfk == nil || pfk.Id == 0 { + return 0, fiber.NewError(fiber.StatusBadRequest, "Project flock kandang tidak valid") + } + + chickInDate, ok := earliestUniformityChickInDate(pfk.Chickins) + if !ok { + return 0, fiber.NewError(fiber.StatusBadRequest, "Tanggal chick in tidak ditemukan") + } + + chickInDay := normalizeUniformityDateOnlyUTC(chickInDate) + uniformDay := normalizeUniformityDateOnlyUTC(uniformDate) + if uniformDay.Before(chickInDay) { + return 0, fiber.NewError(fiber.StatusBadRequest, "Uniformity date tidak boleh sebelum tanggal chick in") + } + + diff := int(uniformDay.Sub(chickInDay).Hours() / 24) + weekBase := 1 + if strings.EqualFold(strings.TrimSpace(pfk.ProjectFlock.Category), string(utils.ProjectFlockCategoryLaying)) { + weekBase = config.LayingWeekStart() + } + + return (diff / 7) + weekBase, nil +} + +func earliestUniformityChickInDate(chickins []entity.ProjectChickin) (time.Time, bool) { + var earliest time.Time + for _, chickin := range chickins { + if chickin.ChickInDate.IsZero() { + continue + } + if earliest.IsZero() || chickin.ChickInDate.Before(earliest) { + earliest = chickin.ChickInDate + } + } + if earliest.IsZero() { + return time.Time{}, false + } + return earliest, true +} + +func normalizeUniformityDateOnlyUTC(value time.Time) time.Time { + return time.Date(value.UTC().Year(), value.UTC().Month(), value.UTC().Day(), 0, 0, 0, 0, time.UTC) +} + func (s uniformityService) DeleteOne(c *fiber.Ctx, id uint) error { if err := m.EnsureUniformityAccess(c, s.Repository.DB(), id); err != nil { return err diff --git a/internal/modules/production/uniformities/services/uniformity_week_test.go b/internal/modules/production/uniformities/services/uniformity_week_test.go new file mode 100644 index 00000000..54dbfc2e --- /dev/null +++ b/internal/modules/production/uniformities/services/uniformity_week_test.go @@ -0,0 +1,78 @@ +package service + +import ( + "testing" + "time" + + "gitlab.com/mbugroup/lti-api.git/internal/config" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gitlab.com/mbugroup/lti-api.git/internal/utils" +) + +func TestComputeUniformityWeekForPFK(t *testing.T) { + originalWeekStart := config.TransferToLayingGrowingMaxWeek + config.TransferToLayingGrowingMaxWeek = 19 + t.Cleanup(func() { + config.TransferToLayingGrowingMaxWeek = originalWeekStart + }) + + svc := &uniformityService{} + baseDate := time.Date(2026, time.January, 1, 9, 30, 0, 0, time.UTC) + + t.Run("growing starts from week one", func(t *testing.T) { + pfk := &entity.ProjectFlockKandang{ + Id: 1, + ProjectFlock: entity.ProjectFlock{ + Category: string(utils.ProjectFlockCategoryGrowing), + }, + Chickins: []entity.ProjectChickin{ + {ChickInDate: baseDate}, + }, + } + + week, err := svc.computeUniformityWeekForPFK(pfk, baseDate) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if week != 1 { + t.Fatalf("expected week 1, got %d", week) + } + }) + + t.Run("laying uses configured week base and earliest chick in", func(t *testing.T) { + pfk := &entity.ProjectFlockKandang{ + Id: 2, + ProjectFlock: entity.ProjectFlock{ + Category: string(utils.ProjectFlockCategoryLaying), + }, + Chickins: []entity.ProjectChickin{ + {ChickInDate: baseDate.AddDate(0, 0, 4)}, + {ChickInDate: baseDate}, + }, + } + + week, err := svc.computeUniformityWeekForPFK(pfk, baseDate.AddDate(0, 0, 7)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if week != 20 { + t.Fatalf("expected week 20, got %d", week) + } + }) + + t.Run("rejects date before chick in", func(t *testing.T) { + pfk := &entity.ProjectFlockKandang{ + Id: 3, + ProjectFlock: entity.ProjectFlock{ + Category: string(utils.ProjectFlockCategoryLaying), + }, + Chickins: []entity.ProjectChickin{ + {ChickInDate: baseDate}, + }, + } + + if _, err := svc.computeUniformityWeekForPFK(pfk, baseDate.AddDate(0, 0, -1)); err == nil { + t.Fatal("expected error for date before chick in") + } + }) +} diff --git a/internal/modules/production/uniformities/validations/uniformity.validation.go b/internal/modules/production/uniformities/validations/uniformity.validation.go index e4f7f8a0..651a29e9 100644 --- a/internal/modules/production/uniformities/validations/uniformity.validation.go +++ b/internal/modules/production/uniformities/validations/uniformity.validation.go @@ -12,7 +12,7 @@ import ( type Create struct { Date string `form:"date" validate:"required"` ProjectFlockKandangId uint `form:"project_flock_kandang_id" validate:"required,number,min=1"` - Week int `form:"week" validate:"required,min=1"` + Week int `form:"week" validate:"omitempty,min=1"` } type Update struct { @@ -120,14 +120,14 @@ func ParseCreate(c *fiber.Ctx) (*Create, *multipart.FileHeader, error) { return nil, nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required") } + week := 0 weekStr := strings.TrimSpace(c.FormValue("week")) - if weekStr == "" { - return nil, nil, fiber.NewError(fiber.StatusBadRequest, "week is required") - } - - week, err := strconv.Atoi(weekStr) - if err != nil || week <= 0 { - return nil, nil, fiber.NewError(fiber.StatusBadRequest, "week is required") + if weekStr != "" { + parsedWeek, err := strconv.Atoi(weekStr) + if err != nil || parsedWeek <= 0 { + return nil, nil, fiber.NewError(fiber.StatusBadRequest, "week is invalid") + } + week = parsedWeek } file, err := c.FormFile("document") diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 3468f266..b866cf96 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -939,12 +939,27 @@ func (s *repportService) getUniformityByWeek(ctx context.Context, projectFlockKa return result, nil } + weekExpr := fmt.Sprintf(`CASE + WHEN u.uniform_date IS NULL OR pc.chick_in_date IS NULL THEN 0 + WHEN u.uniform_date::date < pc.chick_in_date THEN 0 + WHEN UPPER(pf.category) = 'LAYING' THEN (((u.uniform_date::date - pc.chick_in_date)::int) / 7) + %d + ELSE (((u.uniform_date::date - pc.chick_in_date)::int) / 7) + 1 + END`, config.LayingWeekStart()) + var rows []entity.ProjectFlockKandangUniformity if err := s.db.WithContext(ctx). - Model(&entity.ProjectFlockKandangUniformity{}). - Select("week, uniformity, uniform_date, id, chart_data"). - Where("project_flock_kandang_id = ?", projectFlockKandangID). - Where("week IN ?", weeks). + Table("project_flock_kandang_uniformity AS u"). + Select(fmt.Sprintf("%s AS week, u.uniformity, u.uniform_date, u.id, u.chart_data", weekExpr)). + Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = u.project_flock_kandang_id"). + Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). + Joins(`JOIN ( + SELECT project_flock_kandang_id, MIN(chick_in_date)::date AS chick_in_date + FROM project_chickins + WHERE deleted_at IS NULL + GROUP BY project_flock_kandang_id + ) AS pc ON pc.project_flock_kandang_id = u.project_flock_kandang_id`). + Where("u.project_flock_kandang_id = ?", projectFlockKandangID). + Where(fmt.Sprintf("%s IN ?", weekExpr), weeks). Order("uniform_date DESC"). Order("id DESC"). Find(&rows).Error; err != nil { From cd549de57843cec5c79e37c282c11b2167fe9f03 Mon Sep 17 00:00:00 2001 From: giovanni Date: Tue, 14 Apr 2026 14:48:56 +0700 Subject: [PATCH 3/8] adjust edit delivery order; add migration for delivery order; adjust response get marketing --- ...2_add_field_weight_per_convertion.down.sql | 3 + ...542_add_field_weight_per_convertion.up.sql | 4 + .../entities/marketing_delivery_product.go | 1 + .../marketing/dto/deliveryorder.dto.go | 80 +++++++++++-------- .../services/deliveryorder.service.go | 52 ++++++++++-- .../marketing/services/salesorder.service.go | 9 +-- .../validations/deliveryorder.validation.go | 15 ++-- 7 files changed, 111 insertions(+), 53 deletions(-) create mode 100644 internal/database/migrations/20260414064542_add_field_weight_per_convertion.down.sql create mode 100644 internal/database/migrations/20260414064542_add_field_weight_per_convertion.up.sql diff --git a/internal/database/migrations/20260414064542_add_field_weight_per_convertion.down.sql b/internal/database/migrations/20260414064542_add_field_weight_per_convertion.down.sql new file mode 100644 index 00000000..489fce73 --- /dev/null +++ b/internal/database/migrations/20260414064542_add_field_weight_per_convertion.down.sql @@ -0,0 +1,3 @@ +-- Remove convertion fields from marketing_delivery_products table +ALTER TABLE marketing_delivery_products +DROP COLUMN IF EXISTS weight_per_convertion; diff --git a/internal/database/migrations/20260414064542_add_field_weight_per_convertion.up.sql b/internal/database/migrations/20260414064542_add_field_weight_per_convertion.up.sql new file mode 100644 index 00000000..bf7b8bfc --- /dev/null +++ b/internal/database/migrations/20260414064542_add_field_weight_per_convertion.up.sql @@ -0,0 +1,4 @@ +-- Add convertion fields to marketing_delivery_products table +ALTER TABLE marketing_delivery_products +ADD COLUMN IF NOT EXISTS weight_per_convertion NUMERIC(15, 3); + diff --git a/internal/entities/marketing_delivery_product.go b/internal/entities/marketing_delivery_product.go index 78ca61ab..2d20fca4 100644 --- a/internal/entities/marketing_delivery_product.go +++ b/internal/entities/marketing_delivery_product.go @@ -12,6 +12,7 @@ type MarketingDeliveryProduct struct { UnitPrice float64 `gorm:"type:numeric(15,3)"` TotalWeight float64 `gorm:"type:numeric(15,3)"` AvgWeight float64 `gorm:"type:numeric(15,3)"` + WeightPerConvertion *float64 `gorm:"type:numeric(15,3)"` TotalPrice float64 `gorm:"type:numeric(15,3)"` DeliveryDate *time.Time `gorm:"type:timestamptz"` VehicleNumber string `gorm:"type:varchar(50)"` diff --git a/internal/modules/marketing/dto/deliveryorder.dto.go b/internal/modules/marketing/dto/deliveryorder.dto.go index 20b3e42b..e8955d34 100644 --- a/internal/modules/marketing/dto/deliveryorder.dto.go +++ b/internal/modules/marketing/dto/deliveryorder.dto.go @@ -49,26 +49,30 @@ type MarketingDetailDTO struct { } type MarketingDeliveryProductDTO struct { - Id uint `json:"id"` - MarketingProductId uint `json:"marketing_product_id"` - Qty float64 `json:"qty"` - UnitPrice float64 `json:"unit_price"` - TotalWeight float64 `json:"total_weight"` - AvgWeight float64 `json:"avg_weight"` - TotalPrice float64 `json:"total_price"` - DeliveryDate *time.Time `json:"delivery_date"` - VehicleNumber string `json:"vehicle_number"` - ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"` + Id uint `json:"id"` + MarketingProductId uint `json:"marketing_product_id"` + Qty float64 `json:"qty"` + UnitPrice float64 `json:"unit_price"` + TotalWeight float64 `json:"total_weight"` + AvgWeight float64 `json:"avg_weight"` + TotalPrice float64 `json:"total_price"` + DeliveryDate *time.Time `json:"delivery_date"` + VehicleNumber string `json:"vehicle_number"` + ConvertionUnit *string `json:"-"` + WeightPerConvertion *float64 `json:"-"` + ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"` } type DeliveryItemDTO struct { - ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse"` - Qty float64 `json:"qty"` - UnitPrice float64 `json:"unit_price"` - TotalWeight float64 `json:"total_weight"` - AvgWeight float64 `json:"avg_weight"` - TotalPrice float64 `json:"total_price"` - VehicleNumber string `json:"vehicle_number"` + ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse"` + Qty float64 `json:"qty"` + UnitPrice float64 `json:"unit_price"` + TotalWeight float64 `json:"total_weight"` + AvgWeight float64 `json:"avg_weight"` + WeightPerConvertion *float64 `json:"weight_per_convertion"` + TotalPeti *float64 `json:"total_peti"` + TotalPrice float64 `json:"total_price"` + VehicleNumber string `json:"vehicle_number"` } type DeliveryGroupDTO struct { @@ -147,15 +151,16 @@ func ToDeliveryMarketingProductDTO(e entity.MarketingProduct, marketingType stri func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingDeliveryProductDTO { return MarketingDeliveryProductDTO{ - Id: e.Id, - MarketingProductId: e.MarketingProductId, - Qty: e.UsageQty, - UnitPrice: e.UnitPrice, - TotalWeight: e.TotalWeight, - AvgWeight: e.AvgWeight, - TotalPrice: e.TotalPrice, - DeliveryDate: e.DeliveryDate, - VehicleNumber: e.VehicleNumber, + Id: e.Id, + MarketingProductId: e.MarketingProductId, + Qty: e.UsageQty, + UnitPrice: e.UnitPrice, + TotalWeight: e.TotalWeight, + AvgWeight: e.AvgWeight, + TotalPrice: e.TotalPrice, + DeliveryDate: e.DeliveryDate, + VehicleNumber: e.VehicleNumber, + WeightPerConvertion: e.WeightPerConvertion, } } @@ -285,6 +290,7 @@ func enrichDeliveryProductDTOsWithWarehouse(deliveryProductDTOs []MarketingDeliv mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(product.ProductWarehouse) deliveryProductDTOs[i].ProductWarehouse = &mapped } + deliveryProductDTOs[i].ConvertionUnit = product.ConvertionUnit } } @@ -322,13 +328,21 @@ func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber stri } deliveryItem := DeliveryItemDTO{ - ProductWarehouse: product.ProductWarehouse, - Qty: product.Qty, - UnitPrice: product.UnitPrice, - TotalWeight: product.TotalWeight, - AvgWeight: product.AvgWeight, - TotalPrice: product.TotalPrice, - VehicleNumber: product.VehicleNumber, + ProductWarehouse: product.ProductWarehouse, + Qty: product.Qty, + UnitPrice: product.UnitPrice, + TotalWeight: product.TotalWeight, + AvgWeight: product.AvgWeight, + WeightPerConvertion: product.WeightPerConvertion, + TotalPrice: product.TotalPrice, + VehicleNumber: product.VehicleNumber, + } + if product.ConvertionUnit != nil && + strings.EqualFold(*product.ConvertionUnit, "PETI") && + product.WeightPerConvertion != nil && + *product.WeightPerConvertion > 0 { + totalPeti := product.TotalWeight / *product.WeightPerConvertion + deliveryItem.TotalPeti = &totalPeti } group.Deliveries = append(group.Deliveries, deliveryItem) } diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index 2ae8dec9..34c415c3 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "strings" "time" @@ -375,11 +376,12 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery itemDeliveryDate = &parsedDate } - totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, requestedProduct.Qty, requestedProduct.AvgWeight, requestedProduct.UnitPrice, foundMarketingProduct.Week) + totalWeight, totalPrice := s.resolveDeliveryTotals(marketing.MarketingType, requestedProduct, foundMarketingProduct) deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId deliveryProduct.UnitPrice = requestedProduct.UnitPrice deliveryProduct.AvgWeight = requestedProduct.AvgWeight + deliveryProduct.WeightPerConvertion = requestedProduct.WeightPerConvertion deliveryProduct.TotalWeight = totalWeight deliveryProduct.TotalPrice = totalPrice deliveryProduct.DeliveryDate = itemDeliveryDate @@ -498,11 +500,12 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO itemDeliveryDate = deliveryProduct.DeliveryDate } - totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, requestedProduct.Qty, requestedProduct.AvgWeight, requestedProduct.UnitPrice, foundMarketingProduct.Week) + totalWeight, totalPrice := s.resolveDeliveryTotals(marketing.MarketingType, requestedProduct, foundMarketingProduct) deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId deliveryProduct.UnitPrice = requestedProduct.UnitPrice deliveryProduct.AvgWeight = requestedProduct.AvgWeight + deliveryProduct.WeightPerConvertion = requestedProduct.WeightPerConvertion deliveryProduct.TotalWeight = totalWeight deliveryProduct.TotalPrice = totalPrice deliveryProduct.DeliveryDate = itemDeliveryDate @@ -541,20 +544,53 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO return s.getMarketingWithDeliveries(c, id) } -func (s *deliveryOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int) (totalWeight, totalPrice float64) { +func (s *deliveryOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int, convertionUnit *string, _ *float64) (totalWeight, totalPrice float64) { if marketingType == string(utils.MarketingTypeTrading) { totalWeight = 0 - totalPrice = qty * unitPrice + totalPrice = math.Round(qty*unitPrice*100) / 100 } else if marketingType == string(utils.MarketingTypeAyamPullet) && week != nil && *week > 0 { - totalWeight = qty * avgWeight - totalPrice = unitPrice * float64(*week) * qty + totalWeight = math.Round(qty*avgWeight*100) / 100 + totalPrice = math.Round(unitPrice*float64(*week)*qty*100) / 100 } else { - totalWeight = qty * avgWeight - totalPrice = totalWeight * unitPrice + totalWeight = math.Round(qty*avgWeight*100) / 100 + + if marketingType == string(utils.MarketingTypeTelur) && convertionUnit != nil { + switch *convertionUnit { + case string(utils.ConvertionUnitQty): + totalPrice = math.Round(qty*unitPrice*100) / 100 + return totalWeight, totalPrice + case string(utils.ConvertionUnitPeti): + totalPrice = math.Round(totalWeight*unitPrice*100) / 100 + return totalWeight, totalPrice + } + } + + totalPrice = math.Round(totalWeight*unitPrice*100) / 100 } return totalWeight, totalPrice } +func (s *deliveryOrdersService) resolveDeliveryTotals(marketingType string, requestedProduct validation.DeliveryProduct, marketingProduct *entity.MarketingProduct) (totalWeight, totalPrice float64) { + totalWeight, totalPrice = s.calculatePriceByMarketingType( + marketingType, + requestedProduct.Qty, + requestedProduct.AvgWeight, + requestedProduct.UnitPrice, + marketingProduct.Week, + marketingProduct.ConvertionUnit, + marketingProduct.WeightPerConvertion, + ) + + if requestedProduct.TotalWeight != nil { + totalWeight = *requestedProduct.TotalWeight + } + if requestedProduct.TotalPrice != nil { + totalPrice = *requestedProduct.TotalPrice + } + + return totalWeight, totalPrice +} + func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64, actorID uint) error { if marketingProduct == nil || marketingProduct.ProductWarehouseId == 0 { return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found") diff --git a/internal/modules/marketing/services/salesorder.service.go b/internal/modules/marketing/services/salesorder.service.go index 858799df..5f646112 100644 --- a/internal/modules/marketing/services/salesorder.service.go +++ b/internal/modules/marketing/services/salesorder.service.go @@ -815,7 +815,7 @@ func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Cont return nil } -func (s *salesOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int, convertionUnit *string, weightPerConvertion *float64) (totalWeight, totalPrice float64) { +func (s *salesOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int, convertionUnit *string, _ *float64) (totalWeight, totalPrice float64) { if marketingType == string(utils.MarketingTypeTrading) { totalWeight = 0 totalPrice = math.Round(qty*unitPrice*100) / 100 @@ -831,11 +831,8 @@ func (s *salesOrdersService) calculatePriceByMarketingType(marketingType string, totalPrice = math.Round(qty*unitPrice*100) / 100 return totalWeight, totalPrice case string(utils.ConvertionUnitPeti): - if weightPerConvertion != nil && *weightPerConvertion > 0 { - totalPeti := totalWeight / *weightPerConvertion - totalPrice = math.Round(totalPeti*unitPrice*100) / 100 - return totalWeight, totalPrice - } + totalPrice = math.Round(totalWeight*unitPrice*100) / 100 + return totalWeight, totalPrice } } diff --git a/internal/modules/marketing/validations/deliveryorder.validation.go b/internal/modules/marketing/validations/deliveryorder.validation.go index e4687fad..4b7c1328 100644 --- a/internal/modules/marketing/validations/deliveryorder.validation.go +++ b/internal/modules/marketing/validations/deliveryorder.validation.go @@ -1,12 +1,15 @@ package validation type DeliveryProduct struct { - MarketingProductId uint `json:"marketing_product_id" validate:"required,gt=0"` - Qty float64 `json:"qty" validate:"omitempty,gte=0"` - UnitPrice float64 `json:"unit_price" validate:"omitempty,gte=0"` - AvgWeight float64 `json:"avg_weight" validate:"omitempty,gte=0"` - DeliveryDate string `json:"delivery_date" validate:"omitempty,datetime=2006-01-02"` - VehicleNumber string `json:"vehicle_number" validate:"omitempty,max=50"` + MarketingProductId uint `json:"marketing_product_id" validate:"required,gt=0"` + Qty float64 `json:"qty" validate:"omitempty,gte=0"` + UnitPrice float64 `json:"unit_price" validate:"omitempty,gte=0"` + AvgWeight float64 `json:"avg_weight" validate:"omitempty,gte=0"` + WeightPerConvertion *float64 `json:"weight_per_convertion" validate:"omitempty,gt=0"` + TotalWeight *float64 `json:"total_weight" validate:"omitempty,gte=0"` + TotalPrice *float64 `json:"total_price" validate:"omitempty,gte=0"` + DeliveryDate string `json:"delivery_date" validate:"omitempty,datetime=2006-01-02"` + VehicleNumber string `json:"vehicle_number" validate:"omitempty,max=50"` } type DeliveryOrderCreate struct { From 1ab16cfe069f9058e7dc31f805780aaf2fd6d3c7 Mon Sep 17 00:00:00 2001 From: Adnan Zahir Date: Tue, 14 Apr 2026 15:14:31 +0700 Subject: [PATCH 4/8] feat: open API v1 and postman collection --- cmd/api-key/main.go | 132 + cmd/api/main.go | 5 + cmd/generate-read-api/main.go | 74 + docs/openapi/read-api.json | 6850 +++++++++++++++++ docs/openapi/read-api.yaml | 4066 ++++++++++ docs/postman/read-api.collection.json | 1619 ++++ docs/postman/read-api.environment.json | 174 + internal/apikeys/defaults.go | 92 + internal/apikeys/repository.go | 107 + internal/apikeys/service.go | 233 + internal/config/config.go | 6 +- ...90000_create_integration_api_keys.down.sql | 1 + ...4090000_create_integration_api_keys.up.sql | 23 + internal/entities/integration_api_key.go | 36 + internal/middleware/auth.go | 88 +- internal/middleware/auth_apikey_test.go | 239 + internal/readapi/readapi.go | 830 ++ internal/readapi/readapi_test.go | 97 + 18 files changed, 14668 insertions(+), 4 deletions(-) create mode 100644 cmd/api-key/main.go create mode 100644 cmd/generate-read-api/main.go create mode 100644 docs/openapi/read-api.json create mode 100644 docs/openapi/read-api.yaml create mode 100644 docs/postman/read-api.collection.json create mode 100644 docs/postman/read-api.environment.json create mode 100644 internal/apikeys/defaults.go create mode 100644 internal/apikeys/repository.go create mode 100644 internal/apikeys/service.go create mode 100644 internal/database/migrations/20260414090000_create_integration_api_keys.down.sql create mode 100644 internal/database/migrations/20260414090000_create_integration_api_keys.up.sql create mode 100644 internal/entities/integration_api_key.go create mode 100644 internal/middleware/auth_apikey_test.go create mode 100644 internal/readapi/readapi.go create mode 100644 internal/readapi/readapi_test.go diff --git a/cmd/api-key/main.go b/cmd/api-key/main.go new file mode 100644 index 00000000..77b21748 --- /dev/null +++ b/cmd/api-key/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + + "gitlab.com/mbugroup/lti-api.git/internal/apikeys" + "gitlab.com/mbugroup/lti-api.git/internal/config" + "gitlab.com/mbugroup/lti-api.git/internal/database" +) + +func main() { + if len(os.Args) < 2 { + usage() + os.Exit(1) + } + + db := database.Connect(config.DBHost, config.DBName) + service := apikeys.NewService(db) + ctx := context.Background() + + switch os.Args[1] { + case "create": + fs := flag.NewFlagSet("create", flag.ExitOnError) + name := fs.String("name", "dashboard-read-api", "integration client name") + environment := fs.String("env", config.AppEnv, "environment label") + permissions := fs.String("permissions", "", "comma separated permission codes") + allArea := fs.Bool("all-area", true, "grant all areas") + areaIDs := fs.String("area-ids", "", "comma separated area ids") + allLocation := fs.Bool("all-location", true, "grant all locations") + locationIDs := fs.String("location-ids", "", "comma separated location ids") + fs.Parse(os.Args[2:]) + + permissionCodes := apikeys.DefaultDashboardPermissions() + if strings.TrimSpace(*permissions) != "" { + permissionCodes = splitCSV(*permissions) + } + + issued, err := service.Create(ctx, apikeys.CreateInput{ + Name: *name, + Environment: *environment, + PermissionCodes: permissionCodes, + AllArea: *allArea, + AreaIDs: parseUintCSV(*areaIDs), + AllLocation: *allLocation, + LocationIDs: parseUintCSV(*locationIDs), + }) + if err != nil { + panic(err) + } + + fmt.Printf("name: %s\n", issued.Record.Name) + fmt.Printf("environment: %s\n", issued.Record.Environment) + fmt.Printf("prefix: %s\n", issued.Record.KeyPrefix) + fmt.Printf("status: %s\n", issued.Record.Status) + fmt.Printf("api_key: %s\n", issued.Key) + case "list": + fs := flag.NewFlagSet("list", flag.ExitOnError) + environment := fs.String("env", "", "filter by environment") + fs.Parse(os.Args[2:]) + + records, err := service.List(ctx, *environment) + if err != nil { + panic(err) + } + + for _, record := range records { + fmt.Printf("%s\t%s\t%s\t%s\tareas=%t\tlocations=%t\n", + record.Environment, + record.KeyPrefix, + record.Status, + record.Name, + record.AllArea, + record.AllLocation, + ) + } + case "revoke": + fs := flag.NewFlagSet("revoke", flag.ExitOnError) + environment := fs.String("env", config.AppEnv, "environment label") + prefix := fs.String("prefix", "", "key prefix to revoke") + fs.Parse(os.Args[2:]) + + if err := service.Revoke(ctx, *environment, *prefix); err != nil { + panic(err) + } + fmt.Printf("revoked %s/%s\n", *environment, *prefix) + default: + usage() + os.Exit(1) + } +} + +func usage() { + fmt.Println("usage:") + fmt.Println(" go run ./cmd/api-key create [flags]") + fmt.Println(" go run ./cmd/api-key list [flags]") + fmt.Println(" go run ./cmd/api-key revoke -env -prefix ") +} + +func splitCSV(raw string) []string { + if strings.TrimSpace(raw) == "" { + return nil + } + parts := strings.Split(raw, ",") + out := make([]string, 0, len(parts)) + for _, part := range parts { + part = strings.TrimSpace(part) + if part != "" { + out = append(out, part) + } + } + return out +} + +func parseUintCSV(raw string) []uint { + parts := splitCSV(raw) + if len(parts) == 0 { + return nil + } + + values := make([]uint, 0, len(parts)) + for _, part := range parts { + var value uint + if _, err := fmt.Sscanf(part, "%d", &value); err == nil && value > 0 { + values = append(values, value) + } + } + return values +} diff --git a/cmd/api/main.go b/cmd/api/main.go index 76de7729..9b2f6d9b 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -9,12 +9,14 @@ import ( "syscall" "time" + "gitlab.com/mbugroup/lti-api.git/internal/apikeys" "gitlab.com/mbugroup/lti-api.git/internal/cache" "gitlab.com/mbugroup/lti-api.git/internal/config" "gitlab.com/mbugroup/lti-api.git/internal/database" "gitlab.com/mbugroup/lti-api.git/internal/middleware" "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session" sso "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/verifier" + "gitlab.com/mbugroup/lti-api.git/internal/readapi" "gitlab.com/mbugroup/lti-api.git/internal/route" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -131,6 +133,7 @@ func setupDatabase() *gorm.DB { } func setupRoutes(app *fiber.App, db *gorm.DB, rdb *redis.Client) { + middleware.SetAPIKeyAuthenticator(apikeys.NewService(db)) // route.Routes(app, db) // app.Use(utils.NotFoundHandler) @@ -169,6 +172,8 @@ func setupRoutes(app *fiber.App, db *gorm.DB, rdb *redis.Client) { return c.Status(status).JSON(body) }) + readAPIRoutes := app.Group("/api") + readapi.RegisterRoutes(readAPIRoutes) route.Routes(app, db) app.Use(utils.NotFoundHandler) } diff --git a/cmd/generate-read-api/main.go b/cmd/generate-read-api/main.go new file mode 100644 index 00000000..bbce6601 --- /dev/null +++ b/cmd/generate-read-api/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "gitlab.com/mbugroup/lti-api.git/internal/cache" + "gitlab.com/mbugroup/lti-api.git/internal/config" + "gitlab.com/mbugroup/lti-api.git/internal/readapi" + "gitlab.com/mbugroup/lti-api.git/internal/route" + + "github.com/gofiber/fiber/v2" + "github.com/redis/go-redis/v9" +) + +func main() { + root, err := findRepoRoot() + if err != nil { + panic(err) + } + + readapi.PrimeBuildConfig() + cache.SetRedis(redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})) + app := fiber.New(config.FiberConfig()) + app.Get("/healthz", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok", "service": "api", "version": config.Version}) + }) + app.Get("/readyz", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok", "db": "up", "redis": "up"}) + }) + route.Routes(app, nil) + + artifacts, err := readapi.BuildArtifactsFromApp(app) + if err != nil { + panic(err) + } + + files := map[string][]byte{ + filepath.Join(root, "docs", "openapi", "read-api.json"): artifacts.OpenAPIJSON, + filepath.Join(root, "docs", "openapi", "read-api.yaml"): artifacts.OpenAPIYAML, + filepath.Join(root, "docs", "postman", "read-api.collection.json"): artifacts.PostmanCollectionJSON, + filepath.Join(root, "docs", "postman", "read-api.environment.json"): artifacts.PostmanEnvironmentJSON, + } + + for path, body := range files { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + panic(err) + } + if err := os.WriteFile(path, body, 0o644); err != nil { + panic(err) + } + fmt.Printf("wrote %s\n", path) + } +} + +func findRepoRoot() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + + current := wd + for { + if _, err := os.Stat(filepath.Join(current, "go.mod")); err == nil { + return current, nil + } + parent := filepath.Dir(current) + if parent == current { + return "", fmt.Errorf("go.mod not found from %s", wd) + } + current = parent + } +} diff --git a/docs/openapi/read-api.json b/docs/openapi/read-api.json new file mode 100644 index 00000000..b047d432 --- /dev/null +++ b/docs/openapi/read-api.json @@ -0,0 +1,6850 @@ +{ + "components": { + "schemas": { + "ErrorEnvelope": { + "properties": { + "code": { + "example": 401, + "type": "integer" + }, + "errors": { + "additionalProperties": true, + "type": "object" + }, + "message": { + "example": "Please authenticate", + "type": "string" + }, + "status": { + "example": "error", + "type": "string" + } + }, + "type": "object" + }, + "PaginatedEnvelope": { + "properties": { + "code": { + "example": 200, + "type": "integer" + }, + "data": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array" + }, + "message": { + "example": "Request completed successfully", + "type": "string" + }, + "meta": { + "properties": { + "limit": { + "example": 10, + "type": "integer" + }, + "page": { + "example": 1, + "type": "integer" + }, + "total_pages": { + "example": 1, + "type": "integer" + }, + "total_results": { + "example": 0, + "type": "integer" + } + }, + "type": "object" + }, + "status": { + "example": "success", + "type": "string" + } + }, + "type": "object" + }, + "SuccessEnvelope": { + "properties": { + "code": { + "example": 200, + "type": "integer" + }, + "data": { + "additionalProperties": true, + "type": "object" + }, + "message": { + "example": "Request completed successfully", + "type": "string" + }, + "status": { + "example": "success", + "type": "string" + } + }, + "type": "object" + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "in": "header", + "name": "X-API-Key", + "type": "apiKey" + }, + "BearerAuth": { + "scheme": "bearer", + "type": "http" + } + } + }, + "info": { + "description": "Read-only OpenAPI surface for dashboard integrations and GET endpoint exploration.", + "title": "LTI ERP Read API", + "version": "v1" + }, + "openapi": "3.1.0", + "paths": { + "/api/approvals/": { + "get": { + "description": "Read access to `/api/approvals/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / approvals", + "tags": [ + "API" + ] + } + }, + "/api/closings/": { + "get": { + "description": "Read access to `/api/closings/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}/keuangan": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId/keuangan`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId / keuangan", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}/production-data": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId/production-data`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId / production data", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}/sapronak": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId/sapronak`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Required sapronak direction.", + "example": "incoming", + "in": "query", + "name": "type", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Search keyword.", + "example": "pakan", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId / sapronak", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{projectFlockId}/sapronak/summary": { + "get": { + "description": "Read access to `/api/closings/:projectFlockId/sapronak/summary`.", + "parameters": [ + { + "description": "Path parameter `projectFlockId`.", + "in": "path", + "name": "projectFlockId", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Required sapronak direction.", + "example": "incoming", + "in": "query", + "name": "type", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Search keyword.", + "example": "pakan", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :projectFlockId / sapronak / summary", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/expedition-hpp": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/expedition-hpp`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Optional project flock kandang id filter.", + "example": 1, + "in": "query", + "name": "project_flock_kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / expedition hpp", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/overhead": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/overhead`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / overhead", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/penjualan": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/penjualan`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / penjualan", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/perhitungan_sapronak": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/perhitungan_sapronak`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / perhitungan_sapronak", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/expedition-hpp": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/expedition-hpp`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / expedition hpp", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/keuangan": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/keuangan`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / keuangan", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/overhead": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/overhead`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / overhead", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/penjualan": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/penjualan`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / penjualan", + "tags": [ + "Closings" + ] + } + }, + "/api/closings/{project_flock_id}/{project_flock_kandang_id}/perhitungan_sapronak": { + "get": { + "description": "Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + }, + { + "description": "Path parameter `project_flock_kandang_id`.", + "in": "path", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / closings / :project_flock_id / :project_flock_kandang_id / perhitungan_sapronak", + "tags": [ + "Closings" + ] + } + }, + "/api/constants/": { + "get": { + "description": "Read access to `/api/constants/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / constants", + "tags": [ + "API" + ] + } + }, + "/api/daily-checklists/": { + "get": { + "description": "Read access to `/api/daily-checklists/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/phase/{idDailyChecklist}": { + "get": { + "description": "Read access to `/api/daily-checklists/phase/:idDailyChecklist`.", + "parameters": [ + { + "description": "Path parameter `idDailyChecklist`.", + "in": "path", + "name": "idDailyChecklist", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / phase / :idDailyChecklist", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/relation/{idDailyChecklist}": { + "get": { + "description": "Read access to `/api/daily-checklists/relation/:idDailyChecklist`.", + "parameters": [ + { + "description": "Path parameter `idDailyChecklist`.", + "in": "path", + "name": "idDailyChecklist", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / relation / :idDailyChecklist", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/report": { + "get": { + "description": "Read access to `/api/daily-checklists/report`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / report", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/summary": { + "get": { + "description": "Read access to `/api/daily-checklists/summary`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / summary", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/daily-checklists/tasks": { + "get": { + "description": "Read access to `/api/daily-checklists/tasks`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / daily checklists / tasks", + "tags": [ + "Daily Checklists" + ] + } + }, + "/api/dashboards/": { + "get": { + "description": "Read access to `/api/dashboards/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / dashboards", + "tags": [ + "Dashboards" + ] + } + }, + "/api/expenses/": { + "get": { + "description": "Read access to `/api/expenses/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / expenses", + "tags": [ + "Expenses" + ] + } + }, + "/api/expenses/{id}": { + "get": { + "description": "Read access to `/api/expenses/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / expenses / :id", + "tags": [ + "Expenses" + ] + } + }, + "/api/finance/initial-balances/{id}": { + "get": { + "description": "Read access to `/api/finance/initial-balances/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / initial balances / :id", + "tags": [ + "Finance" + ] + } + }, + "/api/finance/injections/{id}": { + "get": { + "description": "Read access to `/api/finance/injections/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / injections / :id", + "tags": [ + "Finance" + ] + } + }, + "/api/finance/payments/{id}": { + "get": { + "description": "Read access to `/api/finance/payments/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / payments / :id", + "tags": [ + "Finance" + ] + } + }, + "/api/finance/transactions/": { + "get": { + "description": "Read access to `/api/finance/transactions/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / transactions", + "tags": [ + "Finance" + ] + } + }, + "/api/finance/transactions/{id}": { + "get": { + "description": "Read access to `/api/finance/transactions/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / finance / transactions / :id", + "tags": [ + "Finance" + ] + } + }, + "/api/inventory/adjustments/": { + "get": { + "description": "Read access to `/api/inventory/adjustments/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / adjustments", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/adjustments/{id}": { + "get": { + "description": "Read access to `/api/inventory/adjustments/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / adjustments / :id", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/product-stocks/": { + "get": { + "description": "Read access to `/api/inventory/product-stocks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / product stocks", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/product-stocks/{id}": { + "get": { + "description": "Read access to `/api/inventory/product-stocks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / product stocks / :id", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/product-warehouses/": { + "get": { + "description": "Read access to `/api/inventory/product-warehouses/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / product warehouses", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/product-warehouses/{id}": { + "get": { + "description": "Read access to `/api/inventory/product-warehouses/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / product warehouses / :id", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/transfers/": { + "get": { + "description": "Read access to `/api/inventory/transfers/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / transfers", + "tags": [ + "Inventory" + ] + } + }, + "/api/inventory/transfers/{id}": { + "get": { + "description": "Read access to `/api/inventory/transfers/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / inventory / transfers / :id", + "tags": [ + "Inventory" + ] + } + }, + "/api/marketing/": { + "get": { + "description": "Read access to `/api/marketing/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / marketing", + "tags": [ + "Marketing" + ] + } + }, + "/api/marketing/{id}": { + "get": { + "description": "Read access to `/api/marketing/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / marketing / :id", + "tags": [ + "Marketing" + ] + } + }, + "/api/master-data/areas/": { + "get": { + "description": "Read access to `/api/master-data/areas/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / areas", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/areas/{id}": { + "get": { + "description": "Read access to `/api/master-data/areas/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / areas / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/banks/": { + "get": { + "description": "Read access to `/api/master-data/banks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / banks", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/banks/{id}": { + "get": { + "description": "Read access to `/api/master-data/banks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / banks / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/config-checklists/": { + "get": { + "description": "Read access to `/api/master-data/config-checklists/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / config checklists", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/config-checklists/{id}": { + "get": { + "description": "Read access to `/api/master-data/config-checklists/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / config checklists / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/customers/": { + "get": { + "description": "Read access to `/api/master-data/customers/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / customers", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/customers/{id}": { + "get": { + "description": "Read access to `/api/master-data/customers/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / customers / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/employees/": { + "get": { + "description": "Read access to `/api/master-data/employees/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / employees", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/employees/{id}": { + "get": { + "description": "Read access to `/api/master-data/employees/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / employees / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/fcrs/": { + "get": { + "description": "Read access to `/api/master-data/fcrs/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / fcrs", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/fcrs/{id}": { + "get": { + "description": "Read access to `/api/master-data/fcrs/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / fcrs / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/flocks/": { + "get": { + "description": "Read access to `/api/master-data/flocks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / flocks", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/flocks/{id}": { + "get": { + "description": "Read access to `/api/master-data/flocks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / flocks / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/kandang-groups/": { + "get": { + "description": "Read access to `/api/master-data/kandang-groups/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / kandang groups", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/kandang-groups/{id}": { + "get": { + "description": "Read access to `/api/master-data/kandang-groups/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / kandang groups / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/kandangs/": { + "get": { + "description": "Read access to `/api/master-data/kandangs/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / kandangs", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/kandangs/{id}": { + "get": { + "description": "Read access to `/api/master-data/kandangs/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / kandangs / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/locations/": { + "get": { + "description": "Read access to `/api/master-data/locations/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / locations", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/locations/{id}": { + "get": { + "description": "Read access to `/api/master-data/locations/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / locations / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/nonstocks/": { + "get": { + "description": "Read access to `/api/master-data/nonstocks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / nonstocks", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/nonstocks/{id}": { + "get": { + "description": "Read access to `/api/master-data/nonstocks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / nonstocks / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/phase-activities/": { + "get": { + "description": "Read access to `/api/master-data/phase-activities/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / phase activities", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/phase-activities/{id}": { + "get": { + "description": "Read access to `/api/master-data/phase-activities/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / phase activities / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/phases/": { + "get": { + "description": "Read access to `/api/master-data/phases/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / phases", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/phases/{id}": { + "get": { + "description": "Read access to `/api/master-data/phases/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / phases / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/product-categories/": { + "get": { + "description": "Read access to `/api/master-data/product-categories/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / product categories", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/product-categories/{id}": { + "get": { + "description": "Read access to `/api/master-data/product-categories/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / product categories / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/production-standards/": { + "get": { + "description": "Read access to `/api/master-data/production-standards/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / production standards", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/production-standards/{id}": { + "get": { + "description": "Read access to `/api/master-data/production-standards/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / production standards / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/products/": { + "get": { + "description": "Read access to `/api/master-data/products/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / products", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/products/{id}": { + "get": { + "description": "Read access to `/api/master-data/products/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / products / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/suppliers/": { + "get": { + "description": "Read access to `/api/master-data/suppliers/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / suppliers", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/suppliers/{id}": { + "get": { + "description": "Read access to `/api/master-data/suppliers/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / suppliers / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/uoms/": { + "get": { + "description": "Read access to `/api/master-data/uoms/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / uoms", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/uoms/{id}": { + "get": { + "description": "Read access to `/api/master-data/uoms/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / uoms / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/warehouses/": { + "get": { + "description": "Read access to `/api/master-data/warehouses/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / warehouses", + "tags": [ + "Master Data" + ] + } + }, + "/api/master-data/warehouses/{id}": { + "get": { + "description": "Read access to `/api/master-data/warehouses/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / master data / warehouses / :id", + "tags": [ + "Master Data" + ] + } + }, + "/api/production/chickins/{id}": { + "get": { + "description": "Read access to `/api/production/chickins/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / chickins / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flock-kandangs/": { + "get": { + "description": "Read access to `/api/production/project-flock-kandangs/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flock kandangs", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flock-kandangs/{id}": { + "get": { + "description": "Read access to `/api/production/project-flock-kandangs/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flock kandangs / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flock-kandangs/{id}/closing/check": { + "get": { + "description": "Read access to `/api/production/project-flock-kandangs/:id/closing/check`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flock kandangs / :id / closing / check", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flocks/": { + "get": { + "description": "Read access to `/api/production/project-flocks/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flocks", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flocks/kandangs/lookup": { + "get": { + "description": "Read access to `/api/production/project-flocks/kandangs/lookup`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flocks / kandangs / lookup", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flocks/locations/{location_id}/periods": { + "get": { + "description": "Read access to `/api/production/project-flocks/locations/:location_id/periods`.", + "parameters": [ + { + "description": "Path parameter `location_id`.", + "in": "path", + "name": "location_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flocks / locations / :location_id / periods", + "tags": [ + "Production" + ] + } + }, + "/api/production/project-flocks/{id}": { + "get": { + "description": "Read access to `/api/production/project-flocks/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / project flocks / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/recordings/": { + "get": { + "description": "Read access to `/api/production/recordings/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / recordings", + "tags": [ + "Production" + ] + } + }, + "/api/production/recordings/next-day": { + "get": { + "description": "Read access to `/api/production/recordings/next-day`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / recordings / next day", + "tags": [ + "Production" + ] + } + }, + "/api/production/recordings/{id}": { + "get": { + "description": "Read access to `/api/production/recordings/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / recordings / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/transfer_layings/": { + "get": { + "description": "Read access to `/api/production/transfer_layings/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / transfer_layings", + "tags": [ + "Production" + ] + } + }, + "/api/production/transfer_layings/project-flocks/{project_flock_id}/available-qty": { + "get": { + "description": "Read access to `/api/production/transfer_layings/project-flocks/:project_flock_id/available-qty`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / transfer_layings / project flocks / :project_flock_id / available qty", + "tags": [ + "Production" + ] + } + }, + "/api/production/transfer_layings/project-flocks/{project_flock_id}/max-target-qty": { + "get": { + "description": "Read access to `/api/production/transfer_layings/project-flocks/:project_flock_id/max-target-qty`.", + "parameters": [ + { + "description": "Path parameter `project_flock_id`.", + "in": "path", + "name": "project_flock_id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / transfer_layings / project flocks / :project_flock_id / max target qty", + "tags": [ + "Production" + ] + } + }, + "/api/production/transfer_layings/{id}": { + "get": { + "description": "Read access to `/api/production/transfer_layings/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / transfer_layings / :id", + "tags": [ + "Production" + ] + } + }, + "/api/production/uniformities/": { + "get": { + "description": "Read access to `/api/production/uniformities/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / uniformities", + "tags": [ + "Production" + ] + } + }, + "/api/production/uniformities/{id}": { + "get": { + "description": "Read access to `/api/production/uniformities/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / production / uniformities / :id", + "tags": [ + "Production" + ] + } + }, + "/api/purchases/": { + "get": { + "description": "Read access to `/api/purchases/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / purchases", + "tags": [ + "Purchases" + ] + } + }, + "/api/purchases/{id}": { + "get": { + "description": "Read access to `/api/purchases/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / purchases / :id", + "tags": [ + "Purchases" + ] + } + }, + "/api/reports/customer-payment": { + "get": { + "description": "Read access to `/api/reports/customer-payment`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated customer ids.", + "example": "1,2", + "in": "query", + "name": "customer_ids", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / customer payment", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/debt-supplier": { + "get": { + "description": "Read access to `/api/reports/debt-supplier`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated supplier ids.", + "example": "1,2", + "in": "query", + "name": "supplier_ids", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / debt supplier", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/expense": { + "get": { + "description": "Read access to `/api/reports/expense`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "operasional", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Expense category filter.", + "example": "BOP", + "in": "query", + "name": "category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Supplier id filter.", + "example": 1, + "in": "query", + "name": "supplier_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Realization date filter (YYYY-MM-DD).", + "example": "2026-01-15", + "in": "query", + "name": "realization_date", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / expense", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/hpp-per-kandang": { + "get": { + "description": "Read access to `/api/reports/hpp-per-kandang`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Daily period filter (YYYY-MM).", + "example": "2026-01", + "in": "query", + "name": "period", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / hpp per kandang", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/marketing": { + "get": { + "description": "Read access to `/api/reports/marketing`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Customer id filter.", + "example": 1, + "in": "query", + "name": "customer_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / marketing", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/production-result/{idProjectFlockKandang}": { + "get": { + "description": "Read access to `/api/reports/production-result/:idProjectFlockKandang`.", + "parameters": [ + { + "description": "Path parameter `idProjectFlockKandang`.", + "in": "path", + "name": "idProjectFlockKandang", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / production result / :idProjectFlockKandang", + "tags": [ + "Reports" + ] + } + }, + "/api/reports/purchase-supplier": { + "get": { + "description": "Read access to `/api/reports/purchase-supplier`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated supplier ids.", + "example": "1,2", + "in": "query", + "name": "supplier_id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated area ids.", + "example": "1,2", + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / reports / purchase supplier", + "tags": [ + "Reports" + ] + } + }, + "/api/sso/callback": { + "get": { + "description": "Read access to `/api/sso/callback`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "GET api / sso / callback", + "tags": [ + "SSO" + ] + } + }, + "/api/sso/master/areas": { + "get": { + "description": "Read access to `/api/sso/master/areas`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "GET api / sso / master / areas", + "tags": [ + "SSO" + ] + } + }, + "/api/sso/master/locations": { + "get": { + "description": "Read access to `/api/sso/master/locations`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "GET api / sso / master / locations", + "tags": [ + "SSO" + ] + } + }, + "/api/sso/start": { + "get": { + "description": "Read access to `/api/sso/start`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "GET api / sso / start", + "tags": [ + "SSO" + ] + } + }, + "/api/sso/userinfo": { + "get": { + "description": "Read access to `/api/sso/userinfo`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "GET api / sso / userinfo", + "tags": [ + "SSO" + ] + } + }, + "/api/users/": { + "get": { + "description": "Read access to `/api/users/`.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / users", + "tags": [ + "Users" + ] + } + }, + "/api/users/{id}": { + "get": { + "description": "Read access to `/api/users/:id`.", + "parameters": [ + { + "description": "Path parameter `id`.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "example": "1", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "summary": "GET api / users / :id", + "tags": [ + "Users" + ] + } + }, + "/healthz": { + "get": { + "description": "Simple liveness probe.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "Health check", + "tags": [ + "System" + ] + } + }, + "/readyz": { + "get": { + "description": "Readiness probe for database and Redis.", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "Readiness check", + "tags": [ + "System" + ] + } + } + }, + "servers": [ + { + "url": "http://localhost:8081" + } + ] +} \ No newline at end of file diff --git a/docs/openapi/read-api.yaml b/docs/openapi/read-api.yaml new file mode 100644 index 00000000..01a4e811 --- /dev/null +++ b/docs/openapi/read-api.yaml @@ -0,0 +1,4066 @@ +components: + schemas: + ErrorEnvelope: + properties: + code: + example: 401 + type: integer + errors: + additionalProperties: true + type: object + message: + example: Please authenticate + type: string + status: + example: error + type: string + type: object + PaginatedEnvelope: + properties: + code: + example: 200 + type: integer + data: + items: + additionalProperties: true + type: object + type: array + message: + example: Request completed successfully + type: string + meta: + properties: + limit: + example: 10 + type: integer + page: + example: 1 + type: integer + total_pages: + example: 1 + type: integer + total_results: + example: 0 + type: integer + type: object + status: + example: success + type: string + type: object + SuccessEnvelope: + properties: + code: + example: 200 + type: integer + data: + additionalProperties: true + type: object + message: + example: Request completed successfully + type: string + status: + example: success + type: string + type: object + securitySchemes: + ApiKeyAuth: + in: header + name: X-API-Key + type: apiKey + BearerAuth: + scheme: bearer + type: http +info: + description: Read-only OpenAPI surface for dashboard integrations and GET endpoint exploration. + title: LTI ERP Read API + version: v1 +openapi: 3.1.0 +paths: + /api/approvals/: + get: + description: Read access to `/api/approvals/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / approvals + tags: + - API + /api/closings/: + get: + description: Read access to `/api/closings/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/expedition-hpp: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/expedition-hpp`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / expedition hpp + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/keuangan: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/keuangan`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / keuangan + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/overhead: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/overhead`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / overhead + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/penjualan: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/penjualan`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / penjualan + tags: + - Closings + /api/closings/{project_flock_id}/{project_flock_kandang_id}/perhitungan_sapronak: + get: + description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Path parameter `project_flock_kandang_id`. + in: path + name: project_flock_kandang_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / :project_flock_kandang_id / perhitungan_sapronak + tags: + - Closings + /api/closings/{project_flock_id}/expedition-hpp: + get: + description: Read access to `/api/closings/:project_flock_id/expedition-hpp`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + - description: Optional project flock kandang id filter. + example: 1 + in: query + name: project_flock_kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / expedition hpp + tags: + - Closings + /api/closings/{project_flock_id}/overhead: + get: + description: Read access to `/api/closings/:project_flock_id/overhead`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / overhead + tags: + - Closings + /api/closings/{project_flock_id}/penjualan: + get: + description: Read access to `/api/closings/:project_flock_id/penjualan`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / penjualan + tags: + - Closings + /api/closings/{project_flock_id}/perhitungan_sapronak: + get: + description: Read access to `/api/closings/:project_flock_id/perhitungan_sapronak`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :project_flock_id / perhitungan_sapronak + tags: + - Closings + /api/closings/{projectFlockId}: + get: + description: Read access to `/api/closings/:projectFlockId`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId + tags: + - Closings + /api/closings/{projectFlockId}/keuangan: + get: + description: Read access to `/api/closings/:projectFlockId/keuangan`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId / keuangan + tags: + - Closings + /api/closings/{projectFlockId}/production-data: + get: + description: Read access to `/api/closings/:projectFlockId/production-data`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId / production data + tags: + - Closings + /api/closings/{projectFlockId}/sapronak: + get: + description: Read access to `/api/closings/:projectFlockId/sapronak`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + - description: Required sapronak direction. + example: incoming + in: query + name: type + required: true + schema: + type: string + - description: Search keyword. + example: pakan + in: query + name: search + required: false + schema: + type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId / sapronak + tags: + - Closings + /api/closings/{projectFlockId}/sapronak/summary: + get: + description: Read access to `/api/closings/:projectFlockId/sapronak/summary`. + parameters: + - description: Path parameter `projectFlockId`. + in: path + name: projectFlockId + required: true + schema: + example: "1" + type: string + - description: Required sapronak direction. + example: incoming + in: query + name: type + required: true + schema: + type: string + - description: Search keyword. + example: pakan + in: query + name: search + required: false + schema: + type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / closings / :projectFlockId / sapronak / summary + tags: + - Closings + /api/constants/: + get: + description: Read access to `/api/constants/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / constants + tags: + - API + /api/daily-checklists/: + get: + description: Read access to `/api/daily-checklists/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists + tags: + - Daily Checklists + /api/daily-checklists/phase/{idDailyChecklist}: + get: + description: Read access to `/api/daily-checklists/phase/:idDailyChecklist`. + parameters: + - description: Path parameter `idDailyChecklist`. + in: path + name: idDailyChecklist + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / phase / :idDailyChecklist + tags: + - Daily Checklists + /api/daily-checklists/relation/{idDailyChecklist}: + get: + description: Read access to `/api/daily-checklists/relation/:idDailyChecklist`. + parameters: + - description: Path parameter `idDailyChecklist`. + in: path + name: idDailyChecklist + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / relation / :idDailyChecklist + tags: + - Daily Checklists + /api/daily-checklists/report: + get: + description: Read access to `/api/daily-checklists/report`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / report + tags: + - Daily Checklists + /api/daily-checklists/summary: + get: + description: Read access to `/api/daily-checklists/summary`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / summary + tags: + - Daily Checklists + /api/daily-checklists/tasks: + get: + description: Read access to `/api/daily-checklists/tasks`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / daily checklists / tasks + tags: + - Daily Checklists + /api/dashboards/: + get: + description: Read access to `/api/dashboards/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / dashboards + tags: + - Dashboards + /api/expenses/: + get: + description: Read access to `/api/expenses/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / expenses + tags: + - Expenses + /api/expenses/{id}: + get: + description: Read access to `/api/expenses/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / expenses / :id + tags: + - Expenses + /api/finance/initial-balances/{id}: + get: + description: Read access to `/api/finance/initial-balances/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / initial balances / :id + tags: + - Finance + /api/finance/injections/{id}: + get: + description: Read access to `/api/finance/injections/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / injections / :id + tags: + - Finance + /api/finance/payments/{id}: + get: + description: Read access to `/api/finance/payments/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / payments / :id + tags: + - Finance + /api/finance/transactions/: + get: + description: Read access to `/api/finance/transactions/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / transactions + tags: + - Finance + /api/finance/transactions/{id}: + get: + description: Read access to `/api/finance/transactions/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / finance / transactions / :id + tags: + - Finance + /api/inventory/adjustments/: + get: + description: Read access to `/api/inventory/adjustments/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / adjustments + tags: + - Inventory + /api/inventory/adjustments/{id}: + get: + description: Read access to `/api/inventory/adjustments/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / adjustments / :id + tags: + - Inventory + /api/inventory/product-stocks/: + get: + description: Read access to `/api/inventory/product-stocks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / product stocks + tags: + - Inventory + /api/inventory/product-stocks/{id}: + get: + description: Read access to `/api/inventory/product-stocks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / product stocks / :id + tags: + - Inventory + /api/inventory/product-warehouses/: + get: + description: Read access to `/api/inventory/product-warehouses/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / product warehouses + tags: + - Inventory + /api/inventory/product-warehouses/{id}: + get: + description: Read access to `/api/inventory/product-warehouses/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / product warehouses / :id + tags: + - Inventory + /api/inventory/transfers/: + get: + description: Read access to `/api/inventory/transfers/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / transfers + tags: + - Inventory + /api/inventory/transfers/{id}: + get: + description: Read access to `/api/inventory/transfers/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / inventory / transfers / :id + tags: + - Inventory + /api/marketing/: + get: + description: Read access to `/api/marketing/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / marketing + tags: + - Marketing + /api/marketing/{id}: + get: + description: Read access to `/api/marketing/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / marketing / :id + tags: + - Marketing + /api/master-data/areas/: + get: + description: Read access to `/api/master-data/areas/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / areas + tags: + - Master Data + /api/master-data/areas/{id}: + get: + description: Read access to `/api/master-data/areas/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / areas / :id + tags: + - Master Data + /api/master-data/banks/: + get: + description: Read access to `/api/master-data/banks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / banks + tags: + - Master Data + /api/master-data/banks/{id}: + get: + description: Read access to `/api/master-data/banks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / banks / :id + tags: + - Master Data + /api/master-data/config-checklists/: + get: + description: Read access to `/api/master-data/config-checklists/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / config checklists + tags: + - Master Data + /api/master-data/config-checklists/{id}: + get: + description: Read access to `/api/master-data/config-checklists/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / config checklists / :id + tags: + - Master Data + /api/master-data/customers/: + get: + description: Read access to `/api/master-data/customers/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / customers + tags: + - Master Data + /api/master-data/customers/{id}: + get: + description: Read access to `/api/master-data/customers/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / customers / :id + tags: + - Master Data + /api/master-data/employees/: + get: + description: Read access to `/api/master-data/employees/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / employees + tags: + - Master Data + /api/master-data/employees/{id}: + get: + description: Read access to `/api/master-data/employees/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / employees / :id + tags: + - Master Data + /api/master-data/fcrs/: + get: + description: Read access to `/api/master-data/fcrs/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / fcrs + tags: + - Master Data + /api/master-data/fcrs/{id}: + get: + description: Read access to `/api/master-data/fcrs/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / fcrs / :id + tags: + - Master Data + /api/master-data/flocks/: + get: + description: Read access to `/api/master-data/flocks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / flocks + tags: + - Master Data + /api/master-data/flocks/{id}: + get: + description: Read access to `/api/master-data/flocks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / flocks / :id + tags: + - Master Data + /api/master-data/kandang-groups/: + get: + description: Read access to `/api/master-data/kandang-groups/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / kandang groups + tags: + - Master Data + /api/master-data/kandang-groups/{id}: + get: + description: Read access to `/api/master-data/kandang-groups/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / kandang groups / :id + tags: + - Master Data + /api/master-data/kandangs/: + get: + description: Read access to `/api/master-data/kandangs/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / kandangs + tags: + - Master Data + /api/master-data/kandangs/{id}: + get: + description: Read access to `/api/master-data/kandangs/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / kandangs / :id + tags: + - Master Data + /api/master-data/locations/: + get: + description: Read access to `/api/master-data/locations/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / locations + tags: + - Master Data + /api/master-data/locations/{id}: + get: + description: Read access to `/api/master-data/locations/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / locations / :id + tags: + - Master Data + /api/master-data/nonstocks/: + get: + description: Read access to `/api/master-data/nonstocks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / nonstocks + tags: + - Master Data + /api/master-data/nonstocks/{id}: + get: + description: Read access to `/api/master-data/nonstocks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / nonstocks / :id + tags: + - Master Data + /api/master-data/phase-activities/: + get: + description: Read access to `/api/master-data/phase-activities/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / phase activities + tags: + - Master Data + /api/master-data/phase-activities/{id}: + get: + description: Read access to `/api/master-data/phase-activities/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / phase activities / :id + tags: + - Master Data + /api/master-data/phases/: + get: + description: Read access to `/api/master-data/phases/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / phases + tags: + - Master Data + /api/master-data/phases/{id}: + get: + description: Read access to `/api/master-data/phases/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / phases / :id + tags: + - Master Data + /api/master-data/product-categories/: + get: + description: Read access to `/api/master-data/product-categories/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / product categories + tags: + - Master Data + /api/master-data/product-categories/{id}: + get: + description: Read access to `/api/master-data/product-categories/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / product categories / :id + tags: + - Master Data + /api/master-data/production-standards/: + get: + description: Read access to `/api/master-data/production-standards/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / production standards + tags: + - Master Data + /api/master-data/production-standards/{id}: + get: + description: Read access to `/api/master-data/production-standards/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / production standards / :id + tags: + - Master Data + /api/master-data/products/: + get: + description: Read access to `/api/master-data/products/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / products + tags: + - Master Data + /api/master-data/products/{id}: + get: + description: Read access to `/api/master-data/products/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / products / :id + tags: + - Master Data + /api/master-data/suppliers/: + get: + description: Read access to `/api/master-data/suppliers/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / suppliers + tags: + - Master Data + /api/master-data/suppliers/{id}: + get: + description: Read access to `/api/master-data/suppliers/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / suppliers / :id + tags: + - Master Data + /api/master-data/uoms/: + get: + description: Read access to `/api/master-data/uoms/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / uoms + tags: + - Master Data + /api/master-data/uoms/{id}: + get: + description: Read access to `/api/master-data/uoms/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / uoms / :id + tags: + - Master Data + /api/master-data/warehouses/: + get: + description: Read access to `/api/master-data/warehouses/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / warehouses + tags: + - Master Data + /api/master-data/warehouses/{id}: + get: + description: Read access to `/api/master-data/warehouses/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / master data / warehouses / :id + tags: + - Master Data + /api/production/chickins/{id}: + get: + description: Read access to `/api/production/chickins/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / chickins / :id + tags: + - Production + /api/production/project-flock-kandangs/: + get: + description: Read access to `/api/production/project-flock-kandangs/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flock kandangs + tags: + - Production + /api/production/project-flock-kandangs/{id}: + get: + description: Read access to `/api/production/project-flock-kandangs/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flock kandangs / :id + tags: + - Production + /api/production/project-flock-kandangs/{id}/closing/check: + get: + description: Read access to `/api/production/project-flock-kandangs/:id/closing/check`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flock kandangs / :id / closing / check + tags: + - Production + /api/production/project-flocks/: + get: + description: Read access to `/api/production/project-flocks/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flocks + tags: + - Production + /api/production/project-flocks/{id}: + get: + description: Read access to `/api/production/project-flocks/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flocks / :id + tags: + - Production + /api/production/project-flocks/kandangs/lookup: + get: + description: Read access to `/api/production/project-flocks/kandangs/lookup`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flocks / kandangs / lookup + tags: + - Production + /api/production/project-flocks/locations/{location_id}/periods: + get: + description: Read access to `/api/production/project-flocks/locations/:location_id/periods`. + parameters: + - description: Path parameter `location_id`. + in: path + name: location_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / project flocks / locations / :location_id / periods + tags: + - Production + /api/production/recordings/: + get: + description: Read access to `/api/production/recordings/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / recordings + tags: + - Production + /api/production/recordings/{id}: + get: + description: Read access to `/api/production/recordings/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / recordings / :id + tags: + - Production + /api/production/recordings/next-day: + get: + description: Read access to `/api/production/recordings/next-day`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / recordings / next day + tags: + - Production + /api/production/transfer_layings/: + get: + description: Read access to `/api/production/transfer_layings/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / transfer_layings + tags: + - Production + /api/production/transfer_layings/{id}: + get: + description: Read access to `/api/production/transfer_layings/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / transfer_layings / :id + tags: + - Production + /api/production/transfer_layings/project-flocks/{project_flock_id}/available-qty: + get: + description: Read access to `/api/production/transfer_layings/project-flocks/:project_flock_id/available-qty`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / transfer_layings / project flocks / :project_flock_id / available qty + tags: + - Production + /api/production/transfer_layings/project-flocks/{project_flock_id}/max-target-qty: + get: + description: Read access to `/api/production/transfer_layings/project-flocks/:project_flock_id/max-target-qty`. + parameters: + - description: Path parameter `project_flock_id`. + in: path + name: project_flock_id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / transfer_layings / project flocks / :project_flock_id / max target qty + tags: + - Production + /api/production/uniformities/: + get: + description: Read access to `/api/production/uniformities/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / uniformities + tags: + - Production + /api/production/uniformities/{id}: + get: + description: Read access to `/api/production/uniformities/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / production / uniformities / :id + tags: + - Production + /api/purchases/: + get: + description: Read access to `/api/purchases/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / purchases + tags: + - Purchases + /api/purchases/{id}: + get: + description: Read access to `/api/purchases/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / purchases / :id + tags: + - Purchases + /api/reports/customer-payment: + get: + description: Read access to `/api/reports/customer-payment`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Comma separated customer ids. + example: 1,2 + in: query + name: customer_ids + required: false + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / customer payment + tags: + - Reports + /api/reports/debt-supplier: + get: + description: Read access to `/api/reports/debt-supplier`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Comma separated supplier ids. + example: 1,2 + in: query + name: supplier_ids + required: false + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / debt supplier + tags: + - Reports + /api/reports/expense: + get: + description: Read access to `/api/reports/expense`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: operasional + in: query + name: search + required: false + schema: + type: string + - description: Expense category filter. + example: BOP + in: query + name: category + required: false + schema: + type: string + - description: Supplier id filter. + example: 1 + in: query + name: supplier_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Realization date filter (YYYY-MM-DD). + example: "2026-01-15" + in: query + name: realization_date + required: false + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / expense + tags: + - Reports + /api/reports/hpp-per-kandang: + get: + description: Read access to `/api/reports/hpp-per-kandang`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Daily period filter (YYYY-MM). + example: 2026-01 + in: query + name: period + required: false + schema: + type: string + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / hpp per kandang + tags: + - Reports + /api/reports/marketing: + get: + description: Read access to `/api/reports/marketing`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Customer id filter. + example: 1 + in: query + name: customer_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / marketing + tags: + - Reports + /api/reports/production-result/{idProjectFlockKandang}: + get: + description: Read access to `/api/reports/production-result/:idProjectFlockKandang`. + parameters: + - description: Path parameter `idProjectFlockKandang`. + in: path + name: idProjectFlockKandang + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / production result / :idProjectFlockKandang + tags: + - Reports + /api/reports/purchase-supplier: + get: + description: Read access to `/api/reports/purchase-supplier`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Comma separated supplier ids. + example: 1,2 + in: query + name: supplier_id + required: false + schema: + type: string + - description: Comma separated area ids. + example: 1,2 + in: query + name: area_id + required: false + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / reports / purchase supplier + tags: + - Reports + /api/sso/callback: + get: + description: Read access to `/api/sso/callback`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: GET api / sso / callback + tags: + - SSO + /api/sso/master/areas: + get: + description: Read access to `/api/sso/master/areas`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: GET api / sso / master / areas + tags: + - SSO + /api/sso/master/locations: + get: + description: Read access to `/api/sso/master/locations`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: GET api / sso / master / locations + tags: + - SSO + /api/sso/start: + get: + description: Read access to `/api/sso/start`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: GET api / sso / start + tags: + - SSO + /api/sso/userinfo: + get: + description: Read access to `/api/sso/userinfo`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - BearerAuth: [] + summary: GET api / sso / userinfo + tags: + - SSO + /api/users/: + get: + description: Read access to `/api/users/`. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / users + tags: + - Users + /api/users/{id}: + get: + description: Read access to `/api/users/:id`. + parameters: + - description: Path parameter `id`. + in: path + name: id + required: true + schema: + example: "1" + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + security: + - ApiKeyAuth: [] + - BearerAuth: [] + summary: GET api / users / :id + tags: + - Users + /healthz: + get: + description: Simple liveness probe. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: Health check + tags: + - System + /readyz: + get: + description: Readiness probe for database and Redis. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessEnvelope' + description: Successful response + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + description: Forbidden + summary: Readiness check + tags: + - System +servers: + - url: http://localhost:8081 diff --git a/docs/postman/read-api.collection.json b/docs/postman/read-api.collection.json new file mode 100644 index 00000000..4b52da17 --- /dev/null +++ b/docs/postman/read-api.collection.json @@ -0,0 +1,1619 @@ +{ + "info": { + "name": "LTI ERP Read API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "item": [ + { + "item": [ + { + "name": "Health check", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/healthz" + } + }, + { + "name": "Readiness check", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/readyz" + } + } + ], + "name": "System" + } + ], + "name": "Public" + }, + { + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const apiKey = pm.environment.get('api_key');", + "const bearerToken = pm.environment.get('bearer_token');", + "if (apiKey) {", + " pm.request.headers.upsert({ key: 'X-API-Key', value: apiKey });", + " pm.request.headers.remove('Authorization');", + "} else if (bearerToken) {", + " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", + " pm.request.headers.remove('X-API-Key');", + "} else {", + " pm.request.headers.remove('Authorization');", + " pm.request.headers.remove('X-API-Key');", + "}" + ], + "type": "text/javascript" + } + } + ], + "item": [ + { + "item": [ + { + "name": "GET api / approvals", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/approvals/" + } + }, + { + "name": "GET api / constants", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/constants/" + } + } + ], + "name": "API" + }, + { + "item": [ + { + "name": "GET api / closings", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/" + } + }, + { + "name": "GET api / closings / :projectFlockId", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}" + } + }, + { + "name": "GET api / closings / :projectFlockId / keuangan", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}/keuangan" + } + }, + { + "name": "GET api / closings / :projectFlockId / production data", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}/production-data" + } + }, + { + "name": "GET api / closings / :projectFlockId / sapronak", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}/sapronak?type=incoming" + } + }, + { + "name": "GET api / closings / :projectFlockId / sapronak / summary", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{projectFlockId}}/sapronak/summary?type=incoming" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / expedition hpp", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/expedition-hpp" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / keuangan", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/keuangan" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / overhead", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/overhead" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / penjualan", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/penjualan" + } + }, + { + "name": "GET api / closings / :project_flock_id / :project_flock_kandang_id / perhitungan_sapronak", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/perhitungan_sapronak" + } + }, + { + "name": "GET api / closings / :project_flock_id / expedition hpp", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/expedition-hpp" + } + }, + { + "name": "GET api / closings / :project_flock_id / overhead", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/overhead" + } + }, + { + "name": "GET api / closings / :project_flock_id / penjualan", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/penjualan" + } + }, + { + "name": "GET api / closings / :project_flock_id / perhitungan_sapronak", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/closings/{{project_flock_id}}/perhitungan_sapronak" + } + } + ], + "name": "Closings" + }, + { + "item": [ + { + "name": "GET api / daily checklists", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/" + } + }, + { + "name": "GET api / daily checklists / phase / :idDailyChecklist", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/phase/{{idDailyChecklist}}" + } + }, + { + "name": "GET api / daily checklists / relation / :idDailyChecklist", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/relation/{{idDailyChecklist}}" + } + }, + { + "name": "GET api / daily checklists / report", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/report" + } + }, + { + "name": "GET api / daily checklists / summary", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/summary" + } + }, + { + "name": "GET api / daily checklists / tasks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/daily-checklists/tasks" + } + } + ], + "name": "Daily Checklists" + }, + { + "item": [ + { + "name": "GET api / dashboards", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/dashboards/" + } + } + ], + "name": "Dashboards" + }, + { + "item": [ + { + "name": "GET api / expenses", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/expenses/" + } + }, + { + "name": "GET api / expenses / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/expenses/{{expense_id}}" + } + } + ], + "name": "Expenses" + }, + { + "item": [ + { + "name": "GET api / finance / initial balances / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/initial-balances/{{initial_balance_id}}" + } + }, + { + "name": "GET api / finance / injections / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/injections/{{injection_id}}" + } + }, + { + "name": "GET api / finance / payments / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/payments/{{payment_id}}" + } + }, + { + "name": "GET api / finance / transactions", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/transactions/" + } + }, + { + "name": "GET api / finance / transactions / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/finance/transactions/{{transaction_id}}" + } + } + ], + "name": "Finance" + }, + { + "item": [ + { + "name": "GET api / inventory / adjustments", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/adjustments/" + } + }, + { + "name": "GET api / inventory / adjustments / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/adjustments/{{adjustment_id}}" + } + }, + { + "name": "GET api / inventory / product stocks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/product-stocks/" + } + }, + { + "name": "GET api / inventory / product stocks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/product-stocks/{{id}}" + } + }, + { + "name": "GET api / inventory / product warehouses", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/product-warehouses/" + } + }, + { + "name": "GET api / inventory / product warehouses / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/product-warehouses/{{id}}" + } + }, + { + "name": "GET api / inventory / transfers", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/transfers/" + } + }, + { + "name": "GET api / inventory / transfers / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/inventory/transfers/{{transfer_id}}" + } + } + ], + "name": "Inventory" + }, + { + "item": [ + { + "name": "GET api / marketing", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/marketing/" + } + }, + { + "name": "GET api / marketing / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/marketing/{{id}}" + } + } + ], + "name": "Marketing" + }, + { + "item": [ + { + "name": "GET api / master data / areas", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/areas/" + } + }, + { + "name": "GET api / master data / areas / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/areas/{{area_id}}" + } + }, + { + "name": "GET api / master data / banks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/banks/" + } + }, + { + "name": "GET api / master data / banks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/banks/{{bank_id}}" + } + }, + { + "name": "GET api / master data / config checklists", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/config-checklists/" + } + }, + { + "name": "GET api / master data / config checklists / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/config-checklists/{{id}}" + } + }, + { + "name": "GET api / master data / customers", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/customers/" + } + }, + { + "name": "GET api / master data / customers / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/customers/{{customer_id}}" + } + }, + { + "name": "GET api / master data / employees", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/employees/" + } + }, + { + "name": "GET api / master data / employees / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/employees/{{employee_id}}" + } + }, + { + "name": "GET api / master data / fcrs", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/fcrs/" + } + }, + { + "name": "GET api / master data / fcrs / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/fcrs/{{id}}" + } + }, + { + "name": "GET api / master data / flocks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/flocks/" + } + }, + { + "name": "GET api / master data / flocks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/flocks/{{flock_id}}" + } + }, + { + "name": "GET api / master data / kandang groups", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/kandang-groups/" + } + }, + { + "name": "GET api / master data / kandang groups / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/kandang-groups/{{id}}" + } + }, + { + "name": "GET api / master data / kandangs", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/kandangs/" + } + }, + { + "name": "GET api / master data / kandangs / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/kandangs/{{id}}" + } + }, + { + "name": "GET api / master data / locations", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/locations/" + } + }, + { + "name": "GET api / master data / locations / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/locations/{{location_id}}" + } + }, + { + "name": "GET api / master data / nonstocks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/nonstocks/" + } + }, + { + "name": "GET api / master data / nonstocks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/nonstocks/{{nonstock_id}}" + } + }, + { + "name": "GET api / master data / phase activities", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/phase-activities/" + } + }, + { + "name": "GET api / master data / phase activities / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/phase-activities/{{id}}" + } + }, + { + "name": "GET api / master data / phases", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/phases/" + } + }, + { + "name": "GET api / master data / phases / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/phases/{{id}}" + } + }, + { + "name": "GET api / master data / product categories", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/product-categories/" + } + }, + { + "name": "GET api / master data / product categories / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/product-categories/{{product_category_id}}" + } + }, + { + "name": "GET api / master data / production standards", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/production-standards/" + } + }, + { + "name": "GET api / master data / production standards / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/production-standards/{{id}}" + } + }, + { + "name": "GET api / master data / products", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/products/" + } + }, + { + "name": "GET api / master data / products / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/products/{{product_id}}" + } + }, + { + "name": "GET api / master data / suppliers", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/suppliers/" + } + }, + { + "name": "GET api / master data / suppliers / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/suppliers/{{supplier_id}}" + } + }, + { + "name": "GET api / master data / uoms", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/uoms/" + } + }, + { + "name": "GET api / master data / uoms / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/uoms/{{uom_id}}" + } + }, + { + "name": "GET api / master data / warehouses", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/warehouses/" + } + }, + { + "name": "GET api / master data / warehouses / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/master-data/warehouses/{{warehouse_id}}" + } + } + ], + "name": "Master Data" + }, + { + "item": [ + { + "name": "GET api / production / chickins / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/chickins/{{chickin_id}}" + } + }, + { + "name": "GET api / production / project flock kandangs", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flock-kandangs/" + } + }, + { + "name": "GET api / production / project flock kandangs / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flock-kandangs/{{id}}" + } + }, + { + "name": "GET api / production / project flock kandangs / :id / closing / check", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flock-kandangs/{{id}}/closing/check" + } + }, + { + "name": "GET api / production / project flocks", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flocks/" + } + }, + { + "name": "GET api / production / project flocks / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flocks/{{id}}" + } + }, + { + "name": "GET api / production / project flocks / kandangs / lookup", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flocks/kandangs/lookup" + } + }, + { + "name": "GET api / production / project flocks / locations / :location_id / periods", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/project-flocks/locations/{{location_id}}/periods" + } + }, + { + "name": "GET api / production / recordings", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/recordings/" + } + }, + { + "name": "GET api / production / recordings / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/recordings/{{recording_id}}" + } + }, + { + "name": "GET api / production / recordings / next day", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/recordings/next-day" + } + }, + { + "name": "GET api / production / transfer_layings", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/transfer_layings/" + } + }, + { + "name": "GET api / production / transfer_layings / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/transfer_layings/{{id}}" + } + }, + { + "name": "GET api / production / transfer_layings / project flocks / :project_flock_id / available qty", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/transfer_layings/project-flocks/{{project_flock_id}}/available-qty" + } + }, + { + "name": "GET api / production / transfer_layings / project flocks / :project_flock_id / max target qty", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/transfer_layings/project-flocks/{{project_flock_id}}/max-target-qty" + } + }, + { + "name": "GET api / production / uniformities", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/uniformities/" + } + }, + { + "name": "GET api / production / uniformities / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/production/uniformities/{{uniformity_id}}" + } + } + ], + "name": "Production" + }, + { + "item": [ + { + "name": "GET api / purchases", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/purchases/" + } + }, + { + "name": "GET api / purchases / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/purchases/{{purchase_id}}" + } + } + ], + "name": "Purchases" + }, + { + "item": [ + { + "name": "GET api / reports / customer payment", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/customer-payment?customer_ids={{customer_id}}" + } + }, + { + "name": "GET api / reports / debt supplier", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/debt-supplier?supplier_ids={{supplier_id}}" + } + }, + { + "name": "GET api / reports / expense", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/expense" + } + }, + { + "name": "GET api / reports / hpp per kandang", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/hpp-per-kandang" + } + }, + { + "name": "GET api / reports / marketing", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/marketing" + } + }, + { + "name": "GET api / reports / production result / :idProjectFlockKandang", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/production-result/{{idProjectFlockKandang}}" + } + }, + { + "name": "GET api / reports / purchase supplier", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/reports/purchase-supplier" + } + } + ], + "name": "Reports" + }, + { + "item": [ + { + "name": "GET api / users", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/users/" + } + }, + { + "name": "GET api / users / :id", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/users/{{user_id}}" + } + } + ], + "name": "Users" + } + ], + "name": "Dashboard API Key" + }, + { + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const bearerToken = pm.environment.get('bearer_token');", + "pm.request.headers.remove('X-API-Key');", + "if (bearerToken) {", + " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", + "} else {", + " pm.request.headers.remove('Authorization');", + "}" + ], + "type": "text/javascript" + } + } + ], + "item": [ + { + "item": [ + { + "name": "GET api / sso / callback", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/callback" + } + }, + { + "name": "GET api / sso / master / areas", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/master/areas" + } + }, + { + "name": "GET api / sso / master / locations", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/master/locations" + } + }, + { + "name": "GET api / sso / start", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/start" + } + }, + { + "name": "GET api / sso / userinfo", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/sso/userinfo" + } + } + ], + "name": "SSO" + } + ], + "name": "Internal/OAuth Reference" + } + ] +} \ No newline at end of file diff --git a/docs/postman/read-api.environment.json b/docs/postman/read-api.environment.json new file mode 100644 index 00000000..2eba4d52 --- /dev/null +++ b/docs/postman/read-api.environment.json @@ -0,0 +1,174 @@ +{ + "_postman_exported_at": "2026-04-14T00:00:00Z", + "_postman_exported_using": "Codex", + "_postman_variable_scope": "environment", + "id": "lti-read-api-local", + "name": "LTI ERP Read API.local", + "values": [ + { + "enabled": true, + "key": "adjustment_id", + "value": "1" + }, + { + "enabled": true, + "key": "api_key", + "value": "" + }, + { + "enabled": true, + "key": "area_id", + "value": "1" + }, + { + "enabled": true, + "key": "bank_id", + "value": "1" + }, + { + "enabled": true, + "key": "base_url", + "value": "http://localhost:8081" + }, + { + "enabled": true, + "key": "bearer_token", + "value": "" + }, + { + "enabled": true, + "key": "chickin_id", + "value": "1" + }, + { + "enabled": true, + "key": "customer_id", + "value": "1" + }, + { + "enabled": true, + "key": "employee_id", + "value": "1" + }, + { + "enabled": true, + "key": "expense_id", + "value": "1" + }, + { + "enabled": true, + "key": "flock_id", + "value": "1" + }, + { + "enabled": true, + "key": "id", + "value": "1" + }, + { + "enabled": true, + "key": "idDailyChecklist", + "value": "1" + }, + { + "enabled": true, + "key": "idProjectFlockKandang", + "value": "1" + }, + { + "enabled": true, + "key": "initial_balance_id", + "value": "1" + }, + { + "enabled": true, + "key": "injection_id", + "value": "1" + }, + { + "enabled": true, + "key": "location_id", + "value": "1" + }, + { + "enabled": true, + "key": "nonstock_id", + "value": "1" + }, + { + "enabled": true, + "key": "payment_id", + "value": "1" + }, + { + "enabled": true, + "key": "product_category_id", + "value": "1" + }, + { + "enabled": true, + "key": "product_id", + "value": "1" + }, + { + "enabled": true, + "key": "projectFlockId", + "value": "1" + }, + { + "enabled": true, + "key": "project_flock_id", + "value": "1" + }, + { + "enabled": true, + "key": "project_flock_kandang_id", + "value": "1" + }, + { + "enabled": true, + "key": "purchase_id", + "value": "1" + }, + { + "enabled": true, + "key": "recording_id", + "value": "1" + }, + { + "enabled": true, + "key": "supplier_id", + "value": "1" + }, + { + "enabled": true, + "key": "transaction_id", + "value": "1" + }, + { + "enabled": true, + "key": "transfer_id", + "value": "1" + }, + { + "enabled": true, + "key": "uniformity_id", + "value": "1" + }, + { + "enabled": true, + "key": "uom_id", + "value": "1" + }, + { + "enabled": true, + "key": "user_id", + "value": "1" + }, + { + "enabled": true, + "key": "warehouse_id", + "value": "1" + } + ] +} \ No newline at end of file diff --git a/internal/apikeys/defaults.go b/internal/apikeys/defaults.go new file mode 100644 index 00000000..29daeda5 --- /dev/null +++ b/internal/apikeys/defaults.go @@ -0,0 +1,92 @@ +package apikeys + +func DefaultDashboardPermissions() []string { + return []string{ + "lti.approval.list", + "lti.closing.list", + "lti.closing.detail", + "lti.daily_checklist.create", + "lti.daily_checklist.dashboard.list", + "lti.daily_checklist.detail", + "lti.daily_checklist.list", + "lti.daily_checklist.master_data.activity", + "lti.daily_checklist.master_data.configuration", + "lti.daily_checklist.master_data.employee", + "lti.daily_checklist.reports", + "lti.dashboard.list", + "lti.expense.detail", + "lti.expense.list", + "lti.finance.initial_balances.detail", + "lti.finance.injections.detail", + "lti.finance.payments.detail", + "lti.finance.transactions.detail", + "lti.finance.transactions.list", + "lti.inventory.detail", + "lti.inventory.list", + "lti.inventory.product_stock.detail", + "lti.inventory.product_stock.list", + "lti.inventory.product_warehouses.detail", + "lti.inventory.product_warehouses.list", + "lti.inventory.transfer.detail", + "lti.inventory.transfer.list", + "lti.marketing.delivery_order.detail", + "lti.marketing.delivery_order.list", + "lti.master.area.detail", + "lti.master.area.list", + "lti.master.banks.detail", + "lti.master.banks.list", + "lti.master.customer.detail", + "lti.master.customer.list", + "lti.master.fcr.detail", + "lti.master.fcr.list", + "lti.master.flocks.detail", + "lti.master.flocks.list", + "lti.master.kandangs.detail", + "lti.master.kandangs.list", + "lti.master.locations.detail", + "lti.master.locations.list", + "lti.master.nonstocks.detail", + "lti.master.nonstocks.list", + "lti.master.product_categories.detail", + "lti.master.product_categories.list", + "lti.master.products.detail", + "lti.master.products.list", + "lti.master.production_standards.detail", + "lti.master.production_standards.list", + "lti.master.suppliers.detail", + "lti.master.suppliers.list", + "lti.master.uoms.detail", + "lti.master.uoms.list", + "lti.master.warehouses.detail", + "lti.master.warehouses.list", + "lti.production.chickins.detail", + "lti.production.project_flock_kandangs.closing.detail", + "lti.production.project_flock_kandangs.detail", + "lti.production.project_flock_kandangs.list", + "lti.production.project_flocks.detail", + "lti.production.project_flocks.list", + "lti.production.project_flocks.lookup", + "lti.production.project_flocks.next_period", + "lti.production.recording.detail", + "lti.production.recording.list", + "lti.production.recording.next_day", + "lti.production.transfer_to_laying.create", + "lti.production.transfer_to_laying.detail", + "lti.production.transfer_to_laying.getavailableqty", + "lti.production.transfer_to_laying.list", + "lti.production.uniformity.detail", + "lti.production.uniformity.list", + "lti.purchase.detail", + "lti.purchase.list", + "lti.repport.customerpayment.list", + "lti.repport.debtsupplier.list", + "lti.repport.delivery.list", + "lti.repport.expense.list", + "lti.repport.gethppperkandang.list", + "lti.repport.production_result.list", + "lti.repport.purchasesupplier.list", + "lti.users.detail", + "lti.users.list", + "lti.daily_checklist.master_data.kandang", + } +} diff --git a/internal/apikeys/repository.go b/internal/apikeys/repository.go new file mode 100644 index 00000000..7348d7c5 --- /dev/null +++ b/internal/apikeys/repository.go @@ -0,0 +1,107 @@ +package apikeys + +import ( + "context" + "errors" + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type Repository interface { + Create(ctx context.Context, record *entity.IntegrationAPIKey) error + GetByEnvironmentAndPrefix(ctx context.Context, environment, prefix string) (*entity.IntegrationAPIKey, error) + List(ctx context.Context, environment string) ([]entity.IntegrationAPIKey, error) + Revoke(ctx context.Context, environment, prefix string, revokedAt time.Time) error + TouchLastUsed(ctx context.Context, id uint, usedAt time.Time, usedFrom string) error +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) Create(ctx context.Context, record *entity.IntegrationAPIKey) error { + if r.db == nil { + return errors.New("database not configured") + } + return r.db.WithContext(ctx).Create(record).Error +} + +func (r *repository) GetByEnvironmentAndPrefix(ctx context.Context, environment, prefix string) (*entity.IntegrationAPIKey, error) { + if r.db == nil { + return nil, errors.New("database not configured") + } + + var record entity.IntegrationAPIKey + if err := r.db.WithContext(ctx). + Where("environment = ?", environment). + Where("key_prefix = ?", prefix). + First(&record).Error; err != nil { + return nil, err + } + + return &record, nil +} + +func (r *repository) List(ctx context.Context, environment string) ([]entity.IntegrationAPIKey, error) { + if r.db == nil { + return nil, errors.New("database not configured") + } + + query := r.db.WithContext(ctx).Model(&entity.IntegrationAPIKey{}) + if environment != "" { + query = query.Where("environment = ?", environment) + } + + var records []entity.IntegrationAPIKey + if err := query.Order("environment ASC").Order("name ASC").Find(&records).Error; err != nil { + return nil, err + } + + return records, nil +} + +func (r *repository) Revoke(ctx context.Context, environment, prefix string, revokedAt time.Time) error { + if r.db == nil { + return errors.New("database not configured") + } + + updates := map[string]any{ + "status": entity.IntegrationAPIKeyStatusRevoked, + "revoked_at": revokedAt, + "updated_at": revokedAt, + } + + result := r.db.WithContext(ctx). + Model(&entity.IntegrationAPIKey{}). + Where("environment = ?", environment). + Where("key_prefix = ?", prefix). + Updates(updates) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +func (r *repository) TouchLastUsed(ctx context.Context, id uint, usedAt time.Time, usedFrom string) error { + if r.db == nil { + return errors.New("database not configured") + } + + return r.db.WithContext(ctx). + Model(&entity.IntegrationAPIKey{}). + Where("id = ?", id). + Updates(map[string]any{ + "last_used_at": usedAt, + "last_used_from": usedFrom, + "updated_at": usedAt, + }).Error +} diff --git a/internal/apikeys/service.go b/internal/apikeys/service.go new file mode 100644 index 00000000..5059b18a --- /dev/null +++ b/internal/apikeys/service.go @@ -0,0 +1,233 @@ +package apikeys + +import ( + "context" + "crypto/rand" + "encoding/base32" + "errors" + "fmt" + "strings" + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gitlab.com/mbugroup/lti-api.git/internal/utils" + "gitlab.com/mbugroup/lti-api.git/internal/utils/secure" + "gorm.io/gorm" +) + +var ( + ErrInvalidAPIKey = errors.New("invalid api key") + ErrInactiveKey = errors.New("inactive api key") +) + +type Principal struct { + ID uint + Name string + Environment string + Permissions []string + AllArea bool + AreaIDs []uint + AllLocation bool + LocationIDs []uint +} + +type Authenticator interface { + Authenticate(ctx context.Context, rawKey, source string) (*Principal, error) +} + +type Service interface { + Authenticator + Create(ctx context.Context, input CreateInput) (*IssuedKey, error) + List(ctx context.Context, environment string) ([]entity.IntegrationAPIKey, error) + Revoke(ctx context.Context, environment, prefix string) error +} + +type CreateInput struct { + Name string + Environment string + PermissionCodes []string + AllArea bool + AreaIDs []uint + AllLocation bool + LocationIDs []uint +} + +type IssuedKey struct { + Key string + Record *entity.IntegrationAPIKey +} + +type service struct { + repo Repository + now func() time.Time +} + +func NewService(db *gorm.DB) Service { + return &service{ + repo: NewRepository(db), + now: time.Now, + } +} + +func (s *service) Authenticate(ctx context.Context, rawKey, source string) (*Principal, error) { + environment, prefix, secret, err := parseRawKey(rawKey) + if err != nil { + return nil, ErrInvalidAPIKey + } + + record, err := s.repo.GetByEnvironmentAndPrefix(ctx, environment, prefix) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrInvalidAPIKey + } + return nil, err + } + + if !strings.EqualFold(record.Status, entity.IntegrationAPIKeyStatusActive) || record.RevokedAt != nil { + return nil, ErrInactiveKey + } + if !secure.Verify(record.KeyHash, secret) { + return nil, ErrInvalidAPIKey + } + + usedAt := s.now().UTC() + if err := s.repo.TouchLastUsed(ctx, record.ID, usedAt, strings.TrimSpace(source)); err != nil { + utils.Log.WithError(err).Warn("api key: failed to update last_used fields") + } + + return &Principal{ + ID: record.ID, + Name: record.Name, + Environment: record.Environment, + Permissions: canonicalPermissions(record.PermissionCodes), + AllArea: record.AllArea, + AreaIDs: uniqueUint(record.AreaIDs), + AllLocation: record.AllLocation, + LocationIDs: uniqueUint(record.LocationIDs), + }, nil +} + +func (s *service) Create(ctx context.Context, input CreateInput) (*IssuedKey, error) { + name := strings.TrimSpace(input.Name) + environment := strings.ToLower(strings.TrimSpace(input.Environment)) + if name == "" || environment == "" { + return nil, fmt.Errorf("name and environment are required") + } + + prefix, err := randomToken(10) + if err != nil { + return nil, err + } + secret, err := randomToken(24) + if err != nil { + return nil, err + } + + hash, err := secure.Hash(secret, nil) + if err != nil { + return nil, err + } + + record := &entity.IntegrationAPIKey{ + Name: name, + Environment: environment, + Status: entity.IntegrationAPIKeyStatusActive, + KeyPrefix: prefix, + KeyHash: hash, + PermissionCodes: canonicalPermissions(input.PermissionCodes), + AllArea: input.AllArea, + AreaIDs: uniqueUint(input.AreaIDs), + AllLocation: input.AllLocation, + LocationIDs: uniqueUint(input.LocationIDs), + } + + if err := s.repo.Create(ctx, record); err != nil { + return nil, err + } + + return &IssuedKey{ + Key: fmt.Sprintf("lti_%s_%s_%s", environment, prefix, secret), + Record: record, + }, nil +} + +func (s *service) List(ctx context.Context, environment string) ([]entity.IntegrationAPIKey, error) { + return s.repo.List(ctx, strings.ToLower(strings.TrimSpace(environment))) +} + +func (s *service) Revoke(ctx context.Context, environment, prefix string) error { + environment = strings.ToLower(strings.TrimSpace(environment)) + prefix = strings.TrimSpace(prefix) + if environment == "" || prefix == "" { + return fmt.Errorf("environment and prefix are required") + } + return s.repo.Revoke(ctx, environment, prefix, s.now().UTC()) +} + +func parseRawKey(rawKey string) (environment string, prefix string, secret string, err error) { + rawKey = strings.TrimSpace(rawKey) + parts := strings.Split(rawKey, "_") + if len(parts) != 4 || parts[0] != "lti" { + return "", "", "", ErrInvalidAPIKey + } + + environment = strings.ToLower(strings.TrimSpace(parts[1])) + prefix = strings.TrimSpace(parts[2]) + secret = strings.TrimSpace(parts[3]) + if environment == "" || prefix == "" || secret == "" { + return "", "", "", ErrInvalidAPIKey + } + + return environment, prefix, secret, nil +} + +func randomToken(size int) (string, error) { + buf := make([]byte, size) + if _, err := rand.Read(buf); err != nil { + return "", err + } + + encoder := base32.StdEncoding.WithPadding(base32.NoPadding) + return strings.ToLower(encoder.EncodeToString(buf)), nil +} + +func canonicalPermissions(perms []string) []string { + if len(perms) == 0 { + return nil + } + + seen := make(map[string]struct{}, len(perms)) + result := make([]string, 0, len(perms)) + for _, perm := range perms { + perm = strings.ToLower(strings.TrimSpace(perm)) + if perm == "" { + continue + } + if _, ok := seen[perm]; ok { + continue + } + seen[perm] = struct{}{} + result = append(result, perm) + } + return result +} + +func uniqueUint(values []uint) []uint { + if len(values) == 0 { + return nil + } + + seen := make(map[uint]struct{}, len(values)) + result := make([]uint, 0, len(values)) + for _, value := range values { + if value == 0 { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + return result +} diff --git a/internal/config/config.go b/internal/config/config.go index 040e0fdd..2a5d640e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ type SSOClientConfig struct { var ( IsProd bool + AppEnv string AppHost string Version string LogLevel string @@ -84,7 +85,8 @@ func init() { loadConfig() // server configuration - IsProd = viper.GetString("APP_ENV") == "prod" + AppEnv = defaultString(strings.TrimSpace(viper.GetString("APP_ENV")), "development") + IsProd = AppEnv == "prod" AppHost = viper.GetString("APP_HOST") AppPort = viper.GetInt("APP_PORT") Version = viper.GetString("VERSION") @@ -111,7 +113,7 @@ func init() { // Cors CORSAllowOrigins = parseList("CORS_ALLOW_ORIGINS") CORSAllowMethods = parseListWithDefault("CORS_ALLOW_METHODS", "GET,POST,PUT,PATCH,DELETE,OPTIONS") - CORSAllowHeaders = parseListWithDefault("CORS_ALLOW_HEADERS", "Content-Type,Authorization,X-Requested-With") + CORSAllowHeaders = parseListWithDefault("CORS_ALLOW_HEADERS", "Content-Type,Authorization,X-API-Key,X-Requested-With") CORSExposeHeaders = parseList("CORS_EXPOSE_HEADERS") CORSAllowCredentials = viper.GetBool("CORS_ALLOW_CREDENTIALS") CORSMaxAge = viper.GetInt("CORS_MAX_AGE") diff --git a/internal/database/migrations/20260414090000_create_integration_api_keys.down.sql b/internal/database/migrations/20260414090000_create_integration_api_keys.down.sql new file mode 100644 index 00000000..874026a5 --- /dev/null +++ b/internal/database/migrations/20260414090000_create_integration_api_keys.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS integration_api_keys; diff --git a/internal/database/migrations/20260414090000_create_integration_api_keys.up.sql b/internal/database/migrations/20260414090000_create_integration_api_keys.up.sql new file mode 100644 index 00000000..ce18a84f --- /dev/null +++ b/internal/database/migrations/20260414090000_create_integration_api_keys.up.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS integration_api_keys ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + environment VARCHAR(50) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'active', + key_prefix VARCHAR(64) NOT NULL, + key_hash TEXT NOT NULL, + permission_codes JSONB NOT NULL DEFAULT '[]'::jsonb, + all_area BOOLEAN NOT NULL DEFAULT FALSE, + area_ids JSONB NOT NULL DEFAULT '[]'::jsonb, + all_location BOOLEAN NOT NULL DEFAULT FALSE, + location_ids JSONB NOT NULL DEFAULT '[]'::jsonb, + last_used_at TIMESTAMPTZ NULL, + last_used_from VARCHAR(128) NULL, + revoked_at TIMESTAMPTZ NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ NULL, + CONSTRAINT uq_integration_api_keys_environment_prefix UNIQUE (environment, key_prefix) +); + +CREATE INDEX idx_integration_api_keys_status ON integration_api_keys (status); +CREATE INDEX idx_integration_api_keys_deleted_at ON integration_api_keys (deleted_at); diff --git a/internal/entities/integration_api_key.go b/internal/entities/integration_api_key.go new file mode 100644 index 00000000..dbaecdc6 --- /dev/null +++ b/internal/entities/integration_api_key.go @@ -0,0 +1,36 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +const ( + IntegrationAPIKeyStatusActive = "active" + IntegrationAPIKeyStatusRevoked = "revoked" +) + +type IntegrationAPIKey struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"type:varchar(100);not null"` + Environment string `gorm:"type:varchar(50);not null;uniqueIndex:idx_integration_api_keys_env_prefix,priority:1"` + Status string `gorm:"type:varchar(20);not null;default:active;index"` + KeyPrefix string `gorm:"type:varchar(64);not null;uniqueIndex:idx_integration_api_keys_env_prefix,priority:2"` + KeyHash string `gorm:"type:text;not null"` + PermissionCodes []string `gorm:"type:jsonb;serializer:json;not null"` + AllArea bool `gorm:"not null;default:false"` + AreaIDs []uint `gorm:"type:jsonb;serializer:json;not null"` + AllLocation bool `gorm:"not null;default:false"` + LocationIDs []uint `gorm:"type:jsonb;serializer:json;not null"` + LastUsedAt *time.Time + LastUsedFrom string `gorm:"type:varchar(128)"` + RevokedAt *time.Time + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (IntegrationAPIKey) TableName() string { + return "integration_api_keys" +} diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index e7640e7b..eddb7afe 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -1,9 +1,13 @@ package middleware import ( + "context" + "errors" "strings" + "sync" "github.com/gofiber/fiber/v2" + "gitlab.com/mbugroup/lti-api.git/internal/apikeys" "gitlab.com/mbugroup/lti-api.git/internal/config" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session" @@ -17,11 +21,21 @@ const ( authUserLocalsKey = "auth.user" ) +var ( + verifyAccessTokenFunc = sso.VerifyAccessToken + fetchProfileFunc = sso.FetchProfile + + apiKeyAuthMu sync.RWMutex + apiKeyAuthenticator apikeys.Authenticator +) + // AuthContext keeps authentication details captured by the middleware. type AuthContext struct { Token string Verification *sso.VerificationResult User *entity.User + PrincipalType string + PrincipalName string Roles []sso.Role Permissions map[string]struct{} UserAreaIDs []uint @@ -30,6 +44,13 @@ type AuthContext struct { UserAllLocation bool } +func SetAPIKeyAuthenticator(authenticator apikeys.Authenticator) { + apiKeyAuthMu.Lock() + defer apiKeyAuthMu.Unlock() + + apiKeyAuthenticator = authenticator +} + // Auth validates the incoming request against the central SSO access token and // loads the corresponding local user. Optional scopes can be provided to enforce // fine-grained authorization using the SSO access token scopes. @@ -62,10 +83,20 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl } } if token == "" { + if c.Method() == fiber.MethodGet { + if err := authenticateAPIKey(c); err == nil { + if len(requiredScopes) > 0 { + return fiber.NewError(fiber.StatusForbidden, "Insufficient scope") + } + return c.Next() + } else if err != nil && !errors.Is(err, apikeys.ErrInvalidAPIKey) && !errors.Is(err, apikeys.ErrInactiveKey) { + return err + } + } return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") } - verification, err := sso.VerifyAccessToken(token) + verification, err := verifyAccessTokenFunc(token) if err != nil { if sso.IsSignatureError(err) { logSignatureError("auth", tokenSource, token, err) @@ -99,7 +130,7 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl permissions := make(map[string]struct{}) var profile *sso.UserProfile if verification.UserID != 0 { - if p, err := sso.FetchProfile(c.Context(), token, verification); err != nil { + if p, err := fetchProfileFunc(c.Context(), token, verification); err != nil { utils.Log.WithError(err).Warn("auth: failed to fetch sso profile") } else { profile = p @@ -118,6 +149,8 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl Token: token, Verification: verification, User: user, + PrincipalType: "user", + PrincipalName: user.Name, Roles: roles, Permissions: permissions, UserAreaIDs: nil, @@ -219,6 +252,57 @@ func bearerToken(c *fiber.Ctx) string { return "" } +func authenticateAPIKey(c *fiber.Ctx) error { + rawKey := strings.TrimSpace(c.Get("X-API-Key")) + if rawKey == "" { + return apikeys.ErrInvalidAPIKey + } + + authenticator := currentAPIKeyAuthenticator() + if authenticator == nil { + return apikeys.ErrInvalidAPIKey + } + + principal, err := authenticator.Authenticate(context.Background(), rawKey, c.IP()) + if err != nil { + if errors.Is(err, apikeys.ErrInvalidAPIKey) || errors.Is(err, apikeys.ErrInactiveKey) { + return apikeys.ErrInvalidAPIKey + } + utils.Log.WithError(err).Warn("auth: api key authentication failed") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to authenticate request") + } + + permissions := make(map[string]struct{}, len(principal.Permissions)) + for _, perm := range principal.Permissions { + if canonical := canonicalPermission(perm); canonical != "" { + permissions[canonical] = struct{}{} + } + } + + c.Locals(authContextLocalsKey, &AuthContext{ + Token: "", + Verification: nil, + User: nil, + PrincipalType: "api_key", + PrincipalName: principal.Name, + Roles: nil, + Permissions: permissions, + UserAreaIDs: principal.AreaIDs, + UserLocationIDs: principal.LocationIDs, + UserAllArea: principal.AllArea, + UserAllLocation: principal.AllLocation, + }) + c.Locals(authUserLocalsKey, nil) + return nil +} + +func currentAPIKeyAuthenticator() apikeys.Authenticator { + apiKeyAuthMu.RLock() + defer apiKeyAuthMu.RUnlock() + + return apiKeyAuthenticator +} + func hasAllScopes(have, required []string) bool { if len(required) == 0 { return true diff --git a/internal/middleware/auth_apikey_test.go b/internal/middleware/auth_apikey_test.go new file mode 100644 index 00000000..4ab8c1a6 --- /dev/null +++ b/internal/middleware/auth_apikey_test.go @@ -0,0 +1,239 @@ +package middleware + +import ( + "context" + "errors" + "net/http/httptest" + "testing" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" + "gitlab.com/mbugroup/lti-api.git/internal/apikeys" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + sso "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/verifier" + userValidation "gitlab.com/mbugroup/lti-api.git/internal/modules/users/validations" +) + +type stubUserService struct { + user *entity.User + err error +} + +func (s *stubUserService) GetAll(_ *fiber.Ctx, _ *userValidation.Query) ([]entity.User, int64, error) { + return nil, 0, nil +} + +func (s *stubUserService) GetOne(_ *fiber.Ctx, _ uint) (*entity.User, error) { + return s.user, s.err +} + +func (s *stubUserService) CreateOne(_ *fiber.Ctx, _ *userValidation.Create) (*entity.User, error) { + return nil, nil +} + +func (s *stubUserService) UpdateOne(_ *fiber.Ctx, _ *userValidation.Update, _ uint) (*entity.User, error) { + return nil, nil +} + +func (s *stubUserService) DeleteOne(_ *fiber.Ctx, _ uint) error { + return nil +} + +func (s *stubUserService) GetBySSOUserID(_ *fiber.Ctx, _ uint) (*entity.User, error) { + return s.user, s.err +} + +type stubAPIKeyAuthenticator struct { + principal *apikeys.Principal + err error +} + +func (s *stubAPIKeyAuthenticator) Authenticate(_ context.Context, _ string, _ string) (*apikeys.Principal, error) { + return s.principal, s.err +} + +func TestAuthAllowsAPIKeyOnGet(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ + principal: &apikeys.Principal{ + Name: "dashboard", + Permissions: []string{"perm.read"}, + LocationIDs: []uint{3, 5}, + }, + }) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { + scope, err := ResolveLocationScope(c, nil) + if err != nil { + return err + } + return c.JSON(fiber.Map{ + "principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType, + "restrict": scope.Restrict, + "ids": scope.IDs, + }) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusOK { + t.Fatalf("expected 200, got %d", resp.StatusCode) + } +} + +func TestAuthRejectsAPIKeyOnPost(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ + principal: &apikeys.Principal{ + Name: "dashboard", + Permissions: []string{"perm.write"}, + }, + }) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Post("/reports", Auth(&stubUserService{}), RequirePermissions("perm.write"), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodPost, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusUnauthorized { + t.Fatalf("expected 401, got %d", resp.StatusCode) + } +} + +func TestAuthRejectsInvalidAPIKey(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInvalidAPIKey}) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusUnauthorized { + t.Fatalf("expected 401, got %d", resp.StatusCode) + } +} + +func TestAuthRejectsInactiveAPIKey(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInactiveKey}) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusUnauthorized { + t.Fatalf("expected 401, got %d", resp.StatusCode) + } +} + +func TestAuthRejectsMissingPermission(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{ + principal: &apikeys.Principal{ + Name: "dashboard", + Permissions: []string{"perm.other"}, + }, + }) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusForbidden { + t.Fatalf("expected 403, got %d", resp.StatusCode) + } +} + +func TestAuthAllowsBearerOnGet(t *testing.T) { + previousVerify := verifyAccessTokenFunc + previousProfile := fetchProfileFunc + defer func() { + verifyAccessTokenFunc = previousVerify + fetchProfileFunc = previousProfile + }() + + verifyAccessTokenFunc = func(_ string) (*sso.VerificationResult, error) { + return &sso.VerificationResult{ + UserID: 1, + Claims: &sso.AccessTokenClaims{ + RegisteredClaims: jwt.RegisteredClaims{ + IssuedAt: jwt.NewNumericDate(time.Now().UTC()), + }, + }, + }, nil + } + fetchProfileFunc = func(_ context.Context, _ string, _ *sso.VerificationResult) (*sso.UserProfile, error) { + return &sso.UserProfile{ + Permissions: []sso.Permission{{Name: "perm.read"}}, + LocationIDs: []uint{7}, + }, nil + } + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{user: &entity.User{Id: 9, Name: "API User"}}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType}) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("Authorization", "Bearer test-token") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusOK { + t.Fatalf("expected 200, got %d", resp.StatusCode) + } +} + +func TestAuthReturnsServerErrorWhenAPIKeyVerifierFailsUnexpectedly(t *testing.T) { + SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: errors.New("boom")}) + defer SetAPIKeyAuthenticator(nil) + + app := fiber.New() + app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + req := httptest.NewRequest(fiber.MethodGet, "/reports", nil) + req.Header.Set("X-API-Key", "lti_dev_prefix_secret") + resp, err := app.Test(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != fiber.StatusInternalServerError { + t.Fatalf("expected 500, got %d", resp.StatusCode) + } +} diff --git a/internal/readapi/readapi.go b/internal/readapi/readapi.go new file mode 100644 index 00000000..ba6651b8 --- /dev/null +++ b/internal/readapi/readapi.go @@ -0,0 +1,830 @@ +package readapi + +import ( + "encoding/json" + "fmt" + "net/http" + "sort" + "strings" + + "github.com/gofiber/fiber/v2" + "gitlab.com/mbugroup/lti-api.git/internal/config" + "gopkg.in/yaml.v3" +) + +type Artifacts struct { + OpenAPIJSON []byte + OpenAPIYAML []byte + PostmanCollectionJSON []byte + PostmanEnvironmentJSON []byte +} + +type normalizedRoute struct { + Method string + Path string + Params []string +} + +type securityMode string + +const ( + securityNone securityMode = "none" + securityBearer securityMode = "bearer" + securityAPIOrBearer securityMode = "api_or_bearer" +) + +type parameterMeta struct { + Name string + In string + Description string + Required bool + Example any + PostmanValue string + IncludePostman bool +} + +type routeMeta struct { + Group string + Tag string + Summary string + Description string + Security securityMode + ListStyle bool + QueryParams []parameterMeta + Exclude bool +} + +func RegisterRoutes(router fiber.Router) { + router.Get("/openapi/read.json", serveOpenAPIJSON) + router.Get("/openapi/read.yaml", serveOpenAPIYAML) +} + +func PrimeBuildConfig() { + if strings.TrimSpace(config.S3Bucket) == "" { + config.S3Bucket = "read-api-artifacts" + } + if strings.TrimSpace(config.S3Region) == "" { + config.S3Region = "us-east-1" + } + if strings.TrimSpace(config.S3AccessKey) == "" { + config.S3AccessKey = "local-access-key" + } + if strings.TrimSpace(config.S3SecretKey) == "" { + config.S3SecretKey = "local-secret-key" + } + if strings.TrimSpace(config.S3Endpoint) == "" { + config.S3Endpoint = "http://localhost:9000" + } + if strings.TrimSpace(config.S3PublicBaseURL) == "" { + config.S3PublicBaseURL = "http://localhost:9000/read-api-artifacts" + } +} + +func BuildArtifactsFromApp(app *fiber.App) (Artifacts, error) { + if app == nil { + return Artifacts{}, fmt.Errorf("app is required") + } + + routes := normalizeRoutes(app.GetRoutes(true)) + return buildArtifactsFromNormalized(routes) +} + +func BuildArtifacts(routes []fiber.Route) (Artifacts, error) { + normalized := normalizeRoutes(routes) + return buildArtifactsFromNormalized(normalized) +} + +func buildArtifactsFromNormalized(normalized []normalizedRoute) (Artifacts, error) { + specDoc := buildOpenAPIDocument(normalized) + collectionDoc := buildPostmanCollection(normalized) + environmentDoc := buildPostmanEnvironment(normalized) + + openAPIJSON, err := json.MarshalIndent(specDoc, "", " ") + if err != nil { + return Artifacts{}, err + } + + openAPIYAML, err := yaml.Marshal(specDoc) + if err != nil { + return Artifacts{}, err + } + + postmanCollectionJSON, err := json.MarshalIndent(collectionDoc, "", " ") + if err != nil { + return Artifacts{}, err + } + + postmanEnvironmentJSON, err := json.MarshalIndent(environmentDoc, "", " ") + if err != nil { + return Artifacts{}, err + } + + return Artifacts{ + OpenAPIJSON: openAPIJSON, + OpenAPIYAML: openAPIYAML, + PostmanCollectionJSON: postmanCollectionJSON, + PostmanEnvironmentJSON: postmanEnvironmentJSON, + }, nil +} + +func serveOpenAPIJSON(c *fiber.Ctx) error { + artifacts, err := BuildArtifactsFromApp(c.App()) + if err != nil { + return err + } + return c.Status(fiber.StatusOK).Type("json").Send(artifacts.OpenAPIJSON) +} + +func serveOpenAPIYAML(c *fiber.Ctx) error { + artifacts, err := BuildArtifactsFromApp(c.App()) + if err != nil { + return err + } + c.Set(fiber.HeaderContentType, "application/yaml") + return c.Status(fiber.StatusOK).Send(artifacts.OpenAPIYAML) +} + +func normalizeRoutes(routes []fiber.Route) []normalizedRoute { + seen := make(map[string]struct{}, len(routes)) + normalized := make([]normalizedRoute, 0, len(routes)) + for _, route := range routes { + if route.Method != http.MethodGet { + continue + } + if route.Path == "" || strings.HasPrefix(route.Path, "/api/openapi/") { + continue + } + + key := route.Method + " " + route.Path + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + + normalized = append(normalized, normalizedRoute{ + Method: route.Method, + Path: route.Path, + Params: append([]string(nil), route.Params...), + }) + } + + sort.Slice(normalized, func(i, j int) bool { + if normalized[i].Path == normalized[j].Path { + return normalized[i].Method < normalized[j].Method + } + return normalized[i].Path < normalized[j].Path + }) + + return normalized +} + +func buildOpenAPIDocument(routes []normalizedRoute) map[string]any { + paths := make(map[string]any, len(routes)) + for _, route := range routes { + meta := describeRoute(route) + if meta.Exclude { + continue + } + + openAPIPath := toOpenAPIPath(route.Path) + operation := map[string]any{ + "summary": meta.Summary, + "description": meta.Description, + "tags": []string{meta.Tag}, + "responses": map[string]any{ + "200": map[string]any{ + "description": "Successful response", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": successSchema(meta), + }, + }, + }, + "401": map[string]any{ + "description": "Unauthorized", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": map[string]any{"$ref": "#/components/schemas/ErrorEnvelope"}, + }, + }, + }, + "403": map[string]any{ + "description": "Forbidden", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": map[string]any{"$ref": "#/components/schemas/ErrorEnvelope"}, + }, + }, + }, + }, + } + + params := buildParameters(route, meta) + if len(params) > 0 { + operation["parameters"] = params + } + + switch meta.Security { + case securityBearer: + operation["security"] = []map[string][]string{{"BearerAuth": {}}} + case securityAPIOrBearer: + operation["security"] = []map[string][]string{ + {"ApiKeyAuth": {}}, + {"BearerAuth": {}}, + } + } + + paths[openAPIPath] = map[string]any{ + "get": operation, + } + } + + return map[string]any{ + "openapi": "3.1.0", + "info": map[string]any{ + "title": "LTI ERP Read API", + "version": "v1", + "description": "Read-only OpenAPI surface for dashboard integrations and GET endpoint exploration.", + }, + "servers": []map[string]any{ + {"url": "http://localhost:8081"}, + }, + "paths": paths, + "components": map[string]any{ + "securitySchemes": map[string]any{ + "ApiKeyAuth": map[string]any{ + "type": "apiKey", + "in": "header", + "name": "X-API-Key", + }, + "BearerAuth": map[string]any{ + "type": "http", + "scheme": "bearer", + }, + }, + "schemas": map[string]any{ + "SuccessEnvelope": map[string]any{ + "type": "object", + "properties": map[string]any{ + "code": map[string]any{"type": "integer", "example": 200}, + "status": map[string]any{"type": "string", "example": "success"}, + "message": map[string]any{"type": "string", "example": "Request completed successfully"}, + "data": map[string]any{"type": "object", "additionalProperties": true}, + }, + }, + "PaginatedEnvelope": map[string]any{ + "type": "object", + "properties": map[string]any{ + "code": map[string]any{"type": "integer", "example": 200}, + "status": map[string]any{"type": "string", "example": "success"}, + "message": map[string]any{"type": "string", "example": "Request completed successfully"}, + "meta": map[string]any{ + "type": "object", + "properties": map[string]any{ + "page": map[string]any{"type": "integer", "example": 1}, + "limit": map[string]any{"type": "integer", "example": 10}, + "total_pages": map[string]any{"type": "integer", "example": 1}, + "total_results": map[string]any{"type": "integer", "example": 0}, + }, + }, + "data": map[string]any{ + "type": "array", + "items": map[string]any{"type": "object", "additionalProperties": true}, + }, + }, + }, + "ErrorEnvelope": map[string]any{ + "type": "object", + "properties": map[string]any{ + "code": map[string]any{"type": "integer", "example": 401}, + "status": map[string]any{"type": "string", "example": "error"}, + "message": map[string]any{"type": "string", "example": "Please authenticate"}, + "errors": map[string]any{"type": "object", "additionalProperties": true}, + }, + }, + }, + }, + } +} + +func buildParameters(route normalizedRoute, meta routeMeta) []map[string]any { + params := make([]map[string]any, 0, len(route.Params)+len(meta.QueryParams)) + for _, param := range route.Params { + params = append(params, map[string]any{ + "name": param, + "in": "path", + "required": true, + "description": fmt.Sprintf("Path parameter `%s`.", param), + "schema": map[string]any{ + "type": "string", + "example": defaultExampleForVariable(inferPostmanPathVariable(route.Path, param)), + }, + }) + } + for _, query := range meta.QueryParams { + parameter := map[string]any{ + "name": query.Name, + "in": query.In, + "required": query.Required, + "description": query.Description, + "schema": inferSchema(query.Example), + } + if query.Example != nil { + parameter["example"] = query.Example + } + params = append(params, parameter) + } + return params +} + +func successSchema(meta routeMeta) map[string]any { + if meta.ListStyle { + return map[string]any{"$ref": "#/components/schemas/PaginatedEnvelope"} + } + return map[string]any{"$ref": "#/components/schemas/SuccessEnvelope"} +} + +func buildPostmanCollection(routes []normalizedRoute) map[string]any { + type folder struct { + name string + items []any + } + + folders := map[string]map[string]*folder{ + "Public": {}, + "Dashboard API Key": {}, + "Internal/OAuth Reference": {}, + } + + for _, route := range routes { + meta := describeRoute(route) + if meta.Exclude { + continue + } + + tagFolder := folders[meta.Group] + group, ok := tagFolder[meta.Tag] + if !ok { + group = &folder{name: meta.Tag} + tagFolder[meta.Tag] = group + } + group.items = append(group.items, buildPostmanRequest(route, meta)) + } + + rootItems := make([]any, 0, len(folders)) + for _, groupName := range []string{"Public", "Dashboard API Key", "Internal/OAuth Reference"} { + tagFolder := folders[groupName] + tagNames := make([]string, 0, len(tagFolder)) + for tagName := range tagFolder { + tagNames = append(tagNames, tagName) + } + sort.Strings(tagNames) + + groupItems := make([]any, 0, len(tagNames)) + for _, tagName := range tagNames { + groupItems = append(groupItems, map[string]any{ + "name": tagName, + "item": tagFolder[tagName].items, + }) + } + + folder := map[string]any{ + "name": groupName, + "item": groupItems, + } + if events := postmanFolderEvents(groupName); len(events) > 0 { + folder["event"] = events + } + rootItems = append(rootItems, folder) + } + + return map[string]any{ + "info": map[string]any{ + "name": "LTI ERP Read API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + }, + "item": rootItems, + } +} + +func postmanFolderEvents(groupName string) []map[string]any { + switch groupName { + case "Dashboard API Key": + return []map[string]any{ + { + "listen": "prerequest", + "script": map[string]any{ + "type": "text/javascript", + "exec": []string{ + "const apiKey = pm.environment.get('api_key');", + "const bearerToken = pm.environment.get('bearer_token');", + "if (apiKey) {", + " pm.request.headers.upsert({ key: 'X-API-Key', value: apiKey });", + " pm.request.headers.remove('Authorization');", + "} else if (bearerToken) {", + " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", + " pm.request.headers.remove('X-API-Key');", + "} else {", + " pm.request.headers.remove('Authorization');", + " pm.request.headers.remove('X-API-Key');", + "}", + }, + }, + }, + } + case "Internal/OAuth Reference": + return []map[string]any{ + { + "listen": "prerequest", + "script": map[string]any{ + "type": "text/javascript", + "exec": []string{ + "const bearerToken = pm.environment.get('bearer_token');", + "pm.request.headers.remove('X-API-Key');", + "if (bearerToken) {", + " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", + "} else {", + " pm.request.headers.remove('Authorization');", + "}", + }, + }, + }, + } + default: + return nil + } +} + +func buildPostmanRequest(route normalizedRoute, meta routeMeta) map[string]any { + return map[string]any{ + "name": meta.Summary, + "request": map[string]any{ + "method": http.MethodGet, + "header": []map[string]any{ + {"key": "Accept", "value": "application/json"}, + }, + "url": buildPostmanURL(route, meta), + }, + } +} + +func buildPostmanURL(route normalizedRoute, meta routeMeta) string { + path := route.Path + for _, param := range route.Params { + path = strings.ReplaceAll(path, ":"+param, "{{"+inferPostmanPathVariable(route.Path, param)+"}}") + } + + query := make([]string, 0, len(meta.QueryParams)) + for _, param := range meta.QueryParams { + if !param.IncludePostman { + continue + } + query = append(query, fmt.Sprintf("%s=%v", param.Name, param.PostmanValue)) + } + + if len(query) == 0 { + return "{{base_url}}" + path + } + + return "{{base_url}}" + path + "?" + strings.Join(query, "&") +} + +func buildPostmanEnvironment(routes []normalizedRoute) map[string]any { + values := map[string]string{ + "base_url": "http://localhost:8081", + "api_key": "", + "bearer_token": "", + "id": "1", + "bank_id": "1", + "customer_id": "1", + "expense_id": "1", + "location_id": "1", + "project_flock_id": "1", + "project_flock_kandang_id": "1", + "supplier_id": "1", + } + + for _, route := range routes { + meta := describeRoute(route) + if meta.Exclude { + continue + } + + for _, param := range route.Params { + name := inferPostmanPathVariable(route.Path, param) + if _, ok := values[name]; !ok { + values[name] = defaultExampleForVariable(name) + } + } + for _, query := range meta.QueryParams { + if query.IncludePostman && strings.HasPrefix(query.PostmanValue, "{{") && strings.HasSuffix(query.PostmanValue, "}}") { + name := strings.TrimSuffix(strings.TrimPrefix(query.PostmanValue, "{{"), "}}") + if _, ok := values[name]; !ok { + values[name] = defaultExampleForVariable(name) + } + } + } + } + + keys := make([]string, 0, len(values)) + for key := range values { + keys = append(keys, key) + } + sort.Strings(keys) + + items := make([]map[string]any, 0, len(keys)) + for _, key := range keys { + items = append(items, map[string]any{ + "key": key, + "value": values[key], + "enabled": true, + }) + } + + return map[string]any{ + "id": "lti-read-api-local", + "name": "LTI ERP Read API.local", + "values": items, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-04-14T00:00:00Z", + "_postman_exported_using": "Codex", + } +} + +func describeRoute(route normalizedRoute) routeMeta { + meta := routeMeta{ + Group: "Dashboard API Key", + Tag: inferTag(route.Path), + Summary: defaultSummary(route.Path), + Description: fmt.Sprintf("Read access to `%s`.", route.Path), + Security: securityAPIOrBearer, + ListStyle: !strings.Contains(route.Path, ":"), + } + + switch { + case route.Path == "/healthz" || route.Path == "/readyz" || route.Path == "/api/constants": + meta.Group = "Public" + meta.Security = securityNone + meta.ListStyle = false + case strings.HasPrefix(route.Path, "/api/sso/"): + meta.Group = "Internal/OAuth Reference" + meta.ListStyle = false + if route.Path == "/api/sso/userinfo" { + meta.Security = securityBearer + } else { + meta.Security = securityNone + } + } + + switch route.Path { + case "/api/dashboards": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "farm"}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "analysis_mode", In: "query", Description: "Dashboard analysis mode.", Example: "OVERVIEW"}, + {Name: "comparison_type", In: "query", Description: "Required when analysis_mode is COMPARISON.", Example: "PREVIOUS_PERIOD"}, + {Name: "metric", In: "query", Description: "Metric to compare.", Example: "egg_mass"}, + {Name: "location_ids", In: "query", Description: "Comma separated location ids.", Example: "1,2"}, + {Name: "flock_ids", In: "query", Description: "Comma separated flock ids.", Example: "1,2"}, + {Name: "kandang_ids", In: "query", Description: "Comma separated kandang ids.", Example: "1,2"}, + {Name: "include", In: "query", Description: "Comma separated dashboard sections to include.", Example: "performance,summary"}, + } + case "/api/closings/:projectFlockId/sapronak", "/api/closings/:projectFlockId/sapronak/summary": + meta.ListStyle = route.Path == "/api/closings/:projectFlockId/sapronak" + meta.QueryParams = []parameterMeta{ + {Name: "type", In: "query", Description: "Required sapronak direction.", Required: true, Example: "incoming", PostmanValue: "incoming", IncludePostman: true}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "pakan"}, + {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1}, + } + case "/api/closings/:projectFlockId/production-data", "/api/closings/:projectFlockId/keuangan": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1}, + } + case "/api/closings/:project_flock_id/expedition-hpp": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "project_flock_kandang_id", In: "query", Description: "Optional project flock kandang id filter.", Example: 1}, + } + case "/api/reports/expense": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "operasional"}, + {Name: "category", In: "query", Description: "Expense category filter.", Example: "BOP"}, + {Name: "supplier_id", In: "query", Description: "Supplier id filter.", Example: 1}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1}, + {Name: "realization_date", In: "query", Description: "Realization date filter (YYYY-MM-DD).", Example: "2026-01-15"}, + } + case "/api/reports/marketing": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "customer_id", In: "query", Description: "Customer id filter.", Example: 1}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, + } + case "/api/reports/purchase-supplier": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "supplier_id", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, + {Name: "area_id", In: "query", Description: "Comma separated area ids.", Example: "1,2"}, + } + case "/api/reports/debt-supplier": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2", PostmanValue: "{{supplier_id}}", IncludePostman: true}, + } + case "/api/reports/customer-payment": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2", PostmanValue: "{{customer_id}}", IncludePostman: true}, + } + case "/api/reports/hpp-per-kandang": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "period", In: "query", Description: "Daily period filter (YYYY-MM).", Example: "2026-01"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1}, + } + case "/api/finance/transactions": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "invoice"}, + {Name: "bank_ids", In: "query", Description: "Comma separated bank ids.", Example: "1,2", PostmanValue: "{{bank_id}}", IncludePostman: true}, + {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2"}, + {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, + {Name: "transaction_types", In: "query", Description: "Comma separated transaction types.", Example: "payment,initial_balance"}, + {Name: "start_date", In: "query", Description: "Start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "End date (YYYY-MM-DD).", Example: "2026-01-31"}, + } + } + + if route.Path == "/healthz" { + meta.Summary = "Health check" + meta.Description = "Simple liveness probe." + } else if route.Path == "/readyz" { + meta.Summary = "Readiness check" + meta.Description = "Readiness probe for database and Redis." + } + + return meta +} + +func inferTag(path string) string { + switch { + case path == "/healthz" || path == "/readyz": + return "System" + case strings.HasPrefix(path, "/api/master-data/"): + return "Master Data" + case strings.HasPrefix(path, "/api/finance/"): + return "Finance" + case strings.HasPrefix(path, "/api/inventory/"): + return "Inventory" + case strings.HasPrefix(path, "/api/production/"): + return "Production" + case strings.HasPrefix(path, "/api/reports/"): + return "Reports" + case strings.HasPrefix(path, "/api/closings/"): + return "Closings" + case strings.HasPrefix(path, "/api/expenses"): + return "Expenses" + case strings.HasPrefix(path, "/api/dashboards"): + return "Dashboards" + case strings.HasPrefix(path, "/api/purchases"): + return "Purchases" + case strings.HasPrefix(path, "/api/marketing"): + return "Marketing" + case strings.HasPrefix(path, "/api/users"): + return "Users" + case strings.HasPrefix(path, "/api/daily-checklists"): + return "Daily Checklists" + case strings.HasPrefix(path, "/api/sso"): + return "SSO" + default: + return "API" + } +} + +func defaultSummary(path string) string { + switch path { + case "/healthz": + return "Health check" + case "/readyz": + return "Readiness check" + } + + trimmed := strings.Trim(path, "/") + if trimmed == "" { + return "List root" + } + parts := strings.Split(trimmed, "/") + for i, part := range parts { + parts[i] = strings.ReplaceAll(part, "-", " ") + } + return "GET " + strings.Join(parts, " / ") +} + +func toOpenAPIPath(path string) string { + segments := strings.Split(path, "/") + for i, segment := range segments { + if strings.HasPrefix(segment, ":") { + segments[i] = "{" + strings.TrimPrefix(segment, ":") + "}" + } + } + return strings.Join(segments, "/") +} + +func inferPostmanPathVariable(path, param string) string { + if param != "id" { + return param + } + + switch { + case strings.HasPrefix(path, "/api/expenses/"): + return "expense_id" + case strings.HasPrefix(path, "/api/finance/payments/"): + return "payment_id" + case strings.HasPrefix(path, "/api/finance/transactions/"): + return "transaction_id" + case strings.HasPrefix(path, "/api/finance/initial-balances/"): + return "initial_balance_id" + case strings.HasPrefix(path, "/api/finance/injections/"): + return "injection_id" + case strings.HasPrefix(path, "/api/purchases/"): + return "purchase_id" + case strings.HasPrefix(path, "/api/inventory/adjustments/"): + return "adjustment_id" + case strings.HasPrefix(path, "/api/inventory/transfers/"): + return "transfer_id" + case strings.HasPrefix(path, "/api/master-data/banks/"): + return "bank_id" + case strings.HasPrefix(path, "/api/master-data/customers/"): + return "customer_id" + case strings.HasPrefix(path, "/api/master-data/suppliers/"): + return "supplier_id" + case strings.HasPrefix(path, "/api/master-data/locations/"): + return "location_id" + case strings.HasPrefix(path, "/api/master-data/areas/"): + return "area_id" + case strings.HasPrefix(path, "/api/master-data/products/"): + return "product_id" + case strings.HasPrefix(path, "/api/master-data/product-categories/"): + return "product_category_id" + case strings.HasPrefix(path, "/api/master-data/nonstocks/"): + return "nonstock_id" + case strings.HasPrefix(path, "/api/master-data/employees/"): + return "employee_id" + case strings.HasPrefix(path, "/api/master-data/flocks/"): + return "flock_id" + case strings.HasPrefix(path, "/api/master-data/warehouses/"): + return "warehouse_id" + case strings.HasPrefix(path, "/api/master-data/uoms/"): + return "uom_id" + case strings.HasPrefix(path, "/api/users/"): + return "user_id" + case strings.HasPrefix(path, "/api/production/recordings/"): + return "recording_id" + case strings.HasPrefix(path, "/api/production/uniformities/"): + return "uniformity_id" + case strings.HasPrefix(path, "/api/production/chickins/"): + return "chickin_id" + default: + return "id" + } +} + +func defaultExampleForVariable(name string) string { + if strings.Contains(name, "date") { + return "2026-01-01" + } + if strings.Contains(name, "token") || strings.Contains(name, "key") { + return "" + } + return "1" +} + +func inferSchema(example any) map[string]any { + switch example.(type) { + case int, int32, int64, uint, uint32, uint64, float64: + return map[string]any{"type": "integer"} + default: + return map[string]any{"type": "string"} + } +} diff --git a/internal/readapi/readapi_test.go b/internal/readapi/readapi_test.go new file mode 100644 index 00000000..fa740428 --- /dev/null +++ b/internal/readapi/readapi_test.go @@ -0,0 +1,97 @@ +package readapi + +import ( + "encoding/json" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "gitlab.com/mbugroup/lti-api.git/internal/cache" + "gitlab.com/mbugroup/lti-api.git/internal/config" + "gitlab.com/mbugroup/lti-api.git/internal/route" + + "github.com/gofiber/fiber/v2" + "github.com/redis/go-redis/v9" + "gopkg.in/yaml.v3" +) + +func TestGeneratedArtifactsAreCurrent(t *testing.T) { + PrimeBuildConfig() + cache.SetRedis(redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})) + app := fiber.New(config.FiberConfig()) + app.Get("/healthz", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok"}) + }) + app.Get("/readyz", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok"}) + }) + route.Routes(app, nil) + + artifacts, err := BuildArtifactsFromApp(app) + if err != nil { + t.Fatalf("build artifacts: %v", err) + } + + root := repoRoot(t) + assertJSONMatchesFile(t, artifacts.OpenAPIJSON, filepath.Join(root, "docs", "openapi", "read-api.json")) + assertYAMLMatchesFile(t, artifacts.OpenAPIYAML, filepath.Join(root, "docs", "openapi", "read-api.yaml")) + assertJSONMatchesFile(t, artifacts.PostmanCollectionJSON, filepath.Join(root, "docs", "postman", "read-api.collection.json")) + assertJSONMatchesFile(t, artifacts.PostmanEnvironmentJSON, filepath.Join(root, "docs", "postman", "read-api.environment.json")) +} + +func assertJSONMatchesFile(t *testing.T, got []byte, path string) { + t.Helper() + + want, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + + var gotValue any + if err := json.Unmarshal(got, &gotValue); err != nil { + t.Fatalf("unmarshal generated json: %v", err) + } + var wantValue any + if err := json.Unmarshal(want, &wantValue); err != nil { + t.Fatalf("unmarshal fixture json: %v", err) + } + + if !reflect.DeepEqual(gotValue, wantValue) { + t.Fatalf("json artifact mismatch for %s", path) + } +} + +func assertYAMLMatchesFile(t *testing.T, got []byte, path string) { + t.Helper() + + want, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + + var gotValue any + if err := yaml.Unmarshal(got, &gotValue); err != nil { + t.Fatalf("unmarshal generated yaml: %v", err) + } + var wantValue any + if err := yaml.Unmarshal(want, &wantValue); err != nil { + t.Fatalf("unmarshal fixture yaml: %v", err) + } + + if !reflect.DeepEqual(gotValue, wantValue) { + t.Fatalf("yaml artifact mismatch for %s", path) + } +} + +func repoRoot(t *testing.T) string { + t.Helper() + + _, filename, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("runtime.Caller failed") + } + + return filepath.Clean(filepath.Join(filepath.Dir(filename), "..", "..")) +} From 94a71913654eb57dc46638f7b1a21c0ac3420080 Mon Sep 17 00:00:00 2001 From: giovanni Date: Tue, 14 Apr 2026 15:23:08 +0700 Subject: [PATCH 5/8] fix get dashboard --- .../dashboards/repositories/dashboard_stats.repository.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/modules/dashboards/repositories/dashboard_stats.repository.go b/internal/modules/dashboards/repositories/dashboard_stats.repository.go index 5cba9acf..c79879fb 100644 --- a/internal/modules/dashboards/repositories/dashboard_stats.repository.go +++ b/internal/modules/dashboards/repositories/dashboard_stats.repository.go @@ -172,7 +172,7 @@ func (r *DashboardRepositoryImpl) GetUniformityWeeklyMetrics(ctx context.Context db = applyDashboardFilters(db, filters) - if err := db.Group("week").Order("week ASC").Scan(&rows).Error; err != nil { + if err := db.Group(weekExpr).Order("1 ASC").Scan(&rows).Error; err != nil { return nil, err } @@ -561,8 +561,8 @@ func (r *DashboardRepositoryImpl) GetComparisonWeeklyUniformityMetrics(ctx conte db = applyDashboardFilters(db, filters) - groupBy := fmt.Sprintf("week, %s", groupExpr) - orderBy := fmt.Sprintf("week ASC, %s", orderExpr) + groupBy := fmt.Sprintf("%s, %s", weekExpr, groupExpr) + orderBy := fmt.Sprintf("1 ASC, %s", orderExpr) if err := db.Group(groupBy).Order(orderBy).Scan(&rows).Error; err != nil { return nil, err } From 6a012b75aa88e7d58860beb43bd816330098a24e Mon Sep 17 00:00:00 2001 From: giovanni Date: Tue, 14 Apr 2026 16:52:50 +0700 Subject: [PATCH 6/8] adjust collection; adjust migration --- docs/openapi/read-api.json | 2870 ++++++++++++++++- docs/openapi/read-api.yaml | 2028 +++++++++++- docs/postman/read-api.collection.json | 157 +- internal/apikeys/service.go | 4 +- ...2821_create_integration_api_keys.down.sql} | 0 ...082821_create_integration_api_keys.up.sql} | 0 internal/readapi/readapi.go | 448 ++- 7 files changed, 5261 insertions(+), 246 deletions(-) rename internal/database/migrations/{20260414090000_create_integration_api_keys.down.sql => 20260414082821_create_integration_api_keys.down.sql} (100%) rename internal/database/migrations/{20260414090000_create_integration_api_keys.up.sql => 20260414082821_create_integration_api_keys.up.sql} (100%) diff --git a/docs/openapi/read-api.json b/docs/openapi/read-api.json index b047d432..f2f91f7d 100644 --- a/docs/openapi/read-api.json +++ b/docs/openapi/read-api.json @@ -110,7 +110,79 @@ "paths": { "/api/approvals/": { "get": { - "description": "Read access to `/api/approvals/`.", + "description": "Read access to `/api/approvals`.", + "parameters": [ + { + "description": "Approval workflow module name.", + "example": "EXPENSES", + "in": "query", + "name": "module_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional approvable module id.", + "example": 1, + "in": "query", + "name": "module_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Group approval records by step number.", + "example": false, + "in": "query", + "name": "group_step_number", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "approval", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Sort direction by date.", + "example": "DESC", + "in": "query", + "name": "order_by_date", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -159,7 +231,59 @@ }, "/api/closings/": { "get": { - "description": "Read access to `/api/closings/`.", + "description": "Read access to `/api/closings`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "kandang", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Project status filter (1 or 2).", + "example": 1, + "in": "query", + "name": "project_status", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -202,7 +326,7 @@ ], "summary": "GET api / closings", "tags": [ - "Closings" + "API" ] } }, @@ -219,6 +343,16 @@ "example": "1", "type": "string" } + }, + { + "description": "Optional kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } } ], "responses": { @@ -433,6 +567,26 @@ "type": "string" } }, + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, { "description": "Search keyword.", "example": "pakan", @@ -797,6 +951,16 @@ "example": "1", "type": "string" } + }, + { + "description": "Product category flag filter (DOC/OVK/PAKAN/PULLET).", + "example": "DOC", + "in": "query", + "name": "flag", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -1152,6 +1316,16 @@ "example": "1", "type": "string" } + }, + { + "description": "Product category flag filter (DOC/OVK/PAKAN/PULLET).", + "example": "DOC", + "in": "query", + "name": "flag", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -1202,13 +1376,13 @@ }, "/api/constants/": { "get": { - "description": "Read access to `/api/constants/`.", + "description": "Read access to `/api/constants`.", "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PaginatedEnvelope" + "$ref": "#/components/schemas/SuccessEnvelope" } } }, @@ -1235,14 +1409,6 @@ "description": "Forbidden" } }, - "security": [ - { - "ApiKeyAuth": [] - }, - { - "BearerAuth": [] - } - ], "summary": "GET api / constants", "tags": [ "API" @@ -1251,7 +1417,79 @@ }, "/api/daily-checklists/": { "get": { - "description": "Read access to `/api/daily-checklists/`.", + "description": "Read access to `/api/daily-checklists`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "kebersihan", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Start date filter (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "date_from", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "End date filter (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "date_to", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Checklist status filter.", + "example": "done", + "in": "query", + "name": "status", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -1423,6 +1661,98 @@ "/api/daily-checklists/report": { "get": { "description": "Read access to `/api/daily-checklists/report`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Month number (1-12).", + "example": 1, + "in": "query", + "name": "bulan", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Year.", + "example": 2026, + "in": "query", + "name": "tahun", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Employee id filter.", + "example": 1, + "in": "query", + "name": "employee_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Phase id filter.", + "example": 1, + "in": "query", + "name": "phase_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -1472,6 +1802,48 @@ "/api/daily-checklists/summary": { "get": { "description": "Read access to `/api/daily-checklists/summary`.", + "parameters": [ + { + "description": "Start date filter (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "date_from", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "End date filter (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "date_to", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Checklist category filter.", + "example": "cleaning", + "in": "query", + "name": "category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -1521,6 +1893,18 @@ "/api/daily-checklists/tasks": { "get": { "description": "Read access to `/api/daily-checklists/tasks`.", + "parameters": [ + { + "description": "Daily checklist id.", + "example": 1, + "in": "query", + "name": "checklist_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -1569,7 +1953,129 @@ }, "/api/dashboards/": { "get": { - "description": "Read access to `/api/dashboards/`.", + "description": "Read access to `/api/dashboards`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "farm", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Period end date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Dashboard analysis mode.", + "example": "OVERVIEW", + "in": "query", + "name": "analysis_mode", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Required when analysis_mode is COMPARISON.", + "example": "PREVIOUS_PERIOD", + "in": "query", + "name": "comparison_type", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Metric to compare.", + "example": "egg_mass", + "in": "query", + "name": "metric", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated location ids.", + "example": "1,2", + "in": "query", + "name": "location_ids", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated flock ids.", + "example": "1,2", + "in": "query", + "name": "flock_ids", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated kandang ids.", + "example": "1,2", + "in": "query", + "name": "kandang_ids", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated dashboard sections to include.", + "example": "performance,summary", + "in": "query", + "name": "include", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -1618,7 +2124,39 @@ }, "/api/expenses/": { "get": { - "description": "Read access to `/api/expenses/`.", + "description": "Read access to `/api/expenses`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "operasional", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -1911,7 +2449,109 @@ }, "/api/finance/transactions/": { "get": { - "description": "Read access to `/api/finance/transactions/`.", + "description": "Read access to `/api/finance/transactions`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "invoice", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated bank ids.", + "example": "1,2", + "in": "query", + "name": "bank_ids", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated customer ids.", + "example": "1,2", + "in": "query", + "name": "customer_ids", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated supplier ids.", + "example": "1,2", + "in": "query", + "name": "supplier_ids", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated transaction types.", + "example": "payment,initial_balance", + "in": "query", + "name": "transaction_types", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Sort date basis.", + "example": "created_at", + "in": "query", + "name": "sort_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "End date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -2021,7 +2661,79 @@ }, "/api/inventory/adjustments/": { "get": { - "description": "Read access to `/api/inventory/adjustments/`.", + "description": "Read access to `/api/inventory/adjustments`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Product id filter.", + "example": 1, + "in": "query", + "name": "product_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Warehouse id filter.", + "example": 1, + "in": "query", + "name": "warehouse_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Transaction type filter.", + "example": "IN", + "in": "query", + "name": "transaction_type", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Transaction subtype filter.", + "example": "ADJUSTMENT", + "in": "query", + "name": "transaction_subtype", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Function code filter.", + "example": "MANUAL_ADJUSTMENT", + "in": "query", + "name": "function_code", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -2131,7 +2843,39 @@ }, "/api/inventory/product-stocks/": { "get": { - "description": "Read access to `/api/inventory/product-stocks/`.", + "description": "Read access to `/api/inventory/product-stocks`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "pakan", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -2241,7 +2985,129 @@ }, "/api/inventory/product-warehouses/": { "get": { - "description": "Read access to `/api/inventory/product-warehouses/`.", + "description": "Read access to `/api/inventory/product-warehouses`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "gudang", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Product id filter.", + "example": 1, + "in": "query", + "name": "product_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Warehouse id filter.", + "example": 1, + "in": "query", + "name": "warehouse_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Stock flags filter.", + "example": "DOC", + "in": "query", + "name": "flags", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Show available stock only.", + "example": true, + "in": "query", + "name": "available_only", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Transfer context filter.", + "example": "inventory_transfer", + "in": "query", + "name": "transfer_context", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Stock mode filter.", + "example": "exclude_chickin", + "in": "query", + "name": "stock_mode", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Warehouse stock type filter.", + "example": "incoming", + "in": "query", + "name": "type", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -2351,7 +3217,39 @@ }, "/api/inventory/transfers/": { "get": { - "description": "Read access to `/api/inventory/transfers/`.", + "description": "Read access to `/api/inventory/transfers`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "TRF", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -2461,7 +3359,79 @@ }, "/api/marketing/": { "get": { - "description": "Read access to `/api/marketing/`.", + "description": "Read access to `/api/marketing`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "delivery", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated product ids.", + "example": "1,2", + "in": "query", + "name": "product_ids", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Delivery status filter.", + "example": "DRAFT", + "in": "query", + "name": "status", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Customer id filter.", + "example": 1, + "in": "query", + "name": "customer_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Marketing id filter.", + "example": 1, + "in": "query", + "name": "marketing_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -2571,7 +3541,39 @@ }, "/api/master-data/areas/": { "get": { - "description": "Read access to `/api/master-data/areas/`.", + "description": "Read access to `/api/master-data/areas`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "bandung", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -2681,7 +3683,39 @@ }, "/api/master-data/banks/": { "get": { - "description": "Read access to `/api/master-data/banks/`.", + "description": "Read access to `/api/master-data/banks`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "bca", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -2791,7 +3825,39 @@ }, "/api/master-data/config-checklists/": { "get": { - "description": "Read access to `/api/master-data/config-checklists/`.", + "description": "Read access to `/api/master-data/config-checklists`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "harian", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -2901,7 +3967,49 @@ }, "/api/master-data/customers/": { "get": { - "description": "Read access to `/api/master-data/customers/`.", + "description": "Read access to `/api/master-data/customers`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "pt", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter customer by marketing relation.", + "example": true, + "in": "query", + "name": "has_marketing", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -3011,7 +4119,59 @@ }, "/api/master-data/employees/": { "get": { - "description": "Read access to `/api/master-data/employees/`.", + "description": "Read access to `/api/master-data/employees`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "andi", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Active status filter.", + "example": true, + "in": "query", + "name": "is_active", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -3121,7 +4281,39 @@ }, "/api/master-data/fcrs/": { "get": { - "description": "Read access to `/api/master-data/fcrs/`.", + "description": "Read access to `/api/master-data/fcrs`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "fcr", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -3231,7 +4423,39 @@ }, "/api/master-data/flocks/": { "get": { - "description": "Read access to `/api/master-data/flocks/`.", + "description": "Read access to `/api/master-data/flocks`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "layer", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -3341,7 +4565,59 @@ }, "/api/master-data/kandang-groups/": { "get": { - "description": "Read access to `/api/master-data/kandang-groups/`.", + "description": "Read access to `/api/master-data/kandang-groups`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "blok", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Person in charge id filter.", + "example": 1, + "in": "query", + "name": "pic_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -3451,7 +4727,59 @@ }, "/api/master-data/kandangs/": { "get": { - "description": "Read access to `/api/master-data/kandangs/`.", + "description": "Read access to `/api/master-data/kandangs`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "kandang", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Person in charge id filter.", + "example": 1, + "in": "query", + "name": "pic_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -3561,7 +4889,59 @@ }, "/api/master-data/locations/": { "get": { - "description": "Read access to `/api/master-data/locations/`.", + "description": "Read access to `/api/master-data/locations`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "farm", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Filter laying locations only.", + "example": false, + "in": "query", + "name": "has_laying", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -3671,7 +5051,49 @@ }, "/api/master-data/nonstocks/": { "get": { - "description": "Read access to `/api/master-data/nonstocks/`.", + "description": "Read access to `/api/master-data/nonstocks`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "vitamin", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Supplier id filter.", + "example": 1, + "in": "query", + "name": "supplier_id", + "required": false, + "schema": { + "type": "integer" + } + } + ], "responses": { "200": { "content": { @@ -3781,7 +5203,49 @@ }, "/api/master-data/phase-activities/": { "get": { - "description": "Read access to `/api/master-data/phase-activities/`.", + "description": "Read access to `/api/master-data/phase-activities`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "cek pakan", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated phase ids.", + "example": "1,2", + "in": "query", + "name": "phase_ids", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -3891,7 +5355,49 @@ }, "/api/master-data/phases/": { "get": { - "description": "Read access to `/api/master-data/phases/`.", + "description": "Read access to `/api/master-data/phases`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "starter", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Phase category filter.", + "example": "Growing", + "in": "query", + "name": "category", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4001,7 +5507,39 @@ }, "/api/master-data/product-categories/": { "get": { - "description": "Read access to `/api/master-data/product-categories/`.", + "description": "Read access to `/api/master-data/product-categories`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "pakan", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4111,7 +5649,49 @@ }, "/api/master-data/production-standards/": { "get": { - "description": "Read access to `/api/master-data/production-standards/`.", + "description": "Read access to `/api/master-data/production-standards`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "standar", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Project category filter.", + "example": "GROWING", + "in": "query", + "name": "project_category", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4221,7 +5801,69 @@ }, "/api/master-data/products/": { "get": { - "description": "Read access to `/api/master-data/products/`.", + "description": "Read access to `/api/master-data/products`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "jagung", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Product category id filter.", + "example": 1, + "in": "query", + "name": "product_category_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Filter depletion products.", + "example": false, + "in": "query", + "name": "is_depletion", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Include all products regardless of status.", + "example": false, + "in": "query", + "name": "include_all", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4331,7 +5973,59 @@ }, "/api/master-data/suppliers/": { "get": { - "description": "Read access to `/api/master-data/suppliers/`.", + "description": "Read access to `/api/master-data/suppliers`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "supplier", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Supplier type flag filter.", + "example": "ACTIVE", + "in": "query", + "name": "flag", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Supplier category filter.", + "example": "PAKAN", + "in": "query", + "name": "category", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4441,7 +6135,39 @@ }, "/api/master-data/uoms/": { "get": { - "description": "Read access to `/api/master-data/uoms/`.", + "description": "Read access to `/api/master-data/uoms`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "kg", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4551,7 +6277,79 @@ }, "/api/master-data/warehouses/": { "get": { - "description": "Read access to `/api/master-data/warehouses/`.", + "description": "Read access to `/api/master-data/warehouses`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "gudang", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Filter only active project flock warehouses.", + "example": false, + "in": "query", + "name": "active_project_flock", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Transfer context filter.", + "example": "inventory_transfer", + "in": "query", + "name": "transfer_context", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4722,7 +6520,129 @@ }, "/api/production/project-flock-kandangs/": { "get": { - "description": "Read access to `/api/production/project-flock-kandangs/`.", + "description": "Read access to `/api/production/project-flock-kandangs`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "kandang", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Return name-with-period projection.", + "example": false, + "in": "query", + "name": "name_with_periode", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Project flock id filter.", + "example": 1, + "in": "query", + "name": "project_flock_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Project category filter.", + "example": "Growing", + "in": "query", + "name": "category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Sort field.", + "example": "created_at", + "in": "query", + "name": "sort_by", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Sort order.", + "example": "ASC", + "in": "query", + "name": "sort_order", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Approval step name filter.", + "example": "Pengajuan", + "in": "query", + "name": "step_name", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4893,7 +6813,129 @@ }, "/api/production/project-flocks/": { "get": { - "description": "Read access to `/api/production/project-flocks/`.", + "description": "Read access to `/api/production/project-flocks`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "flock", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Sort field.", + "example": "created_at", + "in": "query", + "name": "sort_by", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Sort order.", + "example": "asc", + "in": "query", + "name": "sort_order", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Project period filter.", + "example": 1, + "in": "query", + "name": "period", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Project category filter.", + "example": "Growing", + "in": "query", + "name": "category", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Project status filter.", + "example": "Aktif", + "in": "query", + "name": "status", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated kandang ids.", + "example": "1,2", + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Transfer context filter.", + "example": "transfer_to_laying", + "in": "query", + "name": "transfer_context", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -4943,12 +6985,54 @@ "/api/production/project-flocks/kandangs/lookup": { "get": { "description": "Read access to `/api/production/project-flocks/kandangs/lookup`.", + "parameters": [ + { + "description": "Project flock id.", + "example": 1, + "in": "query", + "name": "project_flock_id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Kandang id.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Include population value in response.", + "example": false, + "in": "query", + "name": "withpopulation", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Reference date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "record_date", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PaginatedEnvelope" + "$ref": "#/components/schemas/SuccessEnvelope" } } }, @@ -5113,7 +7197,59 @@ }, "/api/production/recordings/": { "get": { - "description": "Read access to `/api/production/recordings/`.", + "description": "Read access to `/api/production/recordings`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Project flock kandang id filter.", + "example": 1, + "in": "query", + "name": "project_flock_kandang_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "record", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Export mode.", + "example": "excel", + "in": "query", + "name": "export", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -5163,12 +7299,34 @@ "/api/production/recordings/next-day": { "get": { "description": "Read access to `/api/production/recordings/next-day`.", + "parameters": [ + { + "description": "Project flock kandang id.", + "example": 1, + "in": "query", + "name": "project_flock_kandang_id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Recording date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "record_date", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PaginatedEnvelope" + "$ref": "#/components/schemas/SuccessEnvelope" } } }, @@ -5272,7 +7430,89 @@ }, "/api/production/transfer_layings/": { "get": { - "description": "Read access to `/api/production/transfer_layings/`.", + "description": "Read access to `/api/production/transfer_layings`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "transfer", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "End date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated source flock ids.", + "example": "1,2", + "in": "query", + "name": "flock_source", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated destination flock ids.", + "example": "3,4", + "in": "query", + "name": "flock_destination", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated status values.", + "example": "DRAFT,APPROVED", + "in": "query", + "name": "status", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -5504,7 +7744,79 @@ }, "/api/production/uniformities/": { "get": { - "description": "Read access to `/api/production/uniformities/`.", + "description": "Read access to `/api/production/uniformities`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Project flock kandang id filter.", + "example": 1, + "in": "query", + "name": "project_flock_kandang_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Week number filter.", + "example": 1, + "in": "query", + "name": "week", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Start date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "End date (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Include chart payload.", + "example": false, + "in": "query", + "name": "with_chart", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -5614,7 +7926,139 @@ }, "/api/purchases/": { "get": { - "description": "Read access to `/api/purchases/`.", + "description": "Read access to `/api/purchases`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Supplier id filter.", + "example": 1, + "in": "query", + "name": "supplier_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Product category id filter.", + "example": "1", + "in": "query", + "name": "product_category_id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Approval status filter.", + "example": "PENDING", + "in": "query", + "name": "approval_status", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "PO date (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "po_date", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "PO date start (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "po_date_from", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "PO date end (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "po_date_to", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Search keyword.", + "example": "PO-", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Created date start (YYYY-MM-DD).", + "example": "2026-01-01", + "in": "query", + "name": "created_from", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Created date end (YYYY-MM-DD).", + "example": "2026-01-31", + "in": "query", + "name": "created_to", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -5775,6 +8219,16 @@ "schema": { "type": "string" } + }, + { + "description": "Date field filter.", + "example": "TRANS_DATE", + "in": "query", + "name": "filter_by", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -5876,6 +8330,26 @@ "schema": { "type": "string" } + }, + { + "description": "Date field filter.", + "example": "received_date", + "in": "query", + "name": "filter_by", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Sort order.", + "example": "asc", + "in": "query", + "name": "sort_order", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -5978,6 +8452,36 @@ "type": "integer" } }, + { + "description": "Kandang id filter.", + "example": 1, + "in": "query", + "name": "kandang_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Project flock kandang id filter.", + "example": 1, + "in": "query", + "name": "project_flock_kandang_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Nonstock id filter.", + "example": 1, + "in": "query", + "name": "nonstock_id", + "required": false, + "schema": { + "type": "integer" + } + }, { "description": "Location id filter.", "example": 1, @@ -6080,8 +8584,8 @@ } }, { - "description": "Daily period filter (YYYY-MM).", - "example": "2026-01", + "description": "Daily period filter (YYYY-MM-DD).", + "example": "2026-01-01", "in": "query", "name": "period", "required": false, @@ -6090,23 +8594,63 @@ } }, { - "description": "Location id filter.", - "example": 1, + "description": "Include unrecorded data.", + "example": false, + "in": "query", + "name": "show_unrecorded", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated area ids.", + "example": "1,2", + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated location ids.", + "example": "1,2", "in": "query", "name": "location_id", "required": false, "schema": { - "type": "integer" + "type": "string" } }, { - "description": "Kandang id filter.", - "example": 1, + "description": "Comma separated kandang ids.", + "example": "1,2", "in": "query", "name": "kandang_id", "required": false, "schema": { - "type": "integer" + "type": "string" + } + }, + { + "description": "Minimum body weight filter.", + "example": "1.2", + "in": "query", + "name": "weight_min", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Maximum body weight filter.", + "example": "1.8", + "in": "query", + "name": "weight_max", + "required": false, + "schema": { + "type": "string" } } ], @@ -6180,6 +8724,96 @@ "type": "integer" } }, + { + "description": "Search keyword.", + "example": "SO-", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Customer id filter.", + "example": 1, + "in": "query", + "name": "customer_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Product id filter.", + "example": 1, + "in": "query", + "name": "product_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Warehouse id filter.", + "example": 1, + "in": "query", + "name": "warehouse_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Sales person id filter.", + "example": 1, + "in": "query", + "name": "sales_person_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Area id filter.", + "example": 1, + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Location id filter.", + "example": 1, + "in": "query", + "name": "location_id", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Marketing type filter.", + "example": "ayam", + "in": "query", + "name": "marketing_type", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Date field filter.", + "example": "so_date", + "in": "query", + "name": "filter_by", + "required": false, + "schema": { + "type": "string" + } + }, { "description": "Period start date (YYYY-MM-DD).", "example": "2026-01-01", @@ -6201,23 +8835,23 @@ } }, { - "description": "Customer id filter.", - "example": 1, + "description": "Sort field.", + "example": "so_date", "in": "query", - "name": "customer_id", + "name": "sort_by", "required": false, "schema": { - "type": "integer" + "type": "string" } }, { - "description": "Location id filter.", - "example": 1, + "description": "Sort order.", + "example": "asc", "in": "query", - "name": "location_id", + "name": "sort_order", "required": false, "schema": { - "type": "integer" + "type": "string" } } ], @@ -6280,6 +8914,26 @@ "example": "1", "type": "string" } + }, + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } } ], "responses": { @@ -6352,6 +9006,46 @@ "type": "integer" } }, + { + "description": "Comma separated area ids.", + "example": "1,2", + "in": "query", + "name": "area_id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated supplier ids.", + "example": "1,2", + "in": "query", + "name": "supplier_id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated product ids.", + "example": "1,2", + "in": "query", + "name": "product_id", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated product category ids.", + "example": "1,2", + "in": "query", + "name": "product_category_id", + "required": false, + "schema": { + "type": "string" + } + }, { "description": "Period start date (YYYY-MM-DD).", "example": "2026-01-01", @@ -6373,20 +9067,20 @@ } }, { - "description": "Comma separated supplier ids.", - "example": "1,2", + "description": "Sort field.", + "example": "created_at", "in": "query", - "name": "supplier_id", + "name": "sort_by", "required": false, "schema": { "type": "string" } }, { - "description": "Comma separated area ids.", - "example": "1,2", + "description": "Filter field.", + "example": "received_date", "in": "query", - "name": "area_id", + "name": "filter_by", "required": false, "schema": { "type": "string" @@ -6651,7 +9345,39 @@ }, "/api/users/": { "get": { - "description": "Read access to `/api/users/`.", + "description": "Read access to `/api/users`.", + "parameters": [ + { + "description": "Page number.", + "example": 1, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Page size.", + "example": 10, + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Search keyword.", + "example": "admin", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { diff --git a/docs/openapi/read-api.yaml b/docs/openapi/read-api.yaml index 01a4e811..7e62562f 100644 --- a/docs/openapi/read-api.yaml +++ b/docs/openapi/read-api.yaml @@ -78,7 +78,57 @@ openapi: 3.1.0 paths: /api/approvals/: get: - description: Read access to `/api/approvals/`. + description: Read access to `/api/approvals`. + parameters: + - description: Approval workflow module name. + example: EXPENSES + in: query + name: module_name + required: true + schema: + type: string + - description: Optional approvable module id. + example: 1 + in: query + name: module_id + required: false + schema: + type: integer + - description: Group approval records by step number. + example: false + in: query + name: group_step_number + required: false + schema: + type: string + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: approval + in: query + name: search + required: false + schema: + type: string + - description: Sort direction by date. + example: DESC + in: query + name: order_by_date + required: false + schema: + type: string responses: "200": content: @@ -106,7 +156,43 @@ paths: - API /api/closings/: get: - description: Read access to `/api/closings/`. + description: Read access to `/api/closings`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: kandang + in: query + name: search + required: false + schema: + type: string + - description: Project status filter (1 or 2). + example: 1 + in: query + name: project_status + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer responses: "200": content: @@ -131,7 +217,7 @@ paths: - BearerAuth: [] summary: GET api / closings tags: - - Closings + - API /api/closings/{project_flock_id}/{project_flock_kandang_id}/expedition-hpp: get: description: Read access to `/api/closings/:project_flock_id/:project_flock_kandang_id/expedition-hpp`. @@ -322,6 +408,13 @@ paths: schema: example: "1" type: string + - description: Product category flag filter (DOC/OVK/PAKAN/PULLET). + example: DOC + in: query + name: flag + required: false + schema: + type: string responses: "200": content: @@ -473,6 +566,13 @@ paths: schema: example: "1" type: string + - description: Product category flag filter (DOC/OVK/PAKAN/PULLET). + example: DOC + in: query + name: flag + required: false + schema: + type: string responses: "200": content: @@ -509,6 +609,13 @@ paths: schema: example: "1" type: string + - description: Optional kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer responses: "200": content: @@ -638,6 +745,20 @@ paths: required: true schema: type: string + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer - description: Search keyword. example: pakan in: query @@ -736,13 +857,13 @@ paths: - Closings /api/constants/: get: - description: Read access to `/api/constants/`. + description: Read access to `/api/constants`. responses: "200": content: application/json: schema: - $ref: '#/components/schemas/PaginatedEnvelope' + $ref: '#/components/schemas/SuccessEnvelope' description: Successful response "401": content: @@ -756,15 +877,62 @@ paths: schema: $ref: '#/components/schemas/ErrorEnvelope' description: Forbidden - security: - - ApiKeyAuth: [] - - BearerAuth: [] summary: GET api / constants tags: - API /api/daily-checklists/: get: - description: Read access to `/api/daily-checklists/`. + description: Read access to `/api/daily-checklists`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: kebersihan + in: query + name: search + required: false + schema: + type: string + - description: Start date filter (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: date_from + required: false + schema: + type: string + - description: End date filter (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: date_to + required: false + schema: + type: string + - description: Checklist status filter. + example: done + in: query + name: status + required: false + schema: + type: string + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer responses: "200": content: @@ -865,6 +1033,70 @@ paths: /api/daily-checklists/report: get: description: Read access to `/api/daily-checklists/report`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: true + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: true + schema: + type: integer + - description: Month number (1-12). + example: 1 + in: query + name: bulan + required: true + schema: + type: integer + - description: Year. + example: 2026 + in: query + name: tahun + required: true + schema: + type: integer + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + - description: Employee id filter. + example: 1 + in: query + name: employee_id + required: false + schema: + type: integer + - description: Phase id filter. + example: 1 + in: query + name: phase_id + required: false + schema: + type: integer responses: "200": content: @@ -893,6 +1125,35 @@ paths: /api/daily-checklists/summary: get: description: Read access to `/api/daily-checklists/summary`. + parameters: + - description: Start date filter (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: date_from + required: true + schema: + type: string + - description: End date filter (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: date_to + required: true + schema: + type: string + - description: Checklist category filter. + example: cleaning + in: query + name: category + required: false + schema: + type: string + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer responses: "200": content: @@ -921,6 +1182,14 @@ paths: /api/daily-checklists/tasks: get: description: Read access to `/api/daily-checklists/tasks`. + parameters: + - description: Daily checklist id. + example: 1 + in: query + name: checklist_id + required: true + schema: + type: integer responses: "200": content: @@ -948,7 +1217,92 @@ paths: - Daily Checklists /api/dashboards/: get: - description: Read access to `/api/dashboards/`. + description: Read access to `/api/dashboards`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: farm + in: query + name: search + required: false + schema: + type: string + - description: Period start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: Period end date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Dashboard analysis mode. + example: OVERVIEW + in: query + name: analysis_mode + required: false + schema: + type: string + - description: Required when analysis_mode is COMPARISON. + example: PREVIOUS_PERIOD + in: query + name: comparison_type + required: false + schema: + type: string + - description: Metric to compare. + example: egg_mass + in: query + name: metric + required: false + schema: + type: string + - description: Comma separated location ids. + example: 1,2 + in: query + name: location_ids + required: false + schema: + type: string + - description: Comma separated flock ids. + example: 1,2 + in: query + name: flock_ids + required: false + schema: + type: string + - description: Comma separated kandang ids. + example: 1,2 + in: query + name: kandang_ids + required: false + schema: + type: string + - description: Comma separated dashboard sections to include. + example: performance,summary + in: query + name: include + required: false + schema: + type: string responses: "200": content: @@ -976,7 +1330,29 @@ paths: - Dashboards /api/expenses/: get: - description: Read access to `/api/expenses/`. + description: Read access to `/api/expenses`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: operasional + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -1148,7 +1524,78 @@ paths: - Finance /api/finance/transactions/: get: - description: Read access to `/api/finance/transactions/`. + description: Read access to `/api/finance/transactions`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: invoice + in: query + name: search + required: false + schema: + type: string + - description: Comma separated bank ids. + example: 1,2 + in: query + name: bank_ids + required: false + schema: + type: string + - description: Comma separated customer ids. + example: 1,2 + in: query + name: customer_ids + required: false + schema: + type: string + - description: Comma separated supplier ids. + example: 1,2 + in: query + name: supplier_ids + required: false + schema: + type: string + - description: Comma separated transaction types. + example: payment,initial_balance + in: query + name: transaction_types + required: false + schema: + type: string + - description: Sort date basis. + example: created_at + in: query + name: sort_date + required: false + schema: + type: string + - description: Start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: End date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string responses: "200": content: @@ -1212,7 +1659,57 @@ paths: - Finance /api/inventory/adjustments/: get: - description: Read access to `/api/inventory/adjustments/`. + description: Read access to `/api/inventory/adjustments`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Product id filter. + example: 1 + in: query + name: product_id + required: false + schema: + type: integer + - description: Warehouse id filter. + example: 1 + in: query + name: warehouse_id + required: false + schema: + type: integer + - description: Transaction type filter. + example: IN + in: query + name: transaction_type + required: false + schema: + type: string + - description: Transaction subtype filter. + example: ADJUSTMENT + in: query + name: transaction_subtype + required: false + schema: + type: string + - description: Function code filter. + example: MANUAL_ADJUSTMENT + in: query + name: function_code + required: false + schema: + type: string responses: "200": content: @@ -1276,7 +1773,29 @@ paths: - Inventory /api/inventory/product-stocks/: get: - description: Read access to `/api/inventory/product-stocks/`. + description: Read access to `/api/inventory/product-stocks`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: pakan + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -1340,7 +1859,92 @@ paths: - Inventory /api/inventory/product-warehouses/: get: - description: Read access to `/api/inventory/product-warehouses/`. + description: Read access to `/api/inventory/product-warehouses`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: gudang + in: query + name: search + required: false + schema: + type: string + - description: Product id filter. + example: 1 + in: query + name: product_id + required: false + schema: + type: integer + - description: Warehouse id filter. + example: 1 + in: query + name: warehouse_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Stock flags filter. + example: DOC + in: query + name: flags + required: false + schema: + type: string + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + - description: Show available stock only. + example: true + in: query + name: available_only + required: false + schema: + type: string + - description: Transfer context filter. + example: inventory_transfer + in: query + name: transfer_context + required: false + schema: + type: string + - description: Stock mode filter. + example: exclude_chickin + in: query + name: stock_mode + required: false + schema: + type: string + - description: Warehouse stock type filter. + example: incoming + in: query + name: type + required: false + schema: + type: string responses: "200": content: @@ -1404,7 +2008,29 @@ paths: - Inventory /api/inventory/transfers/: get: - description: Read access to `/api/inventory/transfers/`. + description: Read access to `/api/inventory/transfers`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: TRF + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -1468,7 +2094,57 @@ paths: - Inventory /api/marketing/: get: - description: Read access to `/api/marketing/`. + description: Read access to `/api/marketing`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: delivery + in: query + name: search + required: false + schema: + type: string + - description: Comma separated product ids. + example: 1,2 + in: query + name: product_ids + required: false + schema: + type: string + - description: Delivery status filter. + example: DRAFT + in: query + name: status + required: false + schema: + type: string + - description: Customer id filter. + example: 1 + in: query + name: customer_id + required: false + schema: + type: integer + - description: Marketing id filter. + example: 1 + in: query + name: marketing_id + required: false + schema: + type: integer responses: "200": content: @@ -1532,7 +2208,29 @@ paths: - Marketing /api/master-data/areas/: get: - description: Read access to `/api/master-data/areas/`. + description: Read access to `/api/master-data/areas`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: bandung + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -1596,7 +2294,29 @@ paths: - Master Data /api/master-data/banks/: get: - description: Read access to `/api/master-data/banks/`. + description: Read access to `/api/master-data/banks`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: bca + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -1660,7 +2380,29 @@ paths: - Master Data /api/master-data/config-checklists/: get: - description: Read access to `/api/master-data/config-checklists/`. + description: Read access to `/api/master-data/config-checklists`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: harian + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -1724,7 +2466,36 @@ paths: - Master Data /api/master-data/customers/: get: - description: Read access to `/api/master-data/customers/`. + description: Read access to `/api/master-data/customers`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: pt + in: query + name: search + required: false + schema: + type: string + - description: Filter customer by marketing relation. + example: true + in: query + name: has_marketing + required: false + schema: + type: string responses: "200": content: @@ -1788,7 +2559,43 @@ paths: - Master Data /api/master-data/employees/: get: - description: Read access to `/api/master-data/employees/`. + description: Read access to `/api/master-data/employees`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: andi + in: query + name: search + required: false + schema: + type: string + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + - description: Active status filter. + example: true + in: query + name: is_active + required: false + schema: + type: string responses: "200": content: @@ -1852,7 +2659,29 @@ paths: - Master Data /api/master-data/fcrs/: get: - description: Read access to `/api/master-data/fcrs/`. + description: Read access to `/api/master-data/fcrs`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: fcr + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -1916,7 +2745,29 @@ paths: - Master Data /api/master-data/flocks/: get: - description: Read access to `/api/master-data/flocks/`. + description: Read access to `/api/master-data/flocks`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: layer + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -1980,7 +2831,43 @@ paths: - Master Data /api/master-data/kandang-groups/: get: - description: Read access to `/api/master-data/kandang-groups/`. + description: Read access to `/api/master-data/kandang-groups`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: blok + in: query + name: search + required: false + schema: + type: string + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Person in charge id filter. + example: 1 + in: query + name: pic_id + required: false + schema: + type: integer responses: "200": content: @@ -2044,7 +2931,43 @@ paths: - Master Data /api/master-data/kandangs/: get: - description: Read access to `/api/master-data/kandangs/`. + description: Read access to `/api/master-data/kandangs`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: kandang + in: query + name: search + required: false + schema: + type: string + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Person in charge id filter. + example: 1 + in: query + name: pic_id + required: false + schema: + type: integer responses: "200": content: @@ -2108,7 +3031,43 @@ paths: - Master Data /api/master-data/locations/: get: - description: Read access to `/api/master-data/locations/`. + description: Read access to `/api/master-data/locations`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: farm + in: query + name: search + required: false + schema: + type: string + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Filter laying locations only. + example: false + in: query + name: has_laying + required: false + schema: + type: string responses: "200": content: @@ -2172,7 +3131,36 @@ paths: - Master Data /api/master-data/nonstocks/: get: - description: Read access to `/api/master-data/nonstocks/`. + description: Read access to `/api/master-data/nonstocks`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: vitamin + in: query + name: search + required: false + schema: + type: string + - description: Supplier id filter. + example: 1 + in: query + name: supplier_id + required: false + schema: + type: integer responses: "200": content: @@ -2236,7 +3224,36 @@ paths: - Master Data /api/master-data/phase-activities/: get: - description: Read access to `/api/master-data/phase-activities/`. + description: Read access to `/api/master-data/phase-activities`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: cek pakan + in: query + name: search + required: false + schema: + type: string + - description: Comma separated phase ids. + example: 1,2 + in: query + name: phase_ids + required: false + schema: + type: string responses: "200": content: @@ -2300,7 +3317,36 @@ paths: - Master Data /api/master-data/phases/: get: - description: Read access to `/api/master-data/phases/`. + description: Read access to `/api/master-data/phases`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: starter + in: query + name: search + required: false + schema: + type: string + - description: Phase category filter. + example: Growing + in: query + name: category + required: false + schema: + type: string responses: "200": content: @@ -2364,7 +3410,29 @@ paths: - Master Data /api/master-data/product-categories/: get: - description: Read access to `/api/master-data/product-categories/`. + description: Read access to `/api/master-data/product-categories`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: pakan + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -2428,7 +3496,36 @@ paths: - Master Data /api/master-data/production-standards/: get: - description: Read access to `/api/master-data/production-standards/`. + description: Read access to `/api/master-data/production-standards`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: standar + in: query + name: search + required: false + schema: + type: string + - description: Project category filter. + example: GROWING + in: query + name: project_category + required: false + schema: + type: string responses: "200": content: @@ -2492,7 +3589,50 @@ paths: - Master Data /api/master-data/products/: get: - description: Read access to `/api/master-data/products/`. + description: Read access to `/api/master-data/products`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: jagung + in: query + name: search + required: false + schema: + type: string + - description: Product category id filter. + example: 1 + in: query + name: product_category_id + required: false + schema: + type: integer + - description: Filter depletion products. + example: false + in: query + name: is_depletion + required: false + schema: + type: string + - description: Include all products regardless of status. + example: false + in: query + name: include_all + required: false + schema: + type: string responses: "200": content: @@ -2556,7 +3696,43 @@ paths: - Master Data /api/master-data/suppliers/: get: - description: Read access to `/api/master-data/suppliers/`. + description: Read access to `/api/master-data/suppliers`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: supplier + in: query + name: search + required: false + schema: + type: string + - description: Supplier type flag filter. + example: ACTIVE + in: query + name: flag + required: false + schema: + type: string + - description: Supplier category filter. + example: PAKAN + in: query + name: category + required: false + schema: + type: string responses: "200": content: @@ -2620,7 +3796,29 @@ paths: - Master Data /api/master-data/uoms/: get: - description: Read access to `/api/master-data/uoms/`. + description: Read access to `/api/master-data/uoms`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: kg + in: query + name: search + required: false + schema: + type: string responses: "200": content: @@ -2684,7 +3882,57 @@ paths: - Master Data /api/master-data/warehouses/: get: - description: Read access to `/api/master-data/warehouses/`. + description: Read access to `/api/master-data/warehouses`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: gudang + in: query + name: search + required: false + schema: + type: string + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Filter only active project flock warehouses. + example: false + in: query + name: active_project_flock + required: false + schema: + type: string + - description: Transfer context filter. + example: inventory_transfer + in: query + name: transfer_context + required: false + schema: + type: string responses: "200": content: @@ -2784,7 +4032,92 @@ paths: - Production /api/production/project-flock-kandangs/: get: - description: Read access to `/api/production/project-flock-kandangs/`. + description: Read access to `/api/production/project-flock-kandangs`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: kandang + in: query + name: search + required: false + schema: + type: string + - description: Return name-with-period projection. + example: false + in: query + name: name_with_periode + required: false + schema: + type: string + - description: Project flock id filter. + example: 1 + in: query + name: project_flock_id + required: false + schema: + type: integer + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + - description: Project category filter. + example: Growing + in: query + name: category + required: false + schema: + type: string + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Sort field. + example: created_at + in: query + name: sort_by + required: false + schema: + type: string + - description: Sort order. + example: ASC + in: query + name: sort_order + required: false + schema: + type: string + - description: Approval step name filter. + example: Pengajuan + in: query + name: step_name + required: false + schema: + type: string responses: "200": content: @@ -2884,7 +4217,92 @@ paths: - Production /api/production/project-flocks/: get: - description: Read access to `/api/production/project-flocks/`. + description: Read access to `/api/production/project-flocks`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: flock + in: query + name: search + required: false + schema: + type: string + - description: Sort field. + example: created_at + in: query + name: sort_by + required: false + schema: + type: string + - description: Sort order. + example: asc + in: query + name: sort_order + required: false + schema: + type: string + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Project period filter. + example: 1 + in: query + name: period + required: false + schema: + type: integer + - description: Project category filter. + example: Growing + in: query + name: category + required: false + schema: + type: string + - description: Project status filter. + example: Aktif + in: query + name: status + required: false + schema: + type: string + - description: Comma separated kandang ids. + example: 1,2 + in: query + name: kandang_id + required: false + schema: + type: string + - description: Transfer context filter. + example: transfer_to_laying + in: query + name: transfer_context + required: false + schema: + type: string responses: "200": content: @@ -2949,12 +4367,41 @@ paths: /api/production/project-flocks/kandangs/lookup: get: description: Read access to `/api/production/project-flocks/kandangs/lookup`. + parameters: + - description: Project flock id. + example: 1 + in: query + name: project_flock_id + required: true + schema: + type: integer + - description: Kandang id. + example: 1 + in: query + name: kandang_id + required: true + schema: + type: integer + - description: Include population value in response. + example: false + in: query + name: withpopulation + required: false + schema: + type: string + - description: Reference date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: record_date + required: false + schema: + type: string responses: "200": content: application/json: schema: - $ref: '#/components/schemas/PaginatedEnvelope' + $ref: '#/components/schemas/SuccessEnvelope' description: Successful response "401": content: @@ -3012,7 +4459,43 @@ paths: - Production /api/production/recordings/: get: - description: Read access to `/api/production/recordings/`. + description: Read access to `/api/production/recordings`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Project flock kandang id filter. + example: 1 + in: query + name: project_flock_kandang_id + required: false + schema: + type: integer + - description: Search keyword. + example: record + in: query + name: search + required: false + schema: + type: string + - description: Export mode. + example: excel + in: query + name: export + required: false + schema: + type: string responses: "200": content: @@ -3077,12 +4560,27 @@ paths: /api/production/recordings/next-day: get: description: Read access to `/api/production/recordings/next-day`. + parameters: + - description: Project flock kandang id. + example: 1 + in: query + name: project_flock_kandang_id + required: true + schema: + type: integer + - description: Recording date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: record_date + required: true + schema: + type: string responses: "200": content: application/json: schema: - $ref: '#/components/schemas/PaginatedEnvelope' + $ref: '#/components/schemas/SuccessEnvelope' description: Successful response "401": content: @@ -3104,7 +4602,64 @@ paths: - Production /api/production/transfer_layings/: get: - description: Read access to `/api/production/transfer_layings/`. + description: Read access to `/api/production/transfer_layings`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: transfer + in: query + name: search + required: false + schema: + type: string + - description: Start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: End date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Comma separated source flock ids. + example: 1,2 + in: query + name: flock_source + required: false + schema: + type: string + - description: Comma separated destination flock ids. + example: 3,4 + in: query + name: flock_destination + required: false + schema: + type: string + - description: Comma separated status values. + example: DRAFT,APPROVED + in: query + name: status + required: false + schema: + type: string responses: "200": content: @@ -3240,7 +4795,57 @@ paths: - Production /api/production/uniformities/: get: - description: Read access to `/api/production/uniformities/`. + description: Read access to `/api/production/uniformities`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Project flock kandang id filter. + example: 1 + in: query + name: project_flock_kandang_id + required: false + schema: + type: integer + - description: Week number filter. + example: 1 + in: query + name: week + required: false + schema: + type: integer + - description: Start date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: start_date + required: false + schema: + type: string + - description: End date (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: end_date + required: false + schema: + type: string + - description: Include chart payload. + example: false + in: query + name: with_chart + required: false + schema: + type: string responses: "200": content: @@ -3304,7 +4909,99 @@ paths: - Production /api/purchases/: get: - description: Read access to `/api/purchases/`. + description: Read access to `/api/purchases`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Supplier id filter. + example: 1 + in: query + name: supplier_id + required: false + schema: + type: integer + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Product category id filter. + example: "1" + in: query + name: product_category_id + required: false + schema: + type: string + - description: Approval status filter. + example: PENDING + in: query + name: approval_status + required: false + schema: + type: string + - description: PO date (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: po_date + required: false + schema: + type: string + - description: PO date start (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: po_date_from + required: false + schema: + type: string + - description: PO date end (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: po_date_to + required: false + schema: + type: string + - description: Search keyword. + example: PO- + in: query + name: search + required: false + schema: + type: string + - description: Created date start (YYYY-MM-DD). + example: "2026-01-01" + in: query + name: created_from + required: false + schema: + type: string + - description: Created date end (YYYY-MM-DD). + example: "2026-01-31" + in: query + name: created_to + required: false + schema: + type: string responses: "200": content: @@ -3405,6 +5102,13 @@ paths: required: false schema: type: string + - description: Date field filter. + example: TRANS_DATE + in: query + name: filter_by + required: false + schema: + type: string responses: "200": content: @@ -3469,6 +5173,20 @@ paths: required: false schema: type: string + - description: Date field filter. + example: received_date + in: query + name: filter_by + required: false + schema: + type: string + - description: Sort order. + example: asc + in: query + name: sort_order + required: false + schema: + type: string responses: "200": content: @@ -3533,6 +5251,27 @@ paths: required: false schema: type: integer + - description: Kandang id filter. + example: 1 + in: query + name: kandang_id + required: false + schema: + type: integer + - description: Project flock kandang id filter. + example: 1 + in: query + name: project_flock_kandang_id + required: false + schema: + type: integer + - description: Nonstock id filter. + example: 1 + in: query + name: nonstock_id + required: false + schema: + type: integer - description: Location id filter. example: 1 in: query @@ -3597,27 +5336,55 @@ paths: required: false schema: type: integer - - description: Daily period filter (YYYY-MM). - example: 2026-01 + - description: Daily period filter (YYYY-MM-DD). + example: "2026-01-01" in: query name: period required: false schema: type: string - - description: Location id filter. - example: 1 + - description: Include unrecorded data. + example: false + in: query + name: show_unrecorded + required: false + schema: + type: string + - description: Comma separated area ids. + example: 1,2 + in: query + name: area_id + required: false + schema: + type: string + - description: Comma separated location ids. + example: 1,2 in: query name: location_id required: false schema: - type: integer - - description: Kandang id filter. - example: 1 + type: string + - description: Comma separated kandang ids. + example: 1,2 in: query name: kandang_id required: false schema: - type: integer + type: string + - description: Minimum body weight filter. + example: "1.2" + in: query + name: weight_min + required: false + schema: + type: string + - description: Maximum body weight filter. + example: "1.8" + in: query + name: weight_max + required: false + schema: + type: string responses: "200": content: @@ -3661,6 +5428,69 @@ paths: required: false schema: type: integer + - description: Search keyword. + example: SO- + in: query + name: search + required: false + schema: + type: string + - description: Customer id filter. + example: 1 + in: query + name: customer_id + required: false + schema: + type: integer + - description: Product id filter. + example: 1 + in: query + name: product_id + required: false + schema: + type: integer + - description: Warehouse id filter. + example: 1 + in: query + name: warehouse_id + required: false + schema: + type: integer + - description: Sales person id filter. + example: 1 + in: query + name: sales_person_id + required: false + schema: + type: integer + - description: Area id filter. + example: 1 + in: query + name: area_id + required: false + schema: + type: integer + - description: Location id filter. + example: 1 + in: query + name: location_id + required: false + schema: + type: integer + - description: Marketing type filter. + example: ayam + in: query + name: marketing_type + required: false + schema: + type: string + - description: Date field filter. + example: so_date + in: query + name: filter_by + required: false + schema: + type: string - description: Period start date (YYYY-MM-DD). example: "2026-01-01" in: query @@ -3675,20 +5505,20 @@ paths: required: false schema: type: string - - description: Customer id filter. - example: 1 + - description: Sort field. + example: so_date in: query - name: customer_id + name: sort_by required: false schema: - type: integer - - description: Location id filter. - example: 1 + type: string + - description: Sort order. + example: asc in: query - name: location_id + name: sort_order required: false schema: - type: integer + type: string responses: "200": content: @@ -3725,6 +5555,20 @@ paths: schema: example: "1" type: string + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer responses: "200": content: @@ -3768,6 +5612,34 @@ paths: required: false schema: type: integer + - description: Comma separated area ids. + example: 1,2 + in: query + name: area_id + required: false + schema: + type: string + - description: Comma separated supplier ids. + example: 1,2 + in: query + name: supplier_id + required: false + schema: + type: string + - description: Comma separated product ids. + example: 1,2 + in: query + name: product_id + required: false + schema: + type: string + - description: Comma separated product category ids. + example: 1,2 + in: query + name: product_category_id + required: false + schema: + type: string - description: Period start date (YYYY-MM-DD). example: "2026-01-01" in: query @@ -3782,17 +5654,17 @@ paths: required: false schema: type: string - - description: Comma separated supplier ids. - example: 1,2 + - description: Sort field. + example: created_at in: query - name: supplier_id + name: sort_by required: false schema: type: string - - description: Comma separated area ids. - example: 1,2 + - description: Filter field. + example: received_date in: query - name: area_id + name: filter_by required: false schema: type: string @@ -3950,7 +5822,29 @@ paths: - SSO /api/users/: get: - description: Read access to `/api/users/`. + description: Read access to `/api/users`. + parameters: + - description: Page number. + example: 1 + in: query + name: page + required: false + schema: + type: integer + - description: Page size. + example: 10 + in: query + name: limit + required: false + schema: + type: integer + - description: Search keyword. + example: admin + in: query + name: search + required: false + schema: + type: string responses: "200": content: diff --git a/docs/postman/read-api.collection.json b/docs/postman/read-api.collection.json index 4b52da17..0ccdba5c 100644 --- a/docs/postman/read-api.collection.json +++ b/docs/postman/read-api.collection.json @@ -6,6 +6,24 @@ "item": [ { "item": [ + { + "item": [ + { + "name": "GET api / constants", + "request": { + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": "{{base_url}}/api/constants/" + } + } + ], + "name": "API" + }, { "item": [ { @@ -76,27 +94,9 @@ } ], "method": "GET", - "url": "{{base_url}}/api/approvals/" + "url": "{{base_url}}/api/approvals/?module_name=EXPENSES\u0026module_id=1\u0026group_step_number=false\u0026page=1\u0026limit=10\u0026search=approval\u0026order_by_date=DESC" } }, - { - "name": "GET api / constants", - "request": { - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "url": "{{base_url}}/api/constants/" - } - } - ], - "name": "API" - }, - { - "item": [ { "name": "GET api / closings", "request": { @@ -107,9 +107,14 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/" + "url": "{{base_url}}/api/closings/?page=1\u0026limit=10\u0026search=kandang\u0026project_status=1\u0026location_id={{location_id}}" } - }, + } + ], + "name": "API" + }, + { + "item": [ { "name": "GET api / closings / :projectFlockId", "request": { @@ -120,7 +125,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/{{projectFlockId}}" + "url": "{{base_url}}/api/closings/{{projectFlockId}}?kandang_id={{project_flock_kandang_id}}" } }, { @@ -133,7 +138,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/{{projectFlockId}}/keuangan" + "url": "{{base_url}}/api/closings/{{projectFlockId}}/keuangan?kandang_id={{project_flock_kandang_id}}" } }, { @@ -146,7 +151,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/{{projectFlockId}}/production-data" + "url": "{{base_url}}/api/closings/{{projectFlockId}}/production-data?kandang_id={{project_flock_kandang_id}}" } }, { @@ -159,7 +164,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/{{projectFlockId}}/sapronak?type=incoming" + "url": "{{base_url}}/api/closings/{{projectFlockId}}/sapronak?type=incoming\u0026page=1\u0026limit=10\u0026search=pakan\u0026kandang_id={{project_flock_kandang_id}}" } }, { @@ -172,7 +177,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/{{projectFlockId}}/sapronak/summary?type=incoming" + "url": "{{base_url}}/api/closings/{{projectFlockId}}/sapronak/summary?type=incoming\u0026search=pakan\u0026kandang_id={{project_flock_kandang_id}}" } }, { @@ -237,7 +242,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/perhitungan_sapronak" + "url": "{{base_url}}/api/closings/{{project_flock_id}}/{{project_flock_kandang_id}}/perhitungan_sapronak?flag=DOC" } }, { @@ -250,7 +255,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/{{project_flock_id}}/expedition-hpp" + "url": "{{base_url}}/api/closings/{{project_flock_id}}/expedition-hpp?project_flock_kandang_id={{project_flock_kandang_id}}" } }, { @@ -289,7 +294,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/closings/{{project_flock_id}}/perhitungan_sapronak" + "url": "{{base_url}}/api/closings/{{project_flock_id}}/perhitungan_sapronak?flag=DOC" } } ], @@ -307,7 +312,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/daily-checklists/" + "url": "{{base_url}}/api/daily-checklists/?page=1\u0026limit=10\u0026search=kebersihan\u0026date_from=2026-01-01\u0026date_to=2026-01-31\u0026status=done\u0026kandang_id={{idProjectFlockKandang}}" } }, { @@ -346,7 +351,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/daily-checklists/report" + "url": "{{base_url}}/api/daily-checklists/report?page=1\u0026limit=10\u0026bulan=1\u0026tahun=2026\u0026area_id={{area_id}}\u0026location_id={{location_id}}\u0026kandang_id={{idProjectFlockKandang}}\u0026employee_id={{employee_id}}\u0026phase_id={{id}}" } }, { @@ -359,7 +364,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/daily-checklists/summary" + "url": "{{base_url}}/api/daily-checklists/summary?date_from=2026-01-01\u0026date_to=2026-01-31\u0026category=cleaning\u0026kandang_id={{idProjectFlockKandang}}" } }, { @@ -372,7 +377,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/daily-checklists/tasks" + "url": "{{base_url}}/api/daily-checklists/tasks?checklist_id={{idDailyChecklist}}" } } ], @@ -390,7 +395,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/dashboards/" + "url": "{{base_url}}/api/dashboards/?page=1\u0026limit=10\u0026search=farm\u0026start_date=2026-01-01\u0026end_date=2026-01-31\u0026analysis_mode=OVERVIEW\u0026comparison_type=PREVIOUS_PERIOD\u0026metric=egg_mass\u0026location_ids=1,2\u0026flock_ids=1,2\u0026kandang_ids=1,2\u0026include=performance,summary" } } ], @@ -408,7 +413,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/expenses/" + "url": "{{base_url}}/api/expenses/?page=1\u0026limit=10\u0026search=operasional" } }, { @@ -478,7 +483,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/finance/transactions/" + "url": "{{base_url}}/api/finance/transactions/?page=1\u0026limit=10\u0026search=invoice\u0026bank_ids={{bank_id}}\u0026customer_ids=1,2\u0026supplier_ids=1,2\u0026transaction_types=payment,initial_balance\u0026sort_date=created_at\u0026start_date=2026-01-01\u0026end_date=2026-01-31" } }, { @@ -509,7 +514,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/inventory/adjustments/" + "url": "{{base_url}}/api/inventory/adjustments/?page=1\u0026limit=10\u0026product_id={{product_id}}\u0026warehouse_id={{warehouse_id}}\u0026transaction_type=IN\u0026transaction_subtype=ADJUSTMENT\u0026function_code=MANUAL_ADJUSTMENT" } }, { @@ -535,7 +540,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/inventory/product-stocks/" + "url": "{{base_url}}/api/inventory/product-stocks/?page=1\u0026limit=10\u0026search=pakan" } }, { @@ -561,7 +566,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/inventory/product-warehouses/" + "url": "{{base_url}}/api/inventory/product-warehouses/?page=1\u0026limit=10\u0026search=gudang\u0026product_id={{product_id}}\u0026warehouse_id={{warehouse_id}}\u0026location_id={{location_id}}\u0026flags=DOC\u0026kandang_id={{project_flock_kandang_id}}\u0026available_only=true\u0026transfer_context=inventory_transfer\u0026stock_mode=exclude_chickin\u0026type=incoming" } }, { @@ -587,7 +592,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/inventory/transfers/" + "url": "{{base_url}}/api/inventory/transfers/?page=1\u0026limit=10\u0026search=TRF" } }, { @@ -618,7 +623,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/marketing/" + "url": "{{base_url}}/api/marketing/?page=1\u0026limit=10\u0026search=delivery\u0026product_ids=1,2\u0026status=DRAFT\u0026customer_id={{customer_id}}\u0026marketing_id=1" } }, { @@ -649,7 +654,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/areas/" + "url": "{{base_url}}/api/master-data/areas/?page=1\u0026limit=10\u0026search=bandung" } }, { @@ -675,7 +680,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/banks/" + "url": "{{base_url}}/api/master-data/banks/?page=1\u0026limit=10\u0026search=bca" } }, { @@ -701,7 +706,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/config-checklists/" + "url": "{{base_url}}/api/master-data/config-checklists/?page=1\u0026limit=10\u0026search=harian" } }, { @@ -727,7 +732,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/customers/" + "url": "{{base_url}}/api/master-data/customers/?page=1\u0026limit=10\u0026search=pt\u0026has_marketing=true" } }, { @@ -753,7 +758,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/employees/" + "url": "{{base_url}}/api/master-data/employees/?page=1\u0026limit=10\u0026search=andi\u0026kandang_id={{project_flock_kandang_id}}\u0026is_active=true" } }, { @@ -779,7 +784,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/fcrs/" + "url": "{{base_url}}/api/master-data/fcrs/?page=1\u0026limit=10\u0026search=fcr" } }, { @@ -805,7 +810,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/flocks/" + "url": "{{base_url}}/api/master-data/flocks/?page=1\u0026limit=10\u0026search=layer" } }, { @@ -831,7 +836,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/kandang-groups/" + "url": "{{base_url}}/api/master-data/kandang-groups/?page=1\u0026limit=10\u0026search=blok\u0026location_id={{location_id}}\u0026pic_id={{employee_id}}" } }, { @@ -857,7 +862,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/kandangs/" + "url": "{{base_url}}/api/master-data/kandangs/?page=1\u0026limit=10\u0026search=kandang\u0026location_id={{location_id}}\u0026pic_id={{employee_id}}" } }, { @@ -883,7 +888,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/locations/" + "url": "{{base_url}}/api/master-data/locations/?page=1\u0026limit=10\u0026search=farm\u0026area_id={{area_id}}\u0026has_laying=false" } }, { @@ -909,7 +914,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/nonstocks/" + "url": "{{base_url}}/api/master-data/nonstocks/?page=1\u0026limit=10\u0026search=vitamin\u0026supplier_id={{supplier_id}}" } }, { @@ -935,7 +940,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/phase-activities/" + "url": "{{base_url}}/api/master-data/phase-activities/?page=1\u0026limit=10\u0026search=cek pakan\u0026phase_ids=1,2" } }, { @@ -961,7 +966,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/phases/" + "url": "{{base_url}}/api/master-data/phases/?page=1\u0026limit=10\u0026search=starter\u0026category=Growing" } }, { @@ -987,7 +992,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/product-categories/" + "url": "{{base_url}}/api/master-data/product-categories/?page=1\u0026limit=10\u0026search=pakan" } }, { @@ -1013,7 +1018,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/production-standards/" + "url": "{{base_url}}/api/master-data/production-standards/?page=1\u0026limit=10\u0026search=standar\u0026project_category=GROWING" } }, { @@ -1039,7 +1044,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/products/" + "url": "{{base_url}}/api/master-data/products/?page=1\u0026limit=10\u0026search=jagung\u0026product_category_id={{product_category_id}}\u0026is_depletion=false\u0026include_all=false" } }, { @@ -1065,7 +1070,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/suppliers/" + "url": "{{base_url}}/api/master-data/suppliers/?page=1\u0026limit=10\u0026search=supplier\u0026flag=ACTIVE\u0026category=PAKAN" } }, { @@ -1091,7 +1096,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/uoms/" + "url": "{{base_url}}/api/master-data/uoms/?page=1\u0026limit=10\u0026search=kg" } }, { @@ -1117,7 +1122,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/master-data/warehouses/" + "url": "{{base_url}}/api/master-data/warehouses/?page=1\u0026limit=10\u0026search=gudang\u0026area_id={{area_id}}\u0026location_id={{location_id}}\u0026active_project_flock=false\u0026transfer_context=inventory_transfer" } }, { @@ -1161,7 +1166,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/production/project-flock-kandangs/" + "url": "{{base_url}}/api/production/project-flock-kandangs/?page=1\u0026limit=10\u0026search=kandang\u0026name_with_periode=false\u0026project_flock_id={{project_flock_id}}\u0026kandang_id={{project_flock_kandang_id}}\u0026category=Growing\u0026area_id={{area_id}}\u0026location_id={{location_id}}\u0026sort_by=created_at\u0026sort_order=ASC\u0026step_name=Pengajuan" } }, { @@ -1200,7 +1205,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/production/project-flocks/" + "url": "{{base_url}}/api/production/project-flocks/?page=1\u0026limit=10\u0026search=flock\u0026sort_by=created_at\u0026sort_order=asc\u0026area_id={{area_id}}\u0026location_id={{location_id}}\u0026period=1\u0026category=Growing\u0026status=Aktif\u0026kandang_id=1,2\u0026transfer_context=transfer_to_laying" } }, { @@ -1226,7 +1231,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/production/project-flocks/kandangs/lookup" + "url": "{{base_url}}/api/production/project-flocks/kandangs/lookup?project_flock_id={{project_flock_id}}\u0026kandang_id={{project_flock_kandang_id}}\u0026withpopulation=false\u0026record_date=2026-01-01" } }, { @@ -1252,7 +1257,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/production/recordings/" + "url": "{{base_url}}/api/production/recordings/?page=1\u0026limit=10\u0026project_flock_kandang_id={{project_flock_kandang_id}}\u0026search=record\u0026export=excel" } }, { @@ -1278,7 +1283,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/production/recordings/next-day" + "url": "{{base_url}}/api/production/recordings/next-day?project_flock_kandang_id={{project_flock_kandang_id}}\u0026record_date=2026-01-01" } }, { @@ -1291,7 +1296,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/production/transfer_layings/" + "url": "{{base_url}}/api/production/transfer_layings/?page=1\u0026limit=10\u0026search=transfer\u0026start_date=2026-01-01\u0026end_date=2026-01-31\u0026flock_source=1,2\u0026flock_destination=3,4\u0026status=DRAFT,APPROVED" } }, { @@ -1343,7 +1348,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/production/uniformities/" + "url": "{{base_url}}/api/production/uniformities/?page=1\u0026limit=10\u0026project_flock_kandang_id={{project_flock_kandang_id}}\u0026week=1\u0026start_date=2026-01-01\u0026end_date=2026-01-31\u0026with_chart=false" } }, { @@ -1374,7 +1379,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/purchases/" + "url": "{{base_url}}/api/purchases/?page=1\u0026limit=10\u0026supplier_id={{supplier_id}}\u0026area_id={{area_id}}\u0026location_id={{location_id}}\u0026product_category_id=1\u0026approval_status=PENDING\u0026po_date=2026-01-01\u0026po_date_from=2026-01-01\u0026po_date_to=2026-01-31\u0026search=PO-\u0026created_from=2026-01-01\u0026created_to=2026-01-31" } }, { @@ -1405,7 +1410,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/reports/customer-payment?customer_ids={{customer_id}}" + "url": "{{base_url}}/api/reports/customer-payment?page=1\u0026limit=10\u0026start_date=2026-01-01\u0026end_date=2026-01-31\u0026customer_ids={{customer_id}}\u0026filter_by=TRANS_DATE" } }, { @@ -1418,7 +1423,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/reports/debt-supplier?supplier_ids={{supplier_id}}" + "url": "{{base_url}}/api/reports/debt-supplier?page=1\u0026limit=10\u0026start_date=2026-01-01\u0026end_date=2026-01-31\u0026supplier_ids={{supplier_id}}\u0026filter_by=received_date\u0026sort_order=asc" } }, { @@ -1431,7 +1436,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/reports/expense" + "url": "{{base_url}}/api/reports/expense?page=1\u0026limit=10\u0026search=operasional\u0026category=BOP\u0026supplier_id=1\u0026kandang_id=1\u0026project_flock_kandang_id=1\u0026nonstock_id=1\u0026location_id=1\u0026area_id=1\u0026realization_date=2026-01-15" } }, { @@ -1444,7 +1449,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/reports/hpp-per-kandang" + "url": "{{base_url}}/api/reports/hpp-per-kandang?page=1\u0026limit=10\u0026period=2026-01-01\u0026show_unrecorded=false\u0026area_id=1,2\u0026location_id=1,2\u0026kandang_id=1,2\u0026weight_min=1.2\u0026weight_max=1.8" } }, { @@ -1457,7 +1462,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/reports/marketing" + "url": "{{base_url}}/api/reports/marketing?page=1\u0026limit=10\u0026search=SO-\u0026customer_id=1\u0026product_id=1\u0026warehouse_id=1\u0026sales_person_id=1\u0026area_id=1\u0026location_id=1\u0026marketing_type=ayam\u0026filter_by=so_date\u0026start_date=2026-01-01\u0026end_date=2026-01-31\u0026sort_by=so_date\u0026sort_order=asc" } }, { @@ -1470,7 +1475,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/reports/production-result/{{idProjectFlockKandang}}" + "url": "{{base_url}}/api/reports/production-result/{{idProjectFlockKandang}}?page=1\u0026limit=10" } }, { @@ -1483,7 +1488,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/reports/purchase-supplier" + "url": "{{base_url}}/api/reports/purchase-supplier?page=1\u0026limit=10\u0026area_id=1,2\u0026supplier_id=1,2\u0026product_id=1,2\u0026product_category_id=1,2\u0026start_date=2026-01-01\u0026end_date=2026-01-31\u0026sort_by=created_at\u0026filter_by=received_date" } } ], @@ -1501,7 +1506,7 @@ } ], "method": "GET", - "url": "{{base_url}}/api/users/" + "url": "{{base_url}}/api/users/?page=1\u0026limit=10\u0026search=admin" } }, { diff --git a/internal/apikeys/service.go b/internal/apikeys/service.go index 5059b18a..4b6a05a9 100644 --- a/internal/apikeys/service.go +++ b/internal/apikeys/service.go @@ -193,7 +193,7 @@ func randomToken(size int) (string, error) { func canonicalPermissions(perms []string) []string { if len(perms) == 0 { - return nil + return []string{} } seen := make(map[string]struct{}, len(perms)) @@ -214,7 +214,7 @@ func canonicalPermissions(perms []string) []string { func uniqueUint(values []uint) []uint { if len(values) == 0 { - return nil + return []uint{} } seen := make(map[uint]struct{}, len(values)) diff --git a/internal/database/migrations/20260414090000_create_integration_api_keys.down.sql b/internal/database/migrations/20260414082821_create_integration_api_keys.down.sql similarity index 100% rename from internal/database/migrations/20260414090000_create_integration_api_keys.down.sql rename to internal/database/migrations/20260414082821_create_integration_api_keys.down.sql diff --git a/internal/database/migrations/20260414090000_create_integration_api_keys.up.sql b/internal/database/migrations/20260414082821_create_integration_api_keys.up.sql similarity index 100% rename from internal/database/migrations/20260414090000_create_integration_api_keys.up.sql rename to internal/database/migrations/20260414082821_create_integration_api_keys.up.sql diff --git a/internal/readapi/readapi.go b/internal/readapi/readapi.go index ba6651b8..2ae62472 100644 --- a/internal/readapi/readapi.go +++ b/internal/readapi/readapi.go @@ -476,10 +476,11 @@ func buildPostmanURL(route normalizedRoute, meta routeMeta) string { query := make([]string, 0, len(meta.QueryParams)) for _, param := range meta.QueryParams { - if !param.IncludePostman { - continue + value := strings.TrimSpace(param.PostmanValue) + if value == "" && param.Example != nil { + value = fmt.Sprintf("%v", param.Example) } - query = append(query, fmt.Sprintf("%s=%v", param.Name, param.PostmanValue)) + query = append(query, fmt.Sprintf("%s=%v", param.Name, value)) } if len(query) == 0 { @@ -552,31 +553,103 @@ func buildPostmanEnvironment(routes []normalizedRoute) map[string]any { } func describeRoute(route normalizedRoute) routeMeta { + routePath := route.Path + if len(routePath) > 1 { + routePath = strings.TrimSuffix(routePath, "/") + } + meta := routeMeta{ Group: "Dashboard API Key", - Tag: inferTag(route.Path), - Summary: defaultSummary(route.Path), - Description: fmt.Sprintf("Read access to `%s`.", route.Path), + Tag: inferTag(routePath), + Summary: defaultSummary(routePath), + Description: fmt.Sprintf("Read access to `%s`.", routePath), Security: securityAPIOrBearer, - ListStyle: !strings.Contains(route.Path, ":"), + ListStyle: !strings.Contains(routePath, ":"), } switch { - case route.Path == "/healthz" || route.Path == "/readyz" || route.Path == "/api/constants": + case routePath == "/healthz" || routePath == "/readyz" || routePath == "/api/constants": meta.Group = "Public" meta.Security = securityNone meta.ListStyle = false - case strings.HasPrefix(route.Path, "/api/sso/"): + case strings.HasPrefix(routePath, "/api/sso/"): meta.Group = "Internal/OAuth Reference" meta.ListStyle = false - if route.Path == "/api/sso/userinfo" { + if routePath == "/api/sso/userinfo" { meta.Security = securityBearer } else { meta.Security = securityNone } } - switch route.Path { + switch routePath { + case "/api/approvals": + meta.QueryParams = []parameterMeta{ + {Name: "module_name", In: "query", Description: "Approval workflow module name.", Required: true, Example: "EXPENSES", PostmanValue: "EXPENSES"}, + {Name: "module_id", In: "query", Description: "Optional approvable module id.", Example: 1}, + {Name: "group_step_number", In: "query", Description: "Group approval records by step number.", Example: false}, + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "approval"}, + {Name: "order_by_date", In: "query", Description: "Sort direction by date.", Example: "DESC"}, + } + case "/api/daily-checklists": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "kebersihan"}, + {Name: "date_from", In: "query", Description: "Start date filter (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "date_to", In: "query", Description: "End date filter (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "status", In: "query", Description: "Checklist status filter.", Example: "done"}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1, PostmanValue: "{{idProjectFlockKandang}}"}, + } + case "/api/daily-checklists/report": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Required: true, Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Required: true, Example: 10}, + {Name: "bulan", In: "query", Description: "Month number (1-12).", Required: true, Example: 1}, + {Name: "tahun", In: "query", Description: "Year.", Required: true, Example: 2026}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1, PostmanValue: "{{area_id}}"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1, PostmanValue: "{{idProjectFlockKandang}}"}, + {Name: "employee_id", In: "query", Description: "Employee id filter.", Example: 1, PostmanValue: "{{employee_id}}"}, + {Name: "phase_id", In: "query", Description: "Phase id filter.", Example: 1, PostmanValue: "{{id}}"}, + } + case "/api/daily-checklists/summary": + meta.QueryParams = []parameterMeta{ + {Name: "date_from", In: "query", Description: "Start date filter (YYYY-MM-DD).", Required: true, Example: "2026-01-01"}, + {Name: "date_to", In: "query", Description: "End date filter (YYYY-MM-DD).", Required: true, Example: "2026-01-31"}, + {Name: "category", In: "query", Description: "Checklist category filter.", Example: "cleaning"}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1, PostmanValue: "{{idProjectFlockKandang}}"}, + } + case "/api/daily-checklists/tasks": + meta.QueryParams = []parameterMeta{ + {Name: "checklist_id", In: "query", Description: "Daily checklist id.", Required: true, Example: 1, PostmanValue: "{{idDailyChecklist}}"}, + } + case "/api/closings": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "kandang"}, + {Name: "project_status", In: "query", Description: "Project status filter (1 or 2).", Example: 1}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + } + case "/api/closings/:projectFlockId": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, + } + case "/api/closings/:project_flock_id/perhitungan_sapronak", "/api/closings/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "flag", In: "query", Description: "Product category flag filter (DOC/OVK/PAKAN/PULLET).", Example: "DOC"}, + } + case "/api/expenses": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "operasional"}, + } case "/api/dashboards": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, @@ -592,22 +665,309 @@ func describeRoute(route normalizedRoute) routeMeta { {Name: "kandang_ids", In: "query", Description: "Comma separated kandang ids.", Example: "1,2"}, {Name: "include", In: "query", Description: "Comma separated dashboard sections to include.", Example: "performance,summary"}, } - case "/api/closings/:projectFlockId/sapronak", "/api/closings/:projectFlockId/sapronak/summary": - meta.ListStyle = route.Path == "/api/closings/:projectFlockId/sapronak" + case "/api/closings/:projectFlockId/sapronak": + meta.ListStyle = true meta.QueryParams = []parameterMeta{ - {Name: "type", In: "query", Description: "Required sapronak direction.", Required: true, Example: "incoming", PostmanValue: "incoming", IncludePostman: true}, + {Name: "type", In: "query", Description: "Required sapronak direction.", Required: true, Example: "incoming", PostmanValue: "incoming"}, + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "search", In: "query", Description: "Search keyword.", Example: "pakan"}, - {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1}, + {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, + } + case "/api/closings/:projectFlockId/sapronak/summary": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "type", In: "query", Description: "Required sapronak direction.", Required: true, Example: "incoming", PostmanValue: "incoming"}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "pakan"}, + {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, } case "/api/closings/:projectFlockId/production-data", "/api/closings/:projectFlockId/keuangan": meta.ListStyle = false meta.QueryParams = []parameterMeta{ - {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1}, + {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, } case "/api/closings/:project_flock_id/expedition-hpp": meta.ListStyle = false meta.QueryParams = []parameterMeta{ - {Name: "project_flock_kandang_id", In: "query", Description: "Optional project flock kandang id filter.", Example: 1}, + {Name: "project_flock_kandang_id", In: "query", Description: "Optional project flock kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, + } + case "/api/inventory/adjustments": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "product_id", In: "query", Description: "Product id filter.", Example: 1, PostmanValue: "{{product_id}}"}, + {Name: "warehouse_id", In: "query", Description: "Warehouse id filter.", Example: 1, PostmanValue: "{{warehouse_id}}"}, + {Name: "transaction_type", In: "query", Description: "Transaction type filter.", Example: "IN"}, + {Name: "transaction_subtype", In: "query", Description: "Transaction subtype filter.", Example: "ADJUSTMENT"}, + {Name: "function_code", In: "query", Description: "Function code filter.", Example: "MANUAL_ADJUSTMENT"}, + } + case "/api/inventory/product-stocks": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "pakan"}, + } + case "/api/inventory/product-warehouses": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "gudang"}, + {Name: "product_id", In: "query", Description: "Product id filter.", Example: 1, PostmanValue: "{{product_id}}"}, + {Name: "warehouse_id", In: "query", Description: "Warehouse id filter.", Example: 1, PostmanValue: "{{warehouse_id}}"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + {Name: "flags", In: "query", Description: "Stock flags filter.", Example: "DOC"}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, + {Name: "available_only", In: "query", Description: "Show available stock only.", Example: true}, + {Name: "transfer_context", In: "query", Description: "Transfer context filter.", Example: "inventory_transfer"}, + {Name: "stock_mode", In: "query", Description: "Stock mode filter.", Example: "exclude_chickin"}, + {Name: "type", In: "query", Description: "Warehouse stock type filter.", Example: "incoming"}, + } + case "/api/inventory/transfers": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "TRF"}, + } + case "/api/marketing": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "delivery"}, + {Name: "product_ids", In: "query", Description: "Comma separated product ids.", Example: "1,2"}, + {Name: "status", In: "query", Description: "Delivery status filter.", Example: "DRAFT"}, + {Name: "customer_id", In: "query", Description: "Customer id filter.", Example: 1, PostmanValue: "{{customer_id}}"}, + {Name: "marketing_id", In: "query", Description: "Marketing id filter.", Example: 1}, + } + case "/api/master-data/areas": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "bandung"}, + } + case "/api/master-data/banks": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "bca"}, + } + case "/api/master-data/config-checklists": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "harian"}, + } + case "/api/master-data/customers": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "pt"}, + {Name: "has_marketing", In: "query", Description: "Filter customer by marketing relation.", Example: true}, + } + case "/api/master-data/employees": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "andi"}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, + {Name: "is_active", In: "query", Description: "Active status filter.", Example: true}, + } + case "/api/master-data/fcrs": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "fcr"}, + } + case "/api/master-data/flocks": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "layer"}, + } + case "/api/master-data/kandang-groups": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "blok"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + {Name: "pic_id", In: "query", Description: "Person in charge id filter.", Example: 1, PostmanValue: "{{employee_id}}"}, + } + case "/api/master-data/kandangs": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "kandang"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + {Name: "pic_id", In: "query", Description: "Person in charge id filter.", Example: 1, PostmanValue: "{{employee_id}}"}, + } + case "/api/master-data/locations": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "farm"}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1, PostmanValue: "{{area_id}}"}, + {Name: "has_laying", In: "query", Description: "Filter laying locations only.", Example: false}, + } + case "/api/master-data/nonstocks": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "vitamin"}, + {Name: "supplier_id", In: "query", Description: "Supplier id filter.", Example: 1, PostmanValue: "{{supplier_id}}"}, + } + case "/api/master-data/phase-activities": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "cek pakan"}, + {Name: "phase_ids", In: "query", Description: "Comma separated phase ids.", Example: "1,2"}, + } + case "/api/master-data/phases": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "starter"}, + {Name: "category", In: "query", Description: "Phase category filter.", Example: "Growing"}, + } + case "/api/master-data/product-categories": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "pakan"}, + } + case "/api/master-data/production-standards": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "standar"}, + {Name: "project_category", In: "query", Description: "Project category filter.", Example: "GROWING"}, + } + case "/api/master-data/products": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "jagung"}, + {Name: "product_category_id", In: "query", Description: "Product category id filter.", Example: 1, PostmanValue: "{{product_category_id}}"}, + {Name: "is_depletion", In: "query", Description: "Filter depletion products.", Example: false}, + {Name: "include_all", In: "query", Description: "Include all products regardless of status.", Example: false}, + } + case "/api/master-data/suppliers": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "supplier"}, + {Name: "flag", In: "query", Description: "Supplier type flag filter.", Example: "ACTIVE"}, + {Name: "category", In: "query", Description: "Supplier category filter.", Example: "PAKAN"}, + } + case "/api/master-data/uoms": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "kg"}, + } + case "/api/master-data/warehouses": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "gudang"}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1, PostmanValue: "{{area_id}}"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + {Name: "active_project_flock", In: "query", Description: "Filter only active project flock warehouses.", Example: false}, + {Name: "transfer_context", In: "query", Description: "Transfer context filter.", Example: "inventory_transfer"}, + } + case "/api/production/project-flock-kandangs": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "kandang"}, + {Name: "name_with_periode", In: "query", Description: "Return name-with-period projection.", Example: false}, + {Name: "project_flock_id", In: "query", Description: "Project flock id filter.", Example: 1, PostmanValue: "{{project_flock_id}}"}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, + {Name: "category", In: "query", Description: "Project category filter.", Example: "Growing"}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1, PostmanValue: "{{area_id}}"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + {Name: "sort_by", In: "query", Description: "Sort field.", Example: "created_at"}, + {Name: "sort_order", In: "query", Description: "Sort order.", Example: "ASC"}, + {Name: "step_name", In: "query", Description: "Approval step name filter.", Example: "Pengajuan"}, + } + case "/api/production/project-flocks": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "flock"}, + {Name: "sort_by", In: "query", Description: "Sort field.", Example: "created_at"}, + {Name: "sort_order", In: "query", Description: "Sort order.", Example: "asc"}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1, PostmanValue: "{{area_id}}"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + {Name: "period", In: "query", Description: "Project period filter.", Example: 1}, + {Name: "category", In: "query", Description: "Project category filter.", Example: "Growing"}, + {Name: "status", In: "query", Description: "Project status filter.", Example: "Aktif"}, + {Name: "kandang_id", In: "query", Description: "Comma separated kandang ids.", Example: "1,2"}, + {Name: "transfer_context", In: "query", Description: "Transfer context filter.", Example: "transfer_to_laying"}, + } + case "/api/production/project-flocks/kandangs/lookup": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "project_flock_id", In: "query", Description: "Project flock id.", Required: true, Example: 1, PostmanValue: "{{project_flock_id}}"}, + {Name: "kandang_id", In: "query", Description: "Kandang id.", Required: true, Example: 1, PostmanValue: "{{project_flock_kandang_id}}"}, + {Name: "withpopulation", In: "query", Description: "Include population value in response.", Example: false}, + {Name: "record_date", In: "query", Description: "Reference date (YYYY-MM-DD).", Example: "2026-01-01"}, + } + case "/api/production/recordings": + 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}}"}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "record"}, + {Name: "export", In: "query", Description: "Export mode.", Example: "excel"}, + } + case "/api/production/recordings/next-day": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {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/transfer_layings": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "transfer"}, + {Name: "start_date", In: "query", Description: "Start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "End date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "flock_source", In: "query", Description: "Comma separated source flock ids.", Example: "1,2"}, + {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"}, + } + case "/api/production/uniformities": + 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}}"}, + {Name: "week", In: "query", Description: "Week number filter.", Example: 1}, + {Name: "start_date", In: "query", Description: "Start date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "end_date", In: "query", Description: "End date (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "with_chart", In: "query", Description: "Include chart payload.", Example: false}, + } + case "/api/purchases": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "supplier_id", In: "query", Description: "Supplier id filter.", Example: 1, PostmanValue: "{{supplier_id}}"}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1, PostmanValue: "{{area_id}}"}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1, PostmanValue: "{{location_id}}"}, + {Name: "product_category_id", In: "query", Description: "Product category id filter.", Example: "1"}, + {Name: "approval_status", In: "query", Description: "Approval status filter.", Example: "PENDING"}, + {Name: "po_date", In: "query", Description: "PO date (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "po_date_from", In: "query", Description: "PO date start (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "po_date_to", In: "query", Description: "PO date end (YYYY-MM-DD).", Example: "2026-01-31"}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "PO-"}, + {Name: "created_from", In: "query", Description: "Created date start (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "created_to", In: "query", Description: "Created date end (YYYY-MM-DD).", Example: "2026-01-31"}, + } + case "/api/users": + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "admin"}, } case "/api/reports/expense": meta.QueryParams = []parameterMeta{ @@ -616,6 +976,9 @@ func describeRoute(route normalizedRoute) routeMeta { {Name: "search", In: "query", Description: "Search keyword.", Example: "operasional"}, {Name: "category", In: "query", Description: "Expense category filter.", Example: "BOP"}, {Name: "supplier_id", In: "query", Description: "Supplier id filter.", Example: 1}, + {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1}, + {Name: "project_flock_kandang_id", In: "query", Description: "Project flock kandang id filter.", Example: 1}, + {Name: "nonstock_id", In: "query", Description: "Nonstock id filter.", Example: 1}, {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1}, {Name: "realization_date", In: "query", Description: "Realization date filter (YYYY-MM-DD).", Example: "2026-01-15"}, @@ -624,19 +987,32 @@ func describeRoute(route normalizedRoute) routeMeta { meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "search", In: "query", Description: "Search keyword.", Example: "SO-"}, + {Name: "customer_id", In: "query", Description: "Customer id filter.", Example: 1}, + {Name: "product_id", In: "query", Description: "Product id filter.", Example: 1}, + {Name: "warehouse_id", In: "query", Description: "Warehouse id filter.", Example: 1}, + {Name: "sales_person_id", In: "query", Description: "Sales person id filter.", Example: 1}, + {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1}, + {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, + {Name: "marketing_type", In: "query", Description: "Marketing type filter.", Example: "ayam"}, + {Name: "filter_by", In: "query", Description: "Date field filter.", Example: "so_date"}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, - {Name: "customer_id", In: "query", Description: "Customer id filter.", Example: 1}, - {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, + {Name: "sort_by", In: "query", Description: "Sort field.", Example: "so_date"}, + {Name: "sort_order", In: "query", Description: "Sort order.", Example: "asc"}, } case "/api/reports/purchase-supplier": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, + {Name: "area_id", In: "query", Description: "Comma separated area ids.", Example: "1,2"}, + {Name: "supplier_id", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, + {Name: "product_id", In: "query", Description: "Comma separated product ids.", Example: "1,2"}, + {Name: "product_category_id", In: "query", Description: "Comma separated product category ids.", Example: "1,2"}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, - {Name: "supplier_id", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, - {Name: "area_id", In: "query", Description: "Comma separated area ids.", Example: "1,2"}, + {Name: "sort_by", In: "query", Description: "Sort field.", Example: "created_at"}, + {Name: "filter_by", In: "query", Description: "Filter field.", Example: "received_date"}, } case "/api/reports/debt-supplier": meta.QueryParams = []parameterMeta{ @@ -644,7 +1020,9 @@ func describeRoute(route normalizedRoute) routeMeta { {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, - {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2", PostmanValue: "{{supplier_id}}", IncludePostman: true}, + {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2", PostmanValue: "{{supplier_id}}"}, + {Name: "filter_by", In: "query", Description: "Date field filter.", Example: "received_date"}, + {Name: "sort_order", In: "query", Description: "Sort order.", Example: "asc"}, } case "/api/reports/customer-payment": meta.QueryParams = []parameterMeta{ @@ -652,34 +1030,46 @@ func describeRoute(route normalizedRoute) routeMeta { {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, - {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2", PostmanValue: "{{customer_id}}", IncludePostman: true}, + {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2", PostmanValue: "{{customer_id}}"}, + {Name: "filter_by", In: "query", Description: "Date field filter.", Example: "TRANS_DATE"}, } case "/api/reports/hpp-per-kandang": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, - {Name: "period", In: "query", Description: "Daily period filter (YYYY-MM).", Example: "2026-01"}, - {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, - {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1}, + {Name: "period", In: "query", Description: "Daily period filter (YYYY-MM-DD).", Example: "2026-01-01"}, + {Name: "show_unrecorded", In: "query", Description: "Include unrecorded data.", Example: false}, + {Name: "area_id", In: "query", Description: "Comma separated area ids.", Example: "1,2"}, + {Name: "location_id", In: "query", Description: "Comma separated location ids.", Example: "1,2"}, + {Name: "kandang_id", In: "query", Description: "Comma separated kandang ids.", Example: "1,2"}, + {Name: "weight_min", In: "query", Description: "Minimum body weight filter.", Example: "1.2"}, + {Name: "weight_max", In: "query", Description: "Maximum body weight filter.", Example: "1.8"}, + } + case "/api/reports/production-result/:idProjectFlockKandang": + meta.ListStyle = false + meta.QueryParams = []parameterMeta{ + {Name: "page", In: "query", Description: "Page number.", Example: 1}, + {Name: "limit", In: "query", Description: "Page size.", Example: 10}, } case "/api/finance/transactions": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "search", In: "query", Description: "Search keyword.", Example: "invoice"}, - {Name: "bank_ids", In: "query", Description: "Comma separated bank ids.", Example: "1,2", PostmanValue: "{{bank_id}}", IncludePostman: true}, + {Name: "bank_ids", In: "query", Description: "Comma separated bank ids.", Example: "1,2", PostmanValue: "{{bank_id}}"}, {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2"}, {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, {Name: "transaction_types", In: "query", Description: "Comma separated transaction types.", Example: "payment,initial_balance"}, + {Name: "sort_date", In: "query", Description: "Sort date basis.", Example: "created_at"}, {Name: "start_date", In: "query", Description: "Start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "End date (YYYY-MM-DD).", Example: "2026-01-31"}, } } - if route.Path == "/healthz" { + if routePath == "/healthz" { meta.Summary = "Health check" meta.Description = "Simple liveness probe." - } else if route.Path == "/readyz" { + } else if routePath == "/readyz" { meta.Summary = "Readiness check" meta.Description = "Readiness probe for database and Redis." } From ef71093b990303fd68d14075e608a9ee821d4703 Mon Sep 17 00:00:00 2001 From: giovanni Date: Wed, 15 Apr 2026 11:22:02 +0700 Subject: [PATCH 7/8] fix value null to string vehicle_number field --- internal/modules/purchases/repositories/purchase.repository.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/modules/purchases/repositories/purchase.repository.go b/internal/modules/purchases/repositories/purchase.repository.go index 2f5818c4..9fdc9ebf 100644 --- a/internal/modules/purchases/repositories/purchase.repository.go +++ b/internal/modules/purchases/repositories/purchase.repository.go @@ -248,7 +248,7 @@ func (r *PurchaseRepositoryImpl) UpdateReceivingDetails( if upd.VehicleNumber != nil { data["vehicle_number"] = upd.VehicleNumber } else if upd.ClearVehicleNumber { - data["vehicle_number"] = gorm.Expr("NULL") + data["vehicle_number"] = "" } if upd.WarehouseID != nil && *upd.WarehouseID != 0 { data["warehouse_id"] = upd.WarehouseID From f808b5cf7988af617752b209bc0fdb74880bd69a Mon Sep 17 00:00:00 2001 From: giovanni Date: Fri, 17 Apr 2026 14:36:04 +0700 Subject: [PATCH 8/8] adjust validation limit recording --- .../production/recordings/validations/recording.validation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/modules/production/recordings/validations/recording.validation.go b/internal/modules/production/recordings/validations/recording.validation.go index 2e6ebbdb..da2ca38f 100644 --- a/internal/modules/production/recordings/validations/recording.validation.go +++ b/internal/modules/production/recordings/validations/recording.validation.go @@ -37,7 +37,7 @@ type Update struct { type Query struct { Page int `query:"page" validate:"omitempty,number,min=1"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` + Limit int `query:"limit" validate:"omitempty,number,min=1"` Offset int `query:"-" validate:"omitempty,number,min=0"` ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"` Search string `query:"search" validate:"omitempty,max=50"`