Files
lti-api/internal/readapi/readapi.go
T
Adnan Zahir 3768892a17 fix: resolve dashboard OpenAPI integration issues
- FCRs & Transfer to Laying: add ExampleResponse field to routeMeta and
  inject example payloads into OpenAPI 200 responses for list and detail
  endpoints so dashboard consumers have concrete response shapes to work with

- Chick In: enable GET /api/production/chickins/ list endpoint (was
  commented out); add P_ChickinsGetAll permission constant and wire it
  into the route; add OpenAPI spec entry with query params and example

- Recording GET all: fix N+1 query bottleneck (2-3s response time) by
  pre-fetching approved transfer maps per PFK ID in two batch queries
  before the per-recording loop; add evaluatePopulationMutationStateFromCaches
  that uses the pre-fetched maps and caches hasAnyRecordingOnTransferTargets
  results by transfer ID — reducing per-page query count from ~20-40 to ~10-12

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 10:57:45 +07:00

1341 lines
59 KiB
Go

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
ExampleResponse any
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)
responseContent := map[string]any{
"schema": successSchema(meta),
}
if meta.ExampleResponse != nil {
responseContent["example"] = meta.ExampleResponse
}
operation := map[string]any{
"summary": meta.Summary,
"description": meta.Description,
"tags": []string{meta.Tag},
"responses": map[string]any{
"200": map[string]any{
"description": "Successful response",
"content": map[string]any{
"application/json": responseContent,
},
},
"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 {
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, value))
}
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 {
routePath := route.Path
if len(routePath) > 1 {
routePath = strings.TrimSuffix(routePath, "/")
}
meta := routeMeta{
Group: "Dashboard API Key",
Tag: inferTag(routePath),
Summary: defaultSummary(routePath),
Description: fmt.Sprintf("Read access to `%s`.", routePath),
Security: securityAPIOrBearer,
ListStyle: !strings.Contains(routePath, ":"),
}
switch {
case routePath == "/healthz" || routePath == "/readyz" || routePath == "/api/constants":
meta.Group = "Public"
meta.Security = securityNone
meta.ListStyle = false
case strings.HasPrefix(routePath, "/api/sso/"):
meta.Group = "Internal/OAuth Reference"
meta.ListStyle = false
if routePath == "/api/sso/userinfo" {
meta.Security = securityBearer
} else {
meta.Security = securityNone
}
}
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},
{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":
meta.ListStyle = true
meta.QueryParams = []parameterMeta{
{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, 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, 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, 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"},
}
meta.ExampleResponse = map[string]any{
"code": 200, "status": "success", "message": "Get all fcrs successfully",
"meta": map[string]any{"page": 1, "limit": 10, "total_pages": 1, "total_results": 1},
"data": []map[string]any{
{
"id": 1, "name": "FCR Broiler Standard",
"created_user": map[string]any{"id": 1, "name": "Admin"},
"created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z",
},
},
}
case "/api/master-data/fcrs/:id":
meta.ExampleResponse = map[string]any{
"code": 200, "status": "success", "message": "Get fcr successfully",
"data": map[string]any{
"id": 1, "name": "FCR Broiler Standard",
"created_user": map[string]any{"id": 1, "name": "Admin"},
"created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z",
"fcr_standards": []map[string]any{
{"id": 1, "weight": 0.5, "fcr_number": 1.2, "mortality": 0.5},
{"id": 2, "weight": 1.0, "fcr_number": 1.35, "mortality": 0.3},
{"id": 3, "weight": 1.5, "fcr_number": 1.5, "mortality": 0.25},
},
},
}
case "/api/master-data/flocks":
meta.QueryParams = []parameterMeta{
{Name: "page", In: "query", Description: "Page number.", Example: 1},
{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/chickins":
meta.QueryParams = []parameterMeta{
{Name: "page", In: "query", Description: "Page number.", Example: 1},
{Name: "limit", In: "query", Description: "Page size.", Example: 10},
{Name: "project_flock_kandang_id", In: "query", Description: "Project flock kandang id filter.", Example: 1, PostmanValue: "{{project_flock_kandang_id}}"},
}
meta.ExampleResponse = map[string]any{
"code": 200, "status": "success", "message": "Get all chickins successfully",
"meta": map[string]any{"page": 1, "limit": 10, "total_pages": 1, "total_results": 1},
"data": []map[string]any{
{
"id": 1, "project_flock_kandang_id": 1,
"chick_in_date": "2026-01-01T00:00:00Z",
"product_warehouse_id": 1,
"product_warehouse": map[string]any{
"id": 1,
"product": map[string]any{"id": 1, "name": "DOC Broiler"},
"warehouse": map[string]any{"id": 1, "name": "Gudang DOC"},
},
"usage_qty": 10000.0, "pending_usage_qty": 0.0, "notes": "",
"created_user": map[string]any{"id": 1, "name": "Admin"},
"created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z",
},
},
}
case "/api/production/transfer_layings":
meta.QueryParams = []parameterMeta{
{Name: "page", In: "query", Description: "Page number.", Example: 1},
{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"},
}
meta.ExampleResponse = map[string]any{
"code": 200, "status": "success", "message": "Get all transferLayings successfully",
"meta": map[string]any{"page": 1, "limit": 10, "total_pages": 1, "total_results": 1},
"data": []map[string]any{
{
"id": 1, "transfer_number": "TL-00001",
"transfer_date": "2026-01-15T00:00:00Z",
"economic_cutoff_date": "2026-01-20T00:00:00Z",
"effective_move_date": "2026-01-18T00:00:00Z",
"executed_at": nil, "notes": "",
"from_project_flock": map[string]any{"id": 1, "flock_name": "Flock A Period 1"},
"to_project_flock": map[string]any{"id": 2, "flock_name": "Flock B Period 1"},
"created_by": 1,
"created_user": map[string]any{"id": 1, "name": "Admin"},
"created_at": "2026-01-15T00:00:00Z",
"approval": map[string]any{"step_number": 1, "step_name": "Pengajuan", "action": nil},
},
},
}
case "/api/production/transfer_layings/:id":
meta.ExampleResponse = map[string]any{
"code": 200, "status": "success", "message": "Get transferLaying successfully",
"data": map[string]any{
"id": 1, "transfer_number": "TL-00001",
"transfer_date": "2026-01-15T00:00:00Z",
"economic_cutoff_date": "2026-01-20T00:00:00Z",
"effective_move_date": "2026-01-18T00:00:00Z",
"executed_at": nil, "notes": "",
"from_project_flock": map[string]any{"id": 1, "flock_name": "Flock A Period 1"},
"to_project_flock": map[string]any{"id": 2, "flock_name": "Flock B Period 1"},
"created_by": 1, "created_user": map[string]any{"id": 1, "name": "Admin"},
"created_at": "2026-01-15T00:00:00Z",
"approval": map[string]any{"step_number": 1, "step_name": "Pengajuan", "action": nil},
"sources": []map[string]any{
{
"source_project_flock_kandang": map[string]any{"id": 1, "kandang_id": 1, "project_flock_id": 1, "kandang": map[string]any{"id": 1, "name": "Kandang A"}},
"qty": 5000.0, "note": "",
},
},
"targets": []map[string]any{
{
"target_project_flock_kandang": map[string]any{"id": 2, "kandang_id": 2, "project_flock_id": 2, "kandang": map[string]any{"id": 2, "name": "Kandang B"}},
"qty": 5000.0, "note": "",
},
},
},
}
case "/api/production/uniformities":
meta.QueryParams = []parameterMeta{
{Name: "page", In: "query", Description: "Page number.", Example: 1},
{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{
{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: "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"},
}
case "/api/reports/expense/depreciation":
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-DD).", Required: true, Example: "2026-01-01"},
{Name: "project_flock_id", In: "query", Description: "Comma separated project flock ids.", Example: "1,2"},
{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"},
}
case "/api/reports/expense/depreciation/manual-inputs":
meta.QueryParams = []parameterMeta{
{Name: "page", In: "query", Description: "Page number.", Example: 1},
{Name: "limit", In: "query", Description: "Page size.", Example: 10},
{Name: "project_flock_id", In: "query", Description: "Comma separated project flock ids.", Example: "1,2"},
{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"},
}
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: "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: "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: "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{
{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}}"},
{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{
{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}}"},
{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-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}}"},
{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 routePath == "/healthz" {
meta.Summary = "Health check"
meta.Description = "Simple liveness probe."
} else if routePath == "/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"}
}
}