apskel-pos-backend/internal/validator/purchase_order_validator.go
Aditya Siregar 4f6208e479 fix
2025-09-13 02:17:51 +07:00

215 lines
7.0 KiB
Go

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
}