Files
gochat/backend/internal/autoassignment/model.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
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.
2026-07-07 14:44:12 +08:00

102 lines
4.6 KiB
Go

package autoassignment
// GORM models for the auto-assignment system.
//
// Reference: Chatwoot AutoAssignment pattern
// - AssignmentPolicy: global policy for how conversations are assigned
// (round_robin, longest_waiting). Has fair_distribution_limit and
// fair_distribution_window for rate limiting per agent.
// - InboxAssignmentPolicy: per-inbox override of the global policy.
// Each inbox can have its own assignment policy and rate limits.
//
// Pattern: gochat models embed model.Base (ID, CreatedAt, UpdatedAt, DeletedAt),
// use TableName() method, and use gorm struct tags.
import (
"github.com/gochat/gochat/internal/model"
)
// AssignmentPolicyType defines the type of auto-assignment policy.
type AssignmentPolicyType string
const (
// PolicyRoundRobin assigns conversations to agents in round-robin order.
// Reference: Chatwoot InboxRoundRobinService
PolicyRoundRobin AssignmentPolicyType = "round_robin"
// PolicyLongestWaiting assigns conversations to the agent with the
// longest idle time since their last assignment.
// Reference: Chatwoot "longest_waiting" policy (planned feature)
PolicyLongestWaiting AssignmentPolicyType = "longest_waiting"
// PolicyLowestLoad assigns conversations to the agent with the
// fewest currently open conversations.
// Reference: Chatwoot "least_busy" concept — agent with lowest workload.
PolicyLowestLoad AssignmentPolicyType = "lowest_load"
)
// AssignmentPolicy represents the global auto-assignment policy for an account.
// Reference: Chatwoot AssignmentPolicy model
// - Defines how unassigned conversations are distributed among agents
// - fair_distribution_limit: max assignments per agent per window (default 5)
// - fair_distribution_window: time window in seconds for rate limiting (default 300 = 5 min)
type AssignmentPolicy struct {
model.Base
AccountID uint `gorm:"index;not null" json:"account_id"`
Policy AssignmentPolicyType `gorm:"size:50;default:round_robin" json:"policy"`
FairDistributionLimit int `gorm:"default:5" json:"fair_distribution_limit"`
FairDistributionWindow int `gorm:"default:300" json:"fair_distribution_window"` // seconds
Active bool `gorm:"default:true" json:"active"`
}
func (AssignmentPolicy) TableName() string { return "assignment_policies" }
// InboxAssignmentPolicy represents a per-inbox override of the global assignment policy.
// Reference: Chatwoot InboxAssignmentPolicy model
// - Each inbox can override the account-level policy
// - If no inbox-specific policy exists, the account policy is used
type InboxAssignmentPolicy struct {
model.Base
AccountID uint `gorm:"index;not null" json:"account_id"`
InboxID uint `gorm:"uniqueIndex;not null" json:"inbox_id"`
Policy AssignmentPolicyType `gorm:"size:50;default:round_robin" json:"policy"`
FairDistributionLimit int `gorm:"default:5" json:"fair_distribution_limit"`
FairDistributionWindow int `gorm:"default:300" json:"fair_distribution_window"` // seconds
Active bool `gorm:"default:true" json:"active"`
}
func (InboxAssignmentPolicy) TableName() string { return "inbox_assignment_policies" }
// EffectivePolicy returns the effective assignment policy for a given inbox.
// If the inbox has a specific policy, it is used; otherwise the account policy is used.
func EffectivePolicy(accountPolicy *AssignmentPolicy, inboxPolicy *InboxAssignmentPolicy) AssignmentPolicyType {
if inboxPolicy != nil && inboxPolicy.Active {
return inboxPolicy.Policy
}
if accountPolicy != nil && accountPolicy.Active {
return accountPolicy.Policy
}
return PolicyRoundRobin // default
}
// EffectiveLimit returns the effective fair distribution limit for a given inbox.
func EffectiveLimit(accountPolicy *AssignmentPolicy, inboxPolicy *InboxAssignmentPolicy) int {
if inboxPolicy != nil && inboxPolicy.Active && inboxPolicy.FairDistributionLimit > 0 {
return inboxPolicy.FairDistributionLimit
}
if accountPolicy != nil && accountPolicy.FairDistributionLimit > 0 {
return accountPolicy.FairDistributionLimit
}
return 5 // default
}
// EffectiveWindow returns the effective fair distribution window (in seconds).
func EffectiveWindow(accountPolicy *AssignmentPolicy, inboxPolicy *InboxAssignmentPolicy) int {
if inboxPolicy != nil && inboxPolicy.Active && inboxPolicy.FairDistributionWindow > 0 {
return inboxPolicy.FairDistributionWindow
}
if accountPolicy != nil && accountPolicy.FairDistributionWindow > 0 {
return accountPolicy.FairDistributionWindow
}
return 300 // default (5 minutes)
}