Restructure the monorepo into clear top-level directories: - backend/: Go module root (cmd, internal, pkg, configs, migrations, docs/swagger, scripts, tests, go.mod, Makefile, .air.toml) - deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd - docs/: project documentation + reports/ (moved from repo root) - AGENTS.md: new AI coding-agent guide at repo root Update all references to the new layout: - Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root) - docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile, env_file ../../.env, volume mounts ../../backend:/app - deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile - CI: working-directory: backend for go commands, file deploy/docker/Dockerfile, coverage path backend/coverage.out, health_check backend/scripts/ - backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../ - README: architecture tree, quickstart, config paths updated Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh, gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to preserve history. Build, vet, SQLite tests, and docker compose config verified.
103 lines
3.8 KiB
Go
103 lines
3.8 KiB
Go
package model
|
|
|
|
import "time"
|
|
|
|
// WorkingHour represents a single day's working hours configuration for an inbox.
|
|
// Reference: Chatwoot app/models/working_hour.rb
|
|
//
|
|
// Each inbox has 7 WorkingHour records (day_of_week 0-6), one per day.
|
|
// Default: weekdays 9:00-17:00, weekends closed_all_day.
|
|
//
|
|
// Table: working_hours
|
|
type WorkingHour struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
InboxID uint `gorm:"not null;index" json:"inbox_id"`
|
|
AccountID uint `gorm:"index" json:"account_id"`
|
|
DayOfWeek int `gorm:"not null" json:"day_of_week"` // 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
ClosedAllDay bool `gorm:"default:false" json:"closed_all_day"` // true → inbox closed entire day
|
|
OpenAllDay bool `gorm:"default:false" json:"open_all_day"` // true → inbox open 24h
|
|
OpenHour *int `gorm:"default:null" json:"open_hour"` // 0-23, nil when closed_all_day or open_all_day
|
|
OpenMinutes *int `gorm:"default:null" json:"open_minutes"` // 0-59
|
|
CloseHour *int `gorm:"default:null" json:"close_hour"` // 0-23
|
|
CloseMinutes *int `gorm:"default:null" json:"close_minutes"` // 0-59
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// TableName returns the GORM table name.
|
|
func (WorkingHour) TableName() string { return "working_hours" }
|
|
|
|
// Validate performs Chatwoot-equivalent validations:
|
|
// - open_hour/close_hour must be 0-23 (unless closed_all_day)
|
|
// - open_minutes/close_minutes must be 0-59 (unless closed_all_day)
|
|
// - close time must be after open time (unless closed_all_day)
|
|
// - open_all_day and closed_all_day cannot both be true
|
|
func (wh *WorkingHour) Validate() map[string]string {
|
|
errors := make(map[string]string)
|
|
|
|
// Chatwoot: open_all_day_and_closed_all_day validation
|
|
if wh.OpenAllDay && wh.ClosedAllDay {
|
|
errors["open_all_day"] = "cannot be true when closed_all_day is also true"
|
|
}
|
|
|
|
if wh.ClosedAllDay {
|
|
return errors // no further validation needed
|
|
}
|
|
|
|
// Chatwoot: inclusion validations (0-23 for hours, 0-59 for minutes)
|
|
if wh.OpenHour != nil {
|
|
if *wh.OpenHour < 0 || *wh.OpenHour > 23 {
|
|
errors["open_hour"] = "must be between 0 and 23"
|
|
}
|
|
} else if !wh.OpenAllDay {
|
|
errors["open_hour"] = "is required when not closed_all_day or open_all_day"
|
|
}
|
|
|
|
if wh.CloseHour != nil {
|
|
if *wh.CloseHour < 0 || *wh.CloseHour > 23 {
|
|
errors["close_hour"] = "must be between 0 and 23"
|
|
}
|
|
} else if !wh.OpenAllDay {
|
|
errors["close_hour"] = "is required when not closed_all_day or open_all_day"
|
|
}
|
|
|
|
if wh.OpenMinutes != nil {
|
|
if *wh.OpenMinutes < 0 || *wh.OpenMinutes > 59 {
|
|
errors["open_minutes"] = "must be between 0 and 59"
|
|
}
|
|
}
|
|
|
|
if wh.CloseMinutes != nil {
|
|
if *wh.CloseMinutes < 0 || *wh.CloseMinutes > 59 {
|
|
errors["close_minutes"] = "must be between 0 and 59"
|
|
}
|
|
}
|
|
|
|
// Chatwoot: close_after_open validation
|
|
// close time must be strictly after open time
|
|
if wh.OpenHour != nil && wh.CloseHour != nil && wh.OpenMinutes != nil && wh.CloseMinutes != nil {
|
|
openTotal := *wh.OpenHour * 60 + *wh.OpenMinutes
|
|
closeTotal := *wh.CloseHour * 60 + *wh.CloseMinutes
|
|
if closeTotal <= openTotal {
|
|
errors["close_hour"] = "close time must be after open time"
|
|
}
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
// EnsureOpenAllDayHours fills open/close fields when open_all_day is true.
|
|
// Chatwoot: before_validation :ensure_open_all_day_hours
|
|
// - When open_all_day: set open_hour=0, open_minutes=0, close_hour=23, close_minutes=59
|
|
func (wh *WorkingHour) EnsureOpenAllDayHours() {
|
|
if wh.OpenAllDay {
|
|
zero := 0
|
|
fiftyNine := 59
|
|
twentyThree := 23
|
|
wh.OpenHour = &zero
|
|
wh.OpenMinutes = &zero
|
|
wh.CloseHour = &twentyThree
|
|
wh.CloseMinutes = &fiftyNine
|
|
wh.ClosedAllDay = false
|
|
}
|
|
} |