package validator import ( "errors" "fmt" "strings" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/contract" "github.com/google/uuid" ) type ExpenseValidator interface { ValidateCreateExpenseRequest(req *contract.CreateExpenseRequest) (error, string) ValidateUpdateExpenseRequest(req *contract.UpdateExpenseRequest) (error, string) ValidateListExpenseRequest(req *contract.ListExpenseRequest) (error, string) } type ExpenseValidatorImpl struct{} func NewExpenseValidator() *ExpenseValidatorImpl { return &ExpenseValidatorImpl{} } func (v *ExpenseValidatorImpl) ValidateCreateExpenseRequest(req *contract.CreateExpenseRequest) (error, string) { if req == nil { return errors.New("request body is required"), constants.MissingFieldErrorCode } if strings.TrimSpace(req.Receiver) == "" { return errors.New("receiver is required"), constants.MissingFieldErrorCode } if strings.TrimSpace(req.TransactionDate) == "" { return errors.New("transaction_date is required"), constants.MissingFieldErrorCode } if strings.TrimSpace(req.CodeNumber) == "" { return errors.New("code_number is required"), constants.MissingFieldErrorCode } if strings.TrimSpace(req.OutletID) == "" { return errors.New("outlet_id is required"), constants.MissingFieldErrorCode } if _, err := uuid.Parse(req.OutletID); err != nil { return errors.New("outlet_id must be a valid UUID"), constants.MalformedFieldErrorCode } if req.Status != nil && !constants.IsValidExpenseStatus(constants.ExpenseStatus(*req.Status)) { return errors.New("status must be one of: draft, sent, approved, cancel"), constants.MalformedFieldErrorCode } if req.Total <= 0 { return errors.New("total must be greater than 0"), constants.MalformedFieldErrorCode } if req.Tax < 0 { return errors.New("tax cannot be negative"), constants.MalformedFieldErrorCode } if len(req.Items) == 0 { return errors.New("at least one item is required"), constants.MissingFieldErrorCode } for i, item := range req.Items { if strings.TrimSpace(item.ChartOfAccountID) == "" { return fmt.Errorf("item %d: chart_of_account_id is required", i), constants.MissingFieldErrorCode } if strings.TrimSpace(item.PurchaseCategoryID) == "" { return fmt.Errorf("item %d: purchase_category_id is required", i), constants.MissingFieldErrorCode } if strings.TrimSpace(item.Item) == "" { return fmt.Errorf("item %d: item is required", i), constants.MissingFieldErrorCode } if _, err := uuid.Parse(item.ChartOfAccountID); err != nil { return fmt.Errorf("item %d: chart_of_account_id must be a valid UUID", i), constants.MalformedFieldErrorCode } if _, err := uuid.Parse(item.PurchaseCategoryID); err != nil { return fmt.Errorf("item %d: purchase_category_id must be a valid UUID", i), constants.MalformedFieldErrorCode } if item.Amount <= 0 { return fmt.Errorf("item %d: amount must be greater than 0", i), constants.MalformedFieldErrorCode } } return nil, "" } func (v *ExpenseValidatorImpl) ValidateUpdateExpenseRequest(req *contract.UpdateExpenseRequest) (error, string) { if req == nil { return errors.New("request body is required"), constants.MissingFieldErrorCode } if req.Receiver != nil && strings.TrimSpace(*req.Receiver) == "" { return errors.New("receiver cannot be empty"), constants.MalformedFieldErrorCode } if req.CodeNumber != nil && strings.TrimSpace(*req.CodeNumber) == "" { return errors.New("code_number cannot be empty"), constants.MalformedFieldErrorCode } if req.Status != nil && !constants.IsValidExpenseStatus(constants.ExpenseStatus(*req.Status)) { return errors.New("status must be one of: draft, sent, approved, cancel"), constants.MalformedFieldErrorCode } if req.OutletID != nil { if strings.TrimSpace(*req.OutletID) == "" { return errors.New("outlet_id cannot be empty"), constants.MalformedFieldErrorCode } if _, err := uuid.Parse(*req.OutletID); err != nil { return errors.New("outlet_id must be a valid UUID"), constants.MalformedFieldErrorCode } } if req.Total != nil && *req.Total <= 0 { return errors.New("total must be greater than 0"), constants.MalformedFieldErrorCode } if req.Tax != nil && *req.Tax < 0 { return errors.New("tax cannot be negative"), constants.MalformedFieldErrorCode } if req.Items != nil { for i, item := range req.Items { if item.ChartOfAccountID != nil { if strings.TrimSpace(*item.ChartOfAccountID) == "" { return fmt.Errorf("item %d: chart_of_account_id cannot be empty", i), constants.MalformedFieldErrorCode } if _, err := uuid.Parse(*item.ChartOfAccountID); err != nil { return fmt.Errorf("item %d: chart_of_account_id must be a valid UUID", i), constants.MalformedFieldErrorCode } } if item.PurchaseCategoryID == nil { return fmt.Errorf("item %d: purchase_category_id is required", i), constants.MissingFieldErrorCode } if strings.TrimSpace(*item.PurchaseCategoryID) == "" { return fmt.Errorf("item %d: purchase_category_id cannot be empty", i), constants.MalformedFieldErrorCode } if _, err := uuid.Parse(*item.PurchaseCategoryID); err != nil { return fmt.Errorf("item %d: purchase_category_id must be a valid UUID", i), constants.MalformedFieldErrorCode } if item.Item != nil && strings.TrimSpace(*item.Item) == "" { return fmt.Errorf("item %d: item cannot be empty", i), constants.MalformedFieldErrorCode } if item.Amount != nil && *item.Amount <= 0 { return fmt.Errorf("item %d: amount must be greater than 0", i), constants.MalformedFieldErrorCode } } } return nil, "" } func (v *ExpenseValidatorImpl) ValidateListExpenseRequest(req *contract.ListExpenseRequest) (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 != "" && !constants.IsValidExpenseStatus(constants.ExpenseStatus(req.Status)) { return errors.New("status must be one of: draft, sent, approved, cancel"), constants.MalformedFieldErrorCode } return nil, "" }