package service import ( "context" "errors" "strings" "time" "wucher/internal/domain/base" "wucher/internal/shared/pkg/appctx" "wucher/internal/shared/pkg/sortkey" ) type BaseService struct { repo base.Repository } func NewBaseService(repo base.Repository) *BaseService { return &BaseService{repo: repo} } var errInvalidBaseCategoryType = errors.New("invalid base category type") func (s *BaseService) CreateBase(ctx context.Context, row *base.Base, categoryType string) error { category, ok := base.NormalizeCategoryType(categoryType) if !ok { return errInvalidBaseCategoryType } if err := validateBaseCoordinates(row); err != nil { return err } row.DefaultStartTimeType = normalizeBaseShiftTimeType(row.DefaultStartTimeType) row.DefaultEndTimeType = normalizeBaseShiftTimeType(row.DefaultEndTimeType) if row.DefaultStartTimeType == base.ShiftTimeTypeFixed && strings.TrimSpace(row.DefaultShiftStart) == "" { return errors.New("default_shift_start is required") } if row.DefaultEndTimeType == base.ShiftTimeTypeFixed && strings.TrimSpace(row.DefaultShiftEnd) == "" { return errors.New("default_shift_end is required") } return s.repo.CreateBase(ctx, row, category) } func (s *BaseService) UpdateBase(ctx context.Context, row *base.Base, categoryType string) error { category, ok := base.NormalizeCategoryType(categoryType) if !ok { return errInvalidBaseCategoryType } if err := validateBaseCoordinates(row); err != nil { return err } row.DefaultStartTimeType = normalizeBaseShiftTimeType(row.DefaultStartTimeType) row.DefaultEndTimeType = normalizeBaseShiftTimeType(row.DefaultEndTimeType) if row.DefaultStartTimeType == base.ShiftTimeTypeFixed && strings.TrimSpace(row.DefaultShiftStart) == "" { return errors.New("default_shift_start is required") } if row.DefaultEndTimeType == base.ShiftTimeTypeFixed && strings.TrimSpace(row.DefaultShiftEnd) == "" { return errors.New("default_shift_end is required") } return s.repo.UpdateBase(ctx, row, category) } func (s *BaseService) DeleteBase(ctx context.Context, id []byte, categoryType string) (*base.Base, error) { category, ok := base.NormalizeCategoryType(categoryType) if !ok { return nil, errInvalidBaseCategoryType } // fetch existing row before deleting so we can return a copy row, err := s.repo.GetBaseByID(ctx, id, category) if err != nil { return nil, err } if err := s.repo.DeleteBase(ctx, id, category); err != nil { return nil, err } // if we had a row, populate DeletedAt/DeletedBy to reflect the repository update if row != nil { now := time.Now().UTC() row.DeletedAt = &now // try to pick actor from context if available if actor := appctx.GetUserID(ctx); len(actor) > 0 { row.DeletedBy = make([]byte, len(actor)) copy(row.DeletedBy, actor) } } return row, nil } func (s *BaseService) GetBaseByID(ctx context.Context, id []byte, categoryType string) (*base.Base, error) { category, ok := base.NormalizeCategoryType(categoryType) if !ok { return nil, errInvalidBaseCategoryType } return s.repo.GetBaseByID(ctx, id, category) } func (s *BaseService) ListBases(ctx context.Context, filter, sort string, limit, offset int, categoryType string, dul *bool) ([]base.Base, int64, error) { if strings.TrimSpace(categoryType) == "" { return s.repo.ListBases(ctx, filter, normalizeBaseSort(sort), limit, offset, "", dul) } category, ok := base.NormalizeCategoryType(categoryType) if !ok { return nil, 0, errInvalidBaseCategoryType } return s.repo.ListBases(ctx, filter, normalizeBaseSort(sort), limit, offset, category, dul) } func normalizeBaseSort(sort string) string { return normalizeSortByRules(sort, sortExpr( joinSortClauses(sortkey.ActivePositiveSortClauses("", "is_active", "sortkey", "base", false)), joinSortClauses(sortkey.ActivePositiveSortClauses("", "is_active", "sortkey", "base", true)), "sortkey", ), sortField("base"), sortField("base_abbreviation"), sortField("notes", "note", "notes"), sortField("is_active"), sortField("created_at"), sortField("updated_at"), ) } func validateBaseCoordinates(row *base.Base) error { if row == nil { return nil } if row.Latitude < -90 || row.Latitude > 90 { return errors.New("latitude must be between -90 and 90") } if row.Longitude < -180 || row.Longitude > 180 { return errors.New("longitude must be between -180 and 180") } return nil } func normalizeBaseShiftTimeType(raw string) string { switch strings.ToUpper(strings.TrimSpace(raw)) { case "", base.ShiftTimeTypeFixed: return base.ShiftTimeTypeFixed case base.ShiftTimeTypeBMCT: return base.ShiftTimeTypeBMCT case base.ShiftTimeTypeECET: return base.ShiftTimeTypeECET default: return strings.ToUpper(strings.TrimSpace(raw)) } }