193 lines
7.0 KiB
Go
193 lines
7.0 KiB
Go
package validator
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"apskel-pos-be/internal/constants"
|
|
"apskel-pos-be/internal/contract"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type CashAdvanceValidator interface {
|
|
ValidateCreateCashAdvanceRequest(req *contract.CreateCashAdvanceRequest) (error, string)
|
|
ValidateUpdateCashAdvanceRequest(req *contract.UpdateCashAdvanceRequest) (error, string)
|
|
ValidateListCashAdvancesRequest(req *contract.ListCashAdvancesRequest) (error, string)
|
|
}
|
|
|
|
type CashAdvanceValidatorImpl struct{}
|
|
|
|
func NewCashAdvanceValidator() *CashAdvanceValidatorImpl {
|
|
return &CashAdvanceValidatorImpl{}
|
|
}
|
|
|
|
func (v *CashAdvanceValidatorImpl) ValidateCreateCashAdvanceRequest(req *contract.CreateCashAdvanceRequest) (error, string) {
|
|
if req == nil {
|
|
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
|
}
|
|
|
|
if strings.TrimSpace(req.CodeNumber) == "" {
|
|
return errors.New("code_number is required"), constants.MissingFieldErrorCode
|
|
}
|
|
if len(req.CodeNumber) > 50 {
|
|
return errors.New("code_number must be at most 50 characters"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
if req.OutletID != nil && *req.OutletID == uuid.Nil {
|
|
return errors.New("outlet_id cannot be empty"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
// A cash advance is cash handed to a team, so the team is not optional here the way
|
|
// it is on a purchase order: allowClear stays false and an empty scope is rejected.
|
|
if err, code := validatePurchaseTeamSelection(&req.TeamScope, req.TeamCategoryID, false); err != nil {
|
|
return err, code
|
|
}
|
|
|
|
if req.Amount <= 0 {
|
|
return errors.New("amount must be greater than 0"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
issuedDate, err := time.Parse("2006-01-02", strings.TrimSpace(req.IssuedDate))
|
|
if err != nil {
|
|
return errors.New("issued_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
if req.DueDate != nil {
|
|
if strings.TrimSpace(*req.DueDate) == "" {
|
|
return errors.New("due_date cannot be empty"), 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(issuedDate) {
|
|
return errors.New("due_date must be after issued_date"), constants.MalformedFieldErrorCode
|
|
}
|
|
}
|
|
|
|
if req.Status != nil && !constants.IsValidCashAdvanceStatus(*req.Status) {
|
|
return errors.New("status must be one of: " + strings.Join(constants.GetAllCashAdvanceStatuses(), ", ")), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
return nil, ""
|
|
}
|
|
|
|
func (v *CashAdvanceValidatorImpl) ValidateUpdateCashAdvanceRequest(req *contract.UpdateCashAdvanceRequest) (error, string) {
|
|
if req == nil {
|
|
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
|
}
|
|
|
|
if req.CodeNumber != nil {
|
|
if strings.TrimSpace(*req.CodeNumber) == "" {
|
|
return errors.New("code_number cannot be empty"), constants.MalformedFieldErrorCode
|
|
}
|
|
if len(*req.CodeNumber) > 50 {
|
|
return errors.New("code_number must be at most 50 characters"), constants.MalformedFieldErrorCode
|
|
}
|
|
}
|
|
|
|
// The team can be moved but never dropped, so clearing is not allowed here either.
|
|
if err, code := validatePurchaseTeamSelection(req.TeamScope, req.TeamCategoryID, false); err != nil {
|
|
return err, code
|
|
}
|
|
|
|
if req.Amount != nil && *req.Amount <= 0 {
|
|
return errors.New("amount must be greater than 0"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
if req.ReturnedAmount != nil && *req.ReturnedAmount < 0 {
|
|
return errors.New("returned_amount must be greater than or equal to 0"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
var issuedDate *time.Time
|
|
if req.IssuedDate != nil {
|
|
if strings.TrimSpace(*req.IssuedDate) == "" {
|
|
return errors.New("issued_date cannot be empty"), constants.MalformedFieldErrorCode
|
|
}
|
|
parsed, err := time.Parse("2006-01-02", *req.IssuedDate)
|
|
if err != nil {
|
|
return errors.New("issued_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode
|
|
}
|
|
issuedDate = &parsed
|
|
}
|
|
|
|
if req.DueDate != nil {
|
|
if strings.TrimSpace(*req.DueDate) == "" {
|
|
return errors.New("due_date cannot be empty"), 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 issuedDate != nil && dueDate.Before(*issuedDate) {
|
|
return errors.New("due_date must be after issued_date"), constants.MalformedFieldErrorCode
|
|
}
|
|
}
|
|
|
|
if req.Status != nil && !constants.IsValidCashAdvanceStatus(*req.Status) {
|
|
return errors.New("status must be one of: " + strings.Join(constants.GetAllCashAdvanceStatuses(), ", ")), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
return nil, ""
|
|
}
|
|
|
|
func (v *CashAdvanceValidatorImpl) ValidateListCashAdvancesRequest(req *contract.ListCashAdvancesRequest) (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.IsValidCashAdvanceStatus(req.Status) {
|
|
return errors.New("status must be one of: " + strings.Join(constants.GetAllCashAdvanceStatuses(), ", ")), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
if req.SettlementStatus != "" && !constants.IsValidCashAdvanceSettlementStatus(req.SettlementStatus) {
|
|
return errors.New("settlement_status must be one of: " + strings.Join(constants.GetAllCashAdvanceSettlementStatuses(), ", ")), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
if req.Team != "" {
|
|
if req.TeamScope != "" || req.TeamCategoryID != nil {
|
|
return errors.New("team cannot be combined with team_scope or team_category_id"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
// Every cash advance has a team, so unlike purchases there is nothing to filter
|
|
// for "no team yet": only Pusat or a category id make sense here.
|
|
if req.Team != constants.PurchaseTeamScopeCentral {
|
|
if categoryID, err := uuid.Parse(req.Team); err != nil || categoryID == uuid.Nil {
|
|
return errors.New("team must be either central or a category id"), constants.MalformedFieldErrorCode
|
|
}
|
|
}
|
|
}
|
|
|
|
if req.TeamScope != "" {
|
|
validScopes := []string{constants.PurchaseTeamScopeCategory, constants.PurchaseTeamScopeCentral}
|
|
if !contains(validScopes, req.TeamScope) {
|
|
return errors.New("team_scope must be one of: category, central"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
if req.TeamScope == constants.PurchaseTeamScopeCentral && req.TeamCategoryID != nil {
|
|
return errors.New("team_category_id must be empty when team_scope is central"), constants.MalformedFieldErrorCode
|
|
}
|
|
}
|
|
|
|
if req.TeamCategoryID != nil && *req.TeamCategoryID == uuid.Nil {
|
|
return errors.New("team_category_id cannot be empty"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
if req.StartDate != nil && req.EndDate != nil && req.EndDate.Before(*req.StartDate) {
|
|
return errors.New("end_date must be after start_date"), constants.MalformedFieldErrorCode
|
|
}
|
|
|
|
return nil, ""
|
|
}
|