package validator import ( "errors" "strings" "time" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/contract" ) type PurchaseOrderValidator interface { ValidateCreatePurchaseOrderRequest(req *contract.CreatePurchaseOrderRequest) (error, string) ValidateUpdatePurchaseOrderRequest(req *contract.UpdatePurchaseOrderRequest) (error, string) ValidateListPurchaseOrdersRequest(req *contract.ListPurchaseOrdersRequest) (error, string) } type PurchaseOrderValidatorImpl struct{} func NewPurchaseOrderValidator() *PurchaseOrderValidatorImpl { return &PurchaseOrderValidatorImpl{} } func (v *PurchaseOrderValidatorImpl) ValidateCreatePurchaseOrderRequest(req *contract.CreatePurchaseOrderRequest) (error, string) { if req == nil { return errors.New("request body is required"), constants.MissingFieldErrorCode } if req.VendorID.String() == "" { return errors.New("vendor_id is required"), constants.MissingFieldErrorCode } if strings.TrimSpace(req.PONumber) == "" { return errors.New("po_number is required"), constants.MissingFieldErrorCode } if len(req.PONumber) < 1 || len(req.PONumber) > 50 { return errors.New("po_number must be between 1 and 50 characters"), constants.MalformedFieldErrorCode } // Validate transaction date if strings.TrimSpace(req.TransactionDate) == "" { return errors.New("transaction_date is required"), constants.MissingFieldErrorCode } transactionDate, err := time.Parse("2006-01-02", req.TransactionDate) if err != nil { return errors.New("transaction_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode } // Validate due date if strings.TrimSpace(req.DueDate) == "" { return errors.New("due_date is required"), constants.MissingFieldErrorCode } dueDate, err := time.Parse("2006-01-02", req.DueDate) if err != nil { return errors.New("due_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode } // Check if due date is after transaction date if dueDate.Before(transactionDate) { return errors.New("due_date must be after transaction_date"), constants.MalformedFieldErrorCode } if req.Reference != nil && len(*req.Reference) > 100 { return errors.New("reference must be at most 100 characters"), constants.MalformedFieldErrorCode } if req.Status != nil { validStatuses := []string{"draft", "sent", "approved", "received", "cancelled"} if !contains(validStatuses, *req.Status) { return errors.New("status must be one of: draft, sent, approved, received, cancelled"), constants.MalformedFieldErrorCode } } if len(req.Items) == 0 { return errors.New("at least one item is required"), constants.MissingFieldErrorCode } // Validate items for i, item := range req.Items { if err, code := v.validatePurchaseOrderItem(&item, i); err != nil { return err, code } } return nil, "" } func (v *PurchaseOrderValidatorImpl) ValidateUpdatePurchaseOrderRequest(req *contract.UpdatePurchaseOrderRequest) (error, string) { if req == nil { return errors.New("request body is required"), constants.MissingFieldErrorCode } if req.PONumber != nil { if strings.TrimSpace(*req.PONumber) == "" { return errors.New("po_number cannot be empty"), constants.MalformedFieldErrorCode } if len(*req.PONumber) < 1 || len(*req.PONumber) > 50 { return errors.New("po_number must be between 1 and 50 characters"), constants.MalformedFieldErrorCode } } // Validate dates if both are provided if req.TransactionDate != nil && req.DueDate != nil { if *req.TransactionDate != "" && *req.DueDate != "" { transactionDate, err := time.Parse("2006-01-02", *req.TransactionDate) if err != nil { return errors.New("transaction_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode } dueDate, err := time.Parse("2006-01-02", *req.DueDate) if err != nil { return errors.New("due_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode } if dueDate.Before(transactionDate) { return errors.New("due_date must be after transaction_date"), constants.MalformedFieldErrorCode } } } if req.Reference != nil && len(*req.Reference) > 100 { return errors.New("reference must be at most 100 characters"), constants.MalformedFieldErrorCode } if req.Status != nil { validStatuses := []string{"draft", "sent", "approved", "received", "cancelled"} if !contains(validStatuses, *req.Status) { return errors.New("status must be one of: draft, sent, approved, received, cancelled"), constants.MalformedFieldErrorCode } } // Validate items if provided if req.Items != nil { for i, item := range req.Items { if err, code := v.validateUpdatePurchaseOrderItem(&item, i); err != nil { return err, code } } } return nil, "" } func (v *PurchaseOrderValidatorImpl) ValidateListPurchaseOrdersRequest(req *contract.ListPurchaseOrdersRequest) (error, string) { if req == nil { return errors.New("request body is required"), constants.MissingFieldErrorCode } if req.Page < 1 { return errors.New("page must be at least 1"), constants.MalformedFieldErrorCode } if req.Limit < 1 || req.Limit > 100 { return errors.New("limit must be between 1 and 100"), constants.MalformedFieldErrorCode } if req.Status != "" { validStatuses := []string{"draft", "sent", "approved", "received", "cancelled"} if !contains(validStatuses, req.Status) { return errors.New("status must be one of: draft, sent, approved, received, cancelled"), constants.MalformedFieldErrorCode } } if req.StartDate != nil && req.EndDate != nil { if req.EndDate.Before(*req.StartDate) { return errors.New("end_date must be after start_date"), constants.MalformedFieldErrorCode } } return nil, "" } func (v *PurchaseOrderValidatorImpl) validatePurchaseOrderItem(item *contract.CreatePurchaseOrderItemRequest, index int) (error, string) { if item.IngredientID.String() == "" { return errors.New("items[" + string(rune(index)) + "].ingredient_id is required"), constants.MissingFieldErrorCode } if item.Quantity <= 0 { return errors.New("items[" + string(rune(index)) + "].quantity must be greater than 0"), constants.MalformedFieldErrorCode } if item.UnitID.String() == "" { return errors.New("items[" + string(rune(index)) + "].unit_id is required"), constants.MissingFieldErrorCode } if item.Amount < 0 { return errors.New("items[" + string(rune(index)) + "].amount must be greater than or equal to 0"), constants.MalformedFieldErrorCode } return nil, "" } func (v *PurchaseOrderValidatorImpl) validateUpdatePurchaseOrderItem(item *contract.UpdatePurchaseOrderItemRequest, index int) (error, string) { if item.Quantity != nil && *item.Quantity <= 0 { return errors.New("items[" + string(rune(index)) + "].quantity must be greater than 0"), constants.MalformedFieldErrorCode } if item.Amount != nil && *item.Amount < 0 { return errors.New("items[" + string(rune(index)) + "].amount must be greater than or equal to 0"), constants.MalformedFieldErrorCode } return nil, "" } // Helper function to check if a string is in a slice func contains(slice []string, item string) bool { for _, s := range slice { if s == item { return true } } return false }