init push
This commit is contained in:
416
internal/service/base_write_service.go
Normal file
416
internal/service/base_write_service.go
Normal file
@@ -0,0 +1,416 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"wucher/internal/domain/base"
|
||||
"wucher/internal/shared/pkg/uuidv7"
|
||||
)
|
||||
|
||||
type BaseWriteError struct {
|
||||
Status int
|
||||
Title string
|
||||
Detail string
|
||||
Pointer string
|
||||
}
|
||||
|
||||
func (e *BaseWriteError) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return e.Detail
|
||||
}
|
||||
|
||||
type BaseWriteResult struct {
|
||||
Row *base.Base
|
||||
CategoryType string
|
||||
PreviousFotoAttachmentID []byte
|
||||
}
|
||||
|
||||
type BaseOperationalShiftTimeInput struct {
|
||||
DateStart time.Time
|
||||
DateEnd time.Time
|
||||
StartTimeType string
|
||||
EndTimeType string
|
||||
ShiftStart *time.Time
|
||||
ShiftEnd *time.Time
|
||||
}
|
||||
|
||||
type BaseCreateInput struct {
|
||||
ID []byte
|
||||
CategoryType string
|
||||
BaseName string
|
||||
BaseAbbreviation string
|
||||
Address string
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
LandlineNumber string
|
||||
MobileNumber string
|
||||
Email string
|
||||
SortKey *int
|
||||
SMSAlert bool
|
||||
Checklist bool
|
||||
LegTime string
|
||||
DefaultStartTimeType string
|
||||
DefaultEndTimeType string
|
||||
DefaultShiftStart *time.Time
|
||||
DefaultShiftEnd *time.Time
|
||||
UTC string
|
||||
Dry bool
|
||||
ControlCenter bool
|
||||
DUL bool
|
||||
Notes string
|
||||
HEMSEDCContactIDs [][]byte
|
||||
MedPaxContactIDs [][]byte
|
||||
ResponsiblePilotContactIDs [][]byte
|
||||
OperationalShiftTimes []BaseOperationalShiftTimeInput
|
||||
IsActive bool
|
||||
CreatedBy []byte
|
||||
UpdatedBy []byte
|
||||
FotoAttachmentID []byte
|
||||
}
|
||||
|
||||
type BaseUpdateInput struct {
|
||||
ID []byte
|
||||
CategoryType string
|
||||
BaseName *string
|
||||
BaseAbbreviation *string
|
||||
Address *string
|
||||
Latitude *float64
|
||||
Longitude *float64
|
||||
LandlineNumber *string
|
||||
MobileNumber *string
|
||||
Email *string
|
||||
SortKeySet bool
|
||||
SortKeyValid bool
|
||||
SortKeyValue *int
|
||||
SMSAlert *bool
|
||||
Checklist *bool
|
||||
LegTime *string
|
||||
DefaultStartTimeType *string
|
||||
DefaultEndTimeType *string
|
||||
DefaultShiftStart *time.Time
|
||||
DefaultShiftEnd *time.Time
|
||||
UTC *string
|
||||
Dry *bool
|
||||
ControlCenter *bool
|
||||
DUL *bool
|
||||
Notes *string
|
||||
HEMSEDCContactIDs [][]byte
|
||||
HEMSEDCContactIDsSet bool
|
||||
MedPaxContactIDs [][]byte
|
||||
MedPaxContactIDsSet bool
|
||||
ResponsiblePilotContactIDs [][]byte
|
||||
ResponsiblePilotContactIDsSet bool
|
||||
OperationalShiftTimes []BaseOperationalShiftTimeInput
|
||||
OperationalShiftTimesSet bool
|
||||
IsActive *bool
|
||||
CreatedBy []byte
|
||||
CreatedBySet bool
|
||||
UpdatedBy []byte
|
||||
UpdatedBySet bool
|
||||
FotoAttachmentID []byte
|
||||
FotoAttachmentSet bool
|
||||
}
|
||||
|
||||
func (s *BaseService) CreateDetailed(ctx context.Context, in BaseCreateInput) (*BaseWriteResult, error) {
|
||||
if s == nil || s.repo == nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusInternalServerError, Title: "Create failed", Detail: "base service is not configured"}
|
||||
}
|
||||
category, ok := base.NormalizeCategoryType(in.CategoryType)
|
||||
if !ok {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "invalid base category type", Pointer: "/data/attributes/base_category"}
|
||||
}
|
||||
baseName := strings.TrimSpace(in.BaseName)
|
||||
if baseName == "" {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "base is required", Pointer: "/data/attributes/base"}
|
||||
}
|
||||
if in.Latitude < -90 || in.Latitude > 90 {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "latitude must be between -90 and 90", Pointer: "/data/attributes/latitude"}
|
||||
}
|
||||
if in.Longitude < -180 || in.Longitude > 180 {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "longitude must be between -180 and 180", Pointer: "/data/attributes/longitude"}
|
||||
}
|
||||
defaultStartTimeType := normalizeShiftTimeType(in.DefaultStartTimeType)
|
||||
defaultEndTimeType := normalizeShiftTimeType(in.DefaultEndTimeType)
|
||||
if defaultStartTimeType == base.ShiftTimeTypeFixed && in.DefaultShiftStart == nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "default_shift_start is required", Pointer: "/data/attributes/default_shift_start"}
|
||||
}
|
||||
if defaultEndTimeType == base.ShiftTimeTypeFixed && in.DefaultShiftEnd == nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "default_shift_end is required", Pointer: "/data/attributes/default_shift_end"}
|
||||
}
|
||||
|
||||
row := &base.Base{
|
||||
ID: append([]byte(nil), in.ID...),
|
||||
BaseName: baseName,
|
||||
BaseAbbreviation: strings.TrimSpace(in.BaseAbbreviation),
|
||||
Address: strings.TrimSpace(in.Address),
|
||||
Latitude: in.Latitude,
|
||||
Longitude: in.Longitude,
|
||||
LandlineNumber: strings.TrimSpace(in.LandlineNumber),
|
||||
MobileNumber: strings.TrimSpace(in.MobileNumber),
|
||||
Email: strings.TrimSpace(in.Email),
|
||||
SortKey: cloneIntPtr(in.SortKey),
|
||||
SMSAlert: in.SMSAlert,
|
||||
Checklist: in.Checklist,
|
||||
LegTime: strings.TrimSpace(in.LegTime),
|
||||
DefaultStartTimeType: defaultStartTimeType,
|
||||
DefaultEndTimeType: defaultEndTimeType,
|
||||
DefaultShiftStart: timePtrToClockString(in.DefaultShiftStart),
|
||||
DefaultShiftEnd: timePtrToClockString(in.DefaultShiftEnd),
|
||||
UTC: normalizeUTCString(in.UTC),
|
||||
Dry: in.Dry,
|
||||
ControlCenter: in.ControlCenter,
|
||||
DUL: in.DUL,
|
||||
Notes: strings.TrimSpace(in.Notes),
|
||||
IsActive: in.IsActive,
|
||||
HEMSEDCContactIDs: appendUniqueIDs(in.HEMSEDCContactIDs),
|
||||
MedPaxContactIDs: appendUniqueIDs(in.MedPaxContactIDs),
|
||||
ResponsiblePilotContactIDs: appendUniqueIDs(in.ResponsiblePilotContactIDs),
|
||||
OperationalShiftTimes: cloneOperationalShiftTimeInputs(in.OperationalShiftTimes),
|
||||
CreatedBy: append([]byte(nil), in.CreatedBy...),
|
||||
UpdatedBy: append([]byte(nil), in.UpdatedBy...),
|
||||
FotoAttachmentID: append([]byte(nil), in.FotoAttachmentID...),
|
||||
}
|
||||
if len(row.ID) == 0 {
|
||||
row.ID = uuidv7.MustBytes()
|
||||
}
|
||||
if err := s.CreateBase(ctx, row, category); err != nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusBadRequest, Title: "Create failed", Detail: err.Error()}
|
||||
}
|
||||
created, err := s.GetBaseByID(ctx, row.ID, category)
|
||||
if err != nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusBadRequest, Title: "Create failed", Detail: err.Error()}
|
||||
}
|
||||
if created == nil {
|
||||
created = row
|
||||
}
|
||||
return &BaseWriteResult{Row: created, CategoryType: category}, nil
|
||||
}
|
||||
|
||||
func (s *BaseService) UpdateDetailed(ctx context.Context, in BaseUpdateInput) (*BaseWriteResult, error) {
|
||||
if s == nil || s.repo == nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusInternalServerError, Title: "Update failed", Detail: "base service is not configured"}
|
||||
}
|
||||
category, ok := base.NormalizeCategoryType(in.CategoryType)
|
||||
if !ok {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "invalid base category type", Pointer: "/data/attributes/base_category"}
|
||||
}
|
||||
if len(in.ID) != 16 {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "uuid is invalid UUID", Pointer: "/path/uuid"}
|
||||
}
|
||||
|
||||
existing, err := s.GetBaseByID(ctx, in.ID, category)
|
||||
if err != nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusNotFound, Title: "Not found", Detail: categoryNotFoundDetail(category)}
|
||||
}
|
||||
if existing == nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusNotFound, Title: "Not found", Detail: categoryNotFoundDetail(category)}
|
||||
}
|
||||
prevFotoAttachmentID := append([]byte(nil), existing.FotoAttachmentID...)
|
||||
if in.Latitude != nil && (*in.Latitude < -90 || *in.Latitude > 90) {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "latitude must be between -90 and 90", Pointer: "/data/attributes/latitude"}
|
||||
}
|
||||
if in.Longitude != nil && (*in.Longitude < -180 || *in.Longitude > 180) {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "longitude must be between -180 and 180", Pointer: "/data/attributes/longitude"}
|
||||
}
|
||||
|
||||
if in.BaseName != nil {
|
||||
v := strings.TrimSpace(*in.BaseName)
|
||||
if v == "" {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "base cannot be empty", Pointer: "/data/attributes/base"}
|
||||
}
|
||||
existing.BaseName = v
|
||||
}
|
||||
if in.BaseAbbreviation != nil {
|
||||
existing.BaseAbbreviation = strings.TrimSpace(*in.BaseAbbreviation)
|
||||
}
|
||||
if in.Address != nil {
|
||||
existing.Address = strings.TrimSpace(*in.Address)
|
||||
}
|
||||
if in.Latitude != nil {
|
||||
existing.Latitude = *in.Latitude
|
||||
}
|
||||
if in.Longitude != nil {
|
||||
existing.Longitude = *in.Longitude
|
||||
}
|
||||
if in.LandlineNumber != nil {
|
||||
existing.LandlineNumber = strings.TrimSpace(*in.LandlineNumber)
|
||||
}
|
||||
if in.MobileNumber != nil {
|
||||
existing.MobileNumber = strings.TrimSpace(*in.MobileNumber)
|
||||
}
|
||||
if in.Email != nil {
|
||||
existing.Email = strings.TrimSpace(*in.Email)
|
||||
}
|
||||
if in.SortKeySet {
|
||||
if in.SortKeyValid {
|
||||
existing.SortKey = cloneIntPtr(in.SortKeyValue)
|
||||
} else {
|
||||
existing.SortKey = nil
|
||||
}
|
||||
}
|
||||
if in.SMSAlert != nil {
|
||||
existing.SMSAlert = *in.SMSAlert
|
||||
}
|
||||
if in.Checklist != nil {
|
||||
existing.Checklist = *in.Checklist
|
||||
}
|
||||
if in.LegTime != nil {
|
||||
existing.LegTime = strings.TrimSpace(*in.LegTime)
|
||||
}
|
||||
if in.DefaultStartTimeType != nil {
|
||||
existing.DefaultStartTimeType = normalizeShiftTimeType(*in.DefaultStartTimeType)
|
||||
}
|
||||
if in.DefaultEndTimeType != nil {
|
||||
existing.DefaultEndTimeType = normalizeShiftTimeType(*in.DefaultEndTimeType)
|
||||
}
|
||||
if in.DefaultShiftStart != nil {
|
||||
existing.DefaultShiftStart = timePtrToClockString(in.DefaultShiftStart)
|
||||
}
|
||||
if in.DefaultShiftEnd != nil {
|
||||
existing.DefaultShiftEnd = timePtrToClockString(in.DefaultShiftEnd)
|
||||
}
|
||||
if normalizeShiftTimeType(existing.DefaultStartTimeType) != base.ShiftTimeTypeFixed {
|
||||
existing.DefaultShiftStart = ""
|
||||
}
|
||||
if normalizeShiftTimeType(existing.DefaultEndTimeType) != base.ShiftTimeTypeFixed {
|
||||
existing.DefaultShiftEnd = ""
|
||||
}
|
||||
if normalizeShiftTimeType(existing.DefaultStartTimeType) == base.ShiftTimeTypeFixed && strings.TrimSpace(existing.DefaultShiftStart) == "" {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "default_shift_start is required", Pointer: "/data/attributes/default_shift_start"}
|
||||
}
|
||||
if normalizeShiftTimeType(existing.DefaultEndTimeType) == base.ShiftTimeTypeFixed && strings.TrimSpace(existing.DefaultShiftEnd) == "" {
|
||||
return nil, &BaseWriteError{Status: http.StatusUnprocessableEntity, Title: "Validation error", Detail: "default_shift_end is required", Pointer: "/data/attributes/default_shift_end"}
|
||||
}
|
||||
if in.UTC != nil {
|
||||
existing.UTC = normalizeUTCString(*in.UTC)
|
||||
}
|
||||
if in.Dry != nil {
|
||||
existing.Dry = *in.Dry
|
||||
}
|
||||
if in.ControlCenter != nil {
|
||||
existing.ControlCenter = *in.ControlCenter
|
||||
}
|
||||
if in.DUL != nil {
|
||||
existing.DUL = *in.DUL
|
||||
}
|
||||
if in.Notes != nil {
|
||||
existing.Notes = strings.TrimSpace(*in.Notes)
|
||||
}
|
||||
if in.HEMSEDCContactIDsSet {
|
||||
existing.HEMSEDCContactIDs = appendUniqueIDs(in.HEMSEDCContactIDs)
|
||||
}
|
||||
if in.MedPaxContactIDsSet {
|
||||
existing.MedPaxContactIDs = appendUniqueIDs(in.MedPaxContactIDs)
|
||||
}
|
||||
if in.ResponsiblePilotContactIDsSet {
|
||||
existing.ResponsiblePilotContactIDs = appendUniqueIDs(in.ResponsiblePilotContactIDs)
|
||||
}
|
||||
if in.OperationalShiftTimesSet {
|
||||
existing.OperationalShiftTimes = cloneOperationalShiftTimeInputs(in.OperationalShiftTimes)
|
||||
if existing.OperationalShiftTimes == nil {
|
||||
existing.OperationalShiftTimes = []base.BaseOperationalShiftTime{}
|
||||
}
|
||||
} else {
|
||||
existing.OperationalShiftTimes = nil
|
||||
}
|
||||
if in.CreatedBySet {
|
||||
existing.CreatedBy = append([]byte(nil), in.CreatedBy...)
|
||||
}
|
||||
if in.UpdatedBySet {
|
||||
existing.UpdatedBy = append([]byte(nil), in.UpdatedBy...)
|
||||
}
|
||||
if in.FotoAttachmentSet {
|
||||
existing.FotoAttachmentID = append([]byte(nil), in.FotoAttachmentID...)
|
||||
}
|
||||
if in.IsActive != nil {
|
||||
existing.IsActive = *in.IsActive
|
||||
}
|
||||
|
||||
if err := s.UpdateBase(ctx, existing, category); err != nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusBadRequest, Title: "Update failed", Detail: err.Error()}
|
||||
}
|
||||
updated, err := s.GetBaseByID(ctx, existing.ID, category)
|
||||
if err != nil {
|
||||
return nil, &BaseWriteError{Status: http.StatusBadRequest, Title: "Update failed", Detail: err.Error()}
|
||||
}
|
||||
if updated == nil {
|
||||
updated = existing
|
||||
}
|
||||
return &BaseWriteResult{Row: updated, CategoryType: category, PreviousFotoAttachmentID: prevFotoAttachmentID}, nil
|
||||
}
|
||||
|
||||
func cloneOperationalShiftTimeInputs(items []BaseOperationalShiftTimeInput) []base.BaseOperationalShiftTime {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]base.BaseOperationalShiftTime, 0, len(items))
|
||||
for i := range items {
|
||||
dateStart := items[i].DateStart.UTC()
|
||||
dateEnd := items[i].DateEnd.UTC()
|
||||
out = append(out, base.BaseOperationalShiftTime{
|
||||
DateStart: &dateStart,
|
||||
DateEnd: &dateEnd,
|
||||
StartTimeType: normalizeShiftTimeType(items[i].StartTimeType),
|
||||
EndTimeType: normalizeShiftTimeType(items[i].EndTimeType),
|
||||
ShiftStart: timePtrToClockString(items[i].ShiftStart),
|
||||
ShiftEnd: timePtrToClockString(items[i].ShiftEnd),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func timePtrToClockString(v *time.Time) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return v.UTC().Format("15:04:05")
|
||||
}
|
||||
|
||||
func normalizeUTCString(raw string) string {
|
||||
return strings.TrimSpace(raw)
|
||||
}
|
||||
|
||||
func normalizeShiftTimeType(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))
|
||||
}
|
||||
}
|
||||
|
||||
func appendUniqueIDs(values [][]byte) [][]byte {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([][]byte, 0, len(values))
|
||||
seen := map[string]struct{}{}
|
||||
for i := range values {
|
||||
if len(values[i]) != 16 {
|
||||
continue
|
||||
}
|
||||
key := string(values[i])
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
out = append(out, append([]byte(nil), values[i]...))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func categoryNotFoundDetail(category string) string {
|
||||
if category == base.CategoryKeyHEMS {
|
||||
return "hems base not found"
|
||||
}
|
||||
return "base not found"
|
||||
}
|
||||
Reference in New Issue
Block a user