diff --git a/internal/modules/constants/repositories/constant.repository.go b/internal/modules/constants/repositories/constant.repository.go index 3c4beb06..3d67bd71 100644 --- a/internal/modules/constants/repositories/constant.repository.go +++ b/internal/modules/constants/repositories/constant.repository.go @@ -32,6 +32,44 @@ func (r *ConstantRepositoryImpl) GetConstants() (map[string]interface{}, error) } sort.Strings(flagList) + productMainFlags := utils.ProductMainFlags() + productMainFlagValues := make([]string, len(productMainFlags)) + for i, flag := range productMainFlags { + productMainFlagValues[i] = string(flag) + } + + type productFlagOption struct { + Flag string `json:"flag"` + SubFlags []string `json:"sub_flags"` + AllowWithoutSubFlag bool `json:"allow_without_sub_flag"` + } + + productOptions := utils.ProductFlagOptions() + productFlagOptions := make([]productFlagOption, 0, len(productOptions)) + for _, option := range productOptions { + subFlags := make([]string, len(option.SubFlags)) + for i, subFlag := range option.SubFlags { + subFlags[i] = string(subFlag) + } + productFlagOptions = append(productFlagOptions, productFlagOption{ + Flag: string(option.Flag), + SubFlags: subFlags, + AllowWithoutSubFlag: option.AllowWithoutSubFlag, + }) + } + + productSubFlagToFlagRaw := utils.ProductSubFlagToFlag() + productSubFlagToFlag := make(map[string]string, len(productSubFlagToFlagRaw)) + for subFlag, flag := range productSubFlagToFlagRaw { + productSubFlagToFlag[string(subFlag)] = string(flag) + } + + legacyAliasesRaw := utils.LegacyFlagTypeAliases() + legacyAliases := make(map[string]string, len(legacyAliasesRaw)) + for legacy, canonical := range legacyAliasesRaw { + legacyAliases[string(legacy)] = string(canonical) + } + type approvalStepConstant struct { StepNumber uint16 `json:"step_number"` StepName string `json:"step_name"` @@ -96,9 +134,15 @@ func (r *ConstantRepositoryImpl) GetConstants() (map[string]interface{}, error) "BISNIS", "INDIVIDUAL", }, - "adjustment": map[string]interface{}{ - "transaction_subtypes": adjustmentSubtypesByType, - }, - "approval_workflows": approvalWorkflows, - }, nil + "adjustment": map[string]interface{}{ + "transaction_subtypes": adjustmentSubtypesByType, + }, + "legacy_flag_aliases": legacyAliases, + "product_flag_mapping": map[string]interface{}{ + "flags": productMainFlagValues, + "options": productFlagOptions, + "sub_flag_to_flag": productSubFlagToFlag, + }, + "approval_workflows": approvalWorkflows, + }, nil } diff --git a/internal/modules/master/products/dto/product.dto.go b/internal/modules/master/products/dto/product.dto.go index d115ad23..dc09bdee 100644 --- a/internal/modules/master/products/dto/product.dto.go +++ b/internal/modules/master/products/dto/product.dto.go @@ -7,6 +7,7 @@ import ( productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto" uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" + utils "gitlab.com/mbugroup/lti-api.git/internal/utils" ) // === DTO Structs === @@ -17,6 +18,9 @@ type ProductRelationDTO struct { ProductPrice float64 `gorm:"type:numeric(15,3);not null"` SellingPrice *float64 `gorm:"type:numeric(15,3)"` Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"` + Flag *string `json:"flag,omitempty"` + SubFlag *string `json:"sub_flag,omitempty"` + SubFlags *[]string `json:"sub_flags,omitempty"` Flags *[]string `json:"flags,omitempty"` ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"` Suppliers []ProductSupplierDTO `json:"suppliers"` @@ -31,6 +35,9 @@ type ProductListDTO struct { SellingPrice *float64 `json:"selling_price,omitempty"` Tax *float64 `json:"tax,omitempty"` ExpiryPeriod *int `json:"expiry_period,omitempty"` + Flag *string `json:"flag,omitempty"` + SubFlag *string `json:"sub_flag,omitempty"` + SubFlags []string `json:"sub_flags,omitempty"` Flags []string `json:"flags"` Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"` ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"` @@ -59,6 +66,13 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO { for i, f := range e.Flags { flags[i] = f.Name } + flag, subFlag, subFlags := resolveProductFlagAndSubFlags(flags) + var subFlagsRef *[]string + if len(subFlags) > 0 { + values := make([]string, len(subFlags)) + copy(values, subFlags) + subFlagsRef = &values + } var uomRef *uomDTO.UomRelationDTO if e.Uom.Id != 0 { @@ -77,6 +91,9 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO { Name: e.Name, ProductPrice: e.ProductPrice, SellingPrice: e.SellingPrice, + Flag: flag, + SubFlag: subFlag, + SubFlags: subFlagsRef, Flags: &flags, Uom: uomRef, ProductCategory: categoryRef, @@ -101,6 +118,7 @@ func ToProductListDTO(e entity.Product) ProductListDTO { for i, f := range e.Flags { flags[i] = f.Name } + flag, subFlag, subFlags := resolveProductFlagAndSubFlags(flags) var uomRef *uomDTO.UomRelationDTO if e.Uom.Id != 0 { @@ -111,6 +129,9 @@ func ToProductListDTO(e entity.Product) ProductListDTO { return ProductListDTO{ Id: e.Id, Name: e.Name, + Flag: flag, + SubFlag: subFlag, + SubFlags: subFlags, Flags: flags, Uom: uomRef, Brand: e.Brand, @@ -141,6 +162,58 @@ func ToProductDetailDTO(e entity.Product) ProductDetailDTO { } } +func resolveProductFlagAndSubFlags(flags []string) (*string, *string, []string) { + normalized := utils.NormalizeFlagTypes(flags) + if len(normalized) == 0 { + return nil, nil, nil + } + + available := make(map[utils.FlagType]struct{}, len(normalized)) + for _, flag := range normalized { + available[flag] = struct{}{} + } + + var selectedFlag utils.FlagType + for _, mainFlag := range utils.ProductMainFlags() { + if _, ok := available[mainFlag]; ok { + selectedFlag = mainFlag + break + } + } + + if selectedFlag == "" { + subToMain := utils.ProductSubFlagToFlag() + for _, flag := range normalized { + if parent, ok := subToMain[flag]; ok { + selectedFlag = parent + break + } + } + } + + if selectedFlag == "" { + return nil, nil, nil + } + + flag := string(selectedFlag) + + var subFlag *string + subFlagValues := make([]string, 0) + subFlagsByMain := utils.ProductSubFlagsByFlag() + for _, sub := range subFlagsByMain[selectedFlag] { + if _, ok := available[sub]; ok { + subFlagValues = append(subFlagValues, string(sub)) + } + } + + if len(subFlagValues) > 0 { + first := subFlagValues[0] + subFlag = &first + } + + return &flag, subFlag, subFlagValues +} + func toProductSupplierDTOs(relations []entity.ProductSupplier) []ProductSupplierDTO { if len(relations) == 0 { return make([]ProductSupplierDTO, 0) diff --git a/internal/modules/master/products/services/product.service.go b/internal/modules/master/products/services/product.service.go index 0aaa0952..f2281d1e 100644 --- a/internal/modules/master/products/services/product.service.go +++ b/internal/modules/master/products/services/product.service.go @@ -41,6 +41,159 @@ func normalizeProductFlags(raw []string) ([]string, error) { return utils.FlagTypesToStrings(normalized), nil } +func productMainFlagOptionsString() []string { + mainFlags := utils.ProductMainFlags() + result := make([]string, len(mainFlags)) + for i, flag := range mainFlags { + result[i] = string(flag) + } + return result +} + +func productSubFlagOptionsString(flag utils.FlagType) []string { + subFlagsByFlag := utils.ProductSubFlagsByFlag() + subFlags := subFlagsByFlag[flag] + result := make([]string, len(subFlags)) + for i, subFlag := range subFlags { + result[i] = string(subFlag) + } + return result +} + +func normalizeStructuredSubFlagsInput(subFlagRaw *string, subFlagsRaw []string, hasSubFlagsField bool) ([]utils.FlagType, error) { + values := make([]string, 0, len(subFlagsRaw)+1) + + if subFlagRaw != nil { + single := strings.TrimSpace(*subFlagRaw) + if single == "" { + return nil, fiber.NewError(fiber.StatusBadRequest, "sub_flag cannot be empty") + } + values = append(values, single) + } + + if hasSubFlagsField { + for _, raw := range subFlagsRaw { + item := strings.TrimSpace(raw) + if item == "" { + return nil, fiber.NewError(fiber.StatusBadRequest, "sub_flags cannot contain empty value") + } + values = append(values, item) + } + } + + if len(values) == 0 { + return nil, nil + } + + return utils.NormalizeFlagTypes(values), nil +} + +func resolveProductFlagsFromFlagInput(flagRaw *string, subFlagRaw *string, subFlagsRaw []string, hasSubFlagsField bool) ([]string, bool, error) { + if flagRaw == nil && subFlagRaw == nil && !hasSubFlagsField { + return nil, false, nil + } + + if flagRaw == nil && (subFlagRaw != nil || hasSubFlagsField) { + return nil, false, fiber.NewError(fiber.StatusBadRequest, "flag is required when sub_flag/sub_flags is provided") + } + + flagText := strings.TrimSpace(*flagRaw) + if flagText == "" { + return nil, false, fiber.NewError(fiber.StatusBadRequest, "flag cannot be empty") + } + + flag := utils.CanonicalFlagType(flagText) + if !utils.IsProductMainFlag(flag) { + return nil, false, fiber.NewError( + fiber.StatusBadRequest, + fmt.Sprintf("Invalid product flag: %s. Allowed flags: %s", flagText, strings.Join(productMainFlagOptionsString(), ", ")), + ) + } + + out := []string{string(flag)} + + normalizedSubFlags, err := normalizeStructuredSubFlagsInput(subFlagRaw, subFlagsRaw, hasSubFlagsField) + if err != nil { + return nil, false, err + } + + if len(normalizedSubFlags) == 0 { + if !utils.ProductFlagAllowWithoutSubFlag(flag) { + return nil, false, fiber.NewError( + fiber.StatusBadRequest, + fmt.Sprintf("sub_flag/sub_flags is required for flag %s", string(flag)), + ) + } + normalizedOut, normalizeErr := normalizeProductFlags(out) + if normalizeErr != nil { + return nil, false, normalizeErr + } + return normalizedOut, true, nil + } + + invalidSubFlags := make([]string, 0) + for _, subFlag := range normalizedSubFlags { + if !utils.IsValidProductSubFlag(flag, subFlag) { + invalidSubFlags = append(invalidSubFlags, string(subFlag)) + } + } + if len(invalidSubFlags) > 0 { + return nil, false, fiber.NewError( + fiber.StatusBadRequest, + fmt.Sprintf("Invalid sub_flags %s for flag %s. Allowed sub_flags: %s", strings.Join(invalidSubFlags, ", "), string(flag), strings.Join(productSubFlagOptionsString(flag), ", ")), + ) + } + + out = append(out, utils.FlagTypesToStrings(normalizedSubFlags)...) + normalizedOut, normalizeErr := normalizeProductFlags(out) + if normalizeErr != nil { + return nil, false, normalizeErr + } + return normalizedOut, true, nil +} + +func resolveCreateProductFlags(req *validation.Create) ([]string, error) { + hasStructuredInput := req.Flag != nil || req.SubFlag != nil || req.SubFlags != nil + if len(req.Flags) > 0 && hasStructuredInput { + return nil, fiber.NewError(fiber.StatusBadRequest, "Use either flags or flag/sub_flag/sub_flags, not both") + } + + if len(req.Flags) > 0 { + return normalizeProductFlags(req.Flags) + } + + flags, _, err := resolveProductFlagsFromFlagInput(req.Flag, req.SubFlag, req.SubFlags, req.SubFlags != nil) + return flags, err +} + +func resolveUpdateProductFlags(req *validation.Update) (bool, []string, error) { + hasStructuredInput := req.Flag != nil || req.SubFlag != nil || req.SubFlags != nil + + if req.Flags != nil { + if hasStructuredInput { + if len(*req.Flags) > 0 { + return false, nil, fiber.NewError(fiber.StatusBadRequest, "Use either flags or flag/sub_flag/sub_flags, not both") + } + } else { + flags, err := normalizeProductFlags(*req.Flags) + if err != nil { + return false, nil, err + } + return true, flags, nil + } + } + + subFlagsRaw := make([]string, 0) + if req.SubFlags != nil { + subFlagsRaw = *req.SubFlags + } + flags, provided, err := resolveProductFlagsFromFlagInput(req.Flag, req.SubFlag, subFlagsRaw, req.SubFlags != nil) + if err != nil { + return false, nil, err + } + return provided, flags, nil +} + func NewProductService(repo repository.ProductRepository, validate *validator.Validate) ProductService { return &productService{ Log: utils.Log, @@ -177,7 +330,7 @@ func (s *productService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } } - productFlags, flagErr := normalizeProductFlags(req.Flags) + productFlags, flagErr := resolveCreateProductFlags(req) if flagErr != nil { return nil, flagErr } @@ -337,13 +490,10 @@ func (s productService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) flagUpdate bool flagValues []string ) - if req.Flags != nil { - flagUpdate = true - var flagErr error - flagValues, flagErr = normalizeProductFlags(*req.Flags) - if flagErr != nil { - return nil, flagErr - } + var flagErr error + flagUpdate, flagValues, flagErr = resolveUpdateProductFlags(req) + if flagErr != nil { + return nil, flagErr } if len(updateBody) == 0 && !supplierUpdate && !flagUpdate { diff --git a/internal/modules/master/products/validations/product.validation.go b/internal/modules/master/products/validations/product.validation.go index 77e8e1bf..0a383192 100644 --- a/internal/modules/master/products/validations/product.validation.go +++ b/internal/modules/master/products/validations/product.validation.go @@ -6,31 +6,37 @@ type SupplierPrice struct { } type Create struct { - Name string `json:"name" validate:"required_strict,min=3,max=50"` - Brand string `json:"brand" validate:"required_strict,min=2,max=50"` - Sku *string `json:"sku,omitempty" validate:"omitempty,max=100"` - UomID uint `json:"uom_id" validate:"required,gt=0"` - ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"` - ProductPrice float64 `json:"product_price" validate:"required"` - SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"` - Tax *float64 `json:"tax,omitempty" validate:"omitempty"` - ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` + Brand string `json:"brand" validate:"required_strict,min=2,max=50"` + Sku *string `json:"sku,omitempty" validate:"omitempty,max=100"` + UomID uint `json:"uom_id" validate:"required,gt=0"` + ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"` + ProductPrice float64 `json:"product_price" validate:"required"` + SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"` + Tax *float64 `json:"tax,omitempty" validate:"omitempty"` + ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"` Suppliers []SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"` - Flags []string `json:"flags,omitempty" validate:"omitempty,dive"` + Flag *string `json:"flag,omitempty" validate:"omitempty,max=50"` + SubFlag *string `json:"sub_flag,omitempty" validate:"omitempty,max=50"` + SubFlags []string `json:"sub_flags,omitempty" validate:"omitempty,dive,max=50"` + Flags []string `json:"flags,omitempty" validate:"omitempty,dive"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty,min=3"` - Brand *string `json:"brand,omitempty" validate:"omitempty,min=2"` - Sku *string `json:"sku,omitempty" validate:"omitempty"` - UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"` - ProductCategoryID *uint `json:"product_category_id,omitempty" validate:"omitempty,gt=0"` - ProductPrice *float64 `json:"product_price,omitempty" validate:"omitempty"` - SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"` - Tax *float64 `json:"tax,omitempty" validate:"omitempty"` - ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"` + Name *string `json:"name,omitempty" validate:"omitempty,min=3"` + Brand *string `json:"brand,omitempty" validate:"omitempty,min=2"` + Sku *string `json:"sku,omitempty" validate:"omitempty"` + UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"` + ProductCategoryID *uint `json:"product_category_id,omitempty" validate:"omitempty,gt=0"` + ProductPrice *float64 `json:"product_price,omitempty" validate:"omitempty"` + SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"` + Tax *float64 `json:"tax,omitempty" validate:"omitempty"` + ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"` Suppliers *[]SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"` - Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive"` + Flag *string `json:"flag,omitempty" validate:"omitempty,max=50"` + SubFlag *string `json:"sub_flag,omitempty" validate:"omitempty,max=50"` + SubFlags *[]string `json:"sub_flags,omitempty" validate:"omitempty,dive,max=50"` + Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive"` } type Query struct { diff --git a/internal/modules/production/chickins/services/fifo_stock_v2_helper.go b/internal/modules/production/chickins/services/fifo_stock_v2_helper.go index a931e43e..6d2e4477 100644 --- a/internal/modules/production/chickins/services/fifo_stock_v2_helper.go +++ b/internal/modules/production/chickins/services/fifo_stock_v2_helper.go @@ -2,6 +2,7 @@ package service import ( "context" + "errors" "fmt" "strings" "time" @@ -11,12 +12,6 @@ import ( "gorm.io/gorm" ) -const ( - chickinOutFunctionCode = "CHICKIN_OUT" - chickinUsableLane = "USABLE" - chickinSourceTable = "project_chickins" -) - func reflowChickinScope( ctx context.Context, fifoStockV2Svc commonSvc.FifoStockV2Service, @@ -62,9 +57,6 @@ func resolveChickinFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, Select("rr.flag_group_code"). Joins("JOIN fifo_stock_v2_flag_groups fg ON fg.code = rr.flag_group_code AND fg.is_active = TRUE"). Where("rr.is_active = TRUE"). - Where("rr.lane = ?", chickinUsableLane). - Where("rr.function_code = ?", chickinOutFunctionCode). - Where("rr.source_table = ?", chickinSourceTable). Where(` EXISTS ( SELECT 1 @@ -76,10 +68,13 @@ func resolveChickinFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, AND fm.flag_group_code = rr.flag_group_code ) `, productWarehouseID, entity.FlagableTypeProduct). - Order("rr.id ASC"). + Order("fg.priority ASC, rr.id ASC"). Limit(1). Take(&selected).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", nil + } return "", err } diff --git a/internal/utils/constant.go b/internal/utils/constant.go index 02b09692..c263180b 100644 --- a/internal/utils/constant.go +++ b/internal/utils/constant.go @@ -14,9 +14,17 @@ type FlagType string type FlagGroup string +type ProductFlagOption struct { + Flag FlagType `json:"flag"` + SubFlags []FlagType `json:"sub_flags"` + AllowWithoutSubFlag bool `json:"allow_without_sub_flag"` +} + const ( FlagIsActive FlagType = "IS_ACTIVE" + FlagAyam FlagType = "AYAM" + FlagDOC FlagType = "DOC" FlagPullet FlagType = "PULLET" FlagLayer FlagType = "LAYER" @@ -36,11 +44,13 @@ const ( FlagAyamMati FlagType = "AYAM-MATI" //flag telur - FlagTelur FlagType = "TELUR" - FlagTelurUtuh FlagType = "TELUR-UTUH" - FlagTelurPecah FlagType = "TELUR-PECAH" - FlagTelurPutih FlagType = "TELUR-PUTIH" - FlagTelurRetak FlagType = "TELUR-RETAK" + FlagTelur FlagType = "TELUR" + FlagTelurUtuh FlagType = "TELUR-UTUH" + FlagTelurPecah FlagType = "TELUR-PECAH" + FlagTelurPutih FlagType = "TELUR-PUTIH" + FlagTelurRetak FlagType = "TELUR-RETAK" + FlagTelurPapacal FlagType = "TELUR-PAPACAL" + FlagTelurJumbo FlagType = "TELUR-JUMBO" ) const ( @@ -50,9 +60,10 @@ const ( var flagGroupOptions = map[FlagGroup][]FlagType{ FlagGroupProduct: { - FlagDOC, - FlagPullet, - FlagLayer, + FlagAyam, + FlagAyamAfkir, + FlagAyamCulling, + FlagAyamMati, FlagPakan, FlagPreStarter, FlagStarter, @@ -61,12 +72,75 @@ var flagGroupOptions = map[FlagGroup][]FlagType{ FlagObat, FlagVitamin, FlagKimia, + FlagTelur, + FlagTelurUtuh, + FlagTelurPecah, + FlagTelurPutih, + FlagTelurRetak, + FlagTelurPapacal, + FlagTelurJumbo, }, FlagGroupNonstock: { FlagEkspedisi, }, } +var productMainFlags = []FlagType{ + FlagAyam, + FlagPakan, + FlagOVK, + FlagTelur, +} + +var productSubFlagsByFlag = map[FlagType][]FlagType{ + FlagAyam: { + FlagAyamAfkir, + FlagAyamCulling, + FlagAyamMati, + }, + FlagPakan: { + FlagPreStarter, + FlagStarter, + FlagFinisher, + }, + FlagOVK: { + FlagObat, + FlagVitamin, + FlagKimia, + }, + FlagTelur: { + FlagTelurUtuh, + FlagTelurPutih, + FlagTelurRetak, + FlagTelurPecah, + FlagTelurPapacal, + FlagTelurJumbo, + }, +} + +var productSubFlagToFlag = func() map[FlagType]FlagType { + out := make(map[FlagType]FlagType) + for flag, subFlags := range productSubFlagsByFlag { + for _, subFlag := range subFlags { + out[subFlag] = flag + } + } + return out +}() + +var productAllowWithoutSubFlagByFlag = map[FlagType]bool{ + FlagAyam: true, + FlagPakan: false, + FlagOVK: false, + FlagTelur: false, +} + +var legacyFlagTypeAliases = map[FlagType]FlagType{ + FlagDOC: FlagAyam, + FlagPullet: FlagAyam, + FlagLayer: FlagAyam, +} + var allFlagTypes = func() map[FlagType]struct{} { m := map[FlagType]struct{}{ FlagIsActive: {}, @@ -83,6 +157,102 @@ func AllFlagTypes() map[FlagType]struct{} { return allFlagTypes } +func canonicalizeFlagType(flag FlagType) FlagType { + if canonical, ok := legacyFlagTypeAliases[flag]; ok { + return canonical + } + return flag +} + +func CanonicalFlagType(v string) FlagType { + normalized := FlagType(strings.ToUpper(strings.TrimSpace(v))) + if normalized == "" { + return "" + } + return canonicalizeFlagType(normalized) +} + +func LegacyFlagTypeAliases() map[FlagType]FlagType { + out := make(map[FlagType]FlagType, len(legacyFlagTypeAliases)) + for legacy, canonical := range legacyFlagTypeAliases { + out[legacy] = canonical + } + return out +} + +func ProductMainFlags() []FlagType { + out := make([]FlagType, len(productMainFlags)) + copy(out, productMainFlags) + return out +} + +func ProductSubFlagsByFlag() map[FlagType][]FlagType { + out := make(map[FlagType][]FlagType, len(productSubFlagsByFlag)) + for flag, subFlags := range productSubFlagsByFlag { + dup := make([]FlagType, len(subFlags)) + copy(dup, subFlags) + out[flag] = dup + } + return out +} + +func ProductSubFlagToFlag() map[FlagType]FlagType { + out := make(map[FlagType]FlagType, len(productSubFlagToFlag)) + for subFlag, flag := range productSubFlagToFlag { + out[subFlag] = flag + } + return out +} + +func ProductFlagOptions() []ProductFlagOption { + result := make([]ProductFlagOption, 0, len(productMainFlags)) + for _, flag := range productMainFlags { + subFlags := productSubFlagsByFlag[flag] + dup := make([]FlagType, len(subFlags)) + copy(dup, subFlags) + result = append(result, ProductFlagOption{ + Flag: flag, + SubFlags: dup, + AllowWithoutSubFlag: productAllowWithoutSubFlagByFlag[flag], + }) + } + return result +} + +func ProductFlagAllowWithoutSubFlag(flag FlagType) bool { + canonical := canonicalizeFlagType(flag) + allow, ok := productAllowWithoutSubFlagByFlag[canonical] + if !ok { + return false + } + return allow +} + +func IsProductMainFlag(flag FlagType) bool { + canonical := canonicalizeFlagType(flag) + for _, f := range productMainFlags { + if f == canonical { + return true + } + } + return false +} + +func IsValidProductSubFlag(flag FlagType, subFlag FlagType) bool { + canonicalFlag := canonicalizeFlagType(flag) + canonicalSubFlag := canonicalizeFlagType(subFlag) + allowedSubFlags, ok := productSubFlagsByFlag[canonicalFlag] + if !ok { + return false + } + for _, allowed := range allowedSubFlags { + if allowed == canonicalSubFlag { + return true + } + } + return false +} + // ------------------------------------------------------------------- // WarehouseType // ------------------------------------------------------------------- @@ -621,7 +791,11 @@ const ( // ------------------------------------------------------------------- func IsValidFlagType(v string) bool { - _, ok := allFlagTypes[FlagType(strings.ToUpper(strings.TrimSpace(v)))] + flag := FlagType(strings.ToUpper(strings.TrimSpace(v))) + if _, ok := allFlagTypes[flag]; ok { + return true + } + _, ok := legacyFlagTypeAliases[flag] return ok } @@ -667,6 +841,7 @@ func NormalizeFlagTypes(flags []string) []FlagType { if normalized == "" { continue } + normalized = canonicalizeFlagType(normalized) if _, exists := seen[normalized]; exists { continue }