package validator import ( "apskel-pos-be/internal/models" "fmt" "strings" "github.com/go-playground/validator/v10" ) type OrderIngredientTransactionValidator interface { ValidateCreateOrderIngredientTransactionRequest(req *models.CreateOrderIngredientTransactionRequest) error ValidateUpdateOrderIngredientTransactionRequest(req *models.UpdateOrderIngredientTransactionRequest) error ValidateListOrderIngredientTransactionsRequest(req *models.ListOrderIngredientTransactionsRequest) error } type OrderIngredientTransactionValidatorImpl struct { validator *validator.Validate } func NewOrderIngredientTransactionValidator() OrderIngredientTransactionValidator { v := validator.New() return &OrderIngredientTransactionValidatorImpl{ validator: v, } } func (v *OrderIngredientTransactionValidatorImpl) ValidateCreateOrderIngredientTransactionRequest(req *models.CreateOrderIngredientTransactionRequest) error { if err := v.validator.Struct(req); err != nil { var validationErrors []string for _, err := range err.(validator.ValidationErrors) { validationErrors = append(validationErrors, fmt.Sprintf("%s: %s", err.Field(), getValidationMessage(err))) } return fmt.Errorf("validation failed: %s", strings.Join(validationErrors, ", ")) } // Custom validations if req.GrossQty < req.NetQty { return fmt.Errorf("gross quantity must be greater than or equal to net quantity") } expectedWasteQty := req.GrossQty - req.NetQty if req.WasteQty != expectedWasteQty { return fmt.Errorf("waste quantity must equal gross quantity minus net quantity") } return nil } func (v *OrderIngredientTransactionValidatorImpl) ValidateUpdateOrderIngredientTransactionRequest(req *models.UpdateOrderIngredientTransactionRequest) error { if err := v.validator.Struct(req); err != nil { var validationErrors []string for _, err := range err.(validator.ValidationErrors) { validationErrors = append(validationErrors, fmt.Sprintf("%s: %s", err.Field(), getValidationMessage(err))) } return fmt.Errorf("validation failed: %s", strings.Join(validationErrors, ", ")) } // Custom validations for partial updates if req.GrossQty != nil && req.NetQty != nil { if *req.GrossQty < *req.NetQty { return fmt.Errorf("gross quantity must be greater than or equal to net quantity") } } if req.GrossQty != nil && req.NetQty != nil && req.WasteQty != nil { expectedWasteQty := *req.GrossQty - *req.NetQty if *req.WasteQty != expectedWasteQty { return fmt.Errorf("waste quantity must equal gross quantity minus net quantity") } } return nil } func (v *OrderIngredientTransactionValidatorImpl) ValidateListOrderIngredientTransactionsRequest(req *models.ListOrderIngredientTransactionsRequest) error { if err := v.validator.Struct(req); err != nil { var validationErrors []string for _, err := range err.(validator.ValidationErrors) { validationErrors = append(validationErrors, fmt.Sprintf("%s: %s", err.Field(), getValidationMessage(err))) } return fmt.Errorf("validation failed: %s", strings.Join(validationErrors, ", ")) } // Custom validations if req.StartDate != nil && req.EndDate != nil { if req.StartDate.After(*req.EndDate) { return fmt.Errorf("start date must be before end date") } } return nil } func getValidationMessage(err validator.FieldError) string { switch err.Tag() { case "required": return "is required" case "min": return fmt.Sprintf("must be at least %s", err.Param()) case "max": return fmt.Sprintf("must be at most %s", err.Param()) case "gt": return fmt.Sprintf("must be greater than %s", err.Param()) case "gte": return fmt.Sprintf("must be greater than or equal to %s", err.Param()) case "lt": return fmt.Sprintf("must be less than %s", err.Param()) case "lte": return fmt.Sprintf("must be less than or equal to %s", err.Param()) case "email": return "must be a valid email address" case "uuid": return "must be a valid UUID" case "len": return fmt.Sprintf("must be exactly %s characters long", err.Param()) default: return "is invalid" } }