package model import ( "encoding/json" "strconv" "strings" "time" "gorm.io/datatypes" ) const accountOutboundEmailCountKey = "_outbound_email_count" // LimitsMap returns the Chatwoot-style account limits JSON object. func (a *Account) LimitsMap() map[string]any { return jsonMap(a.Limits) } // EmailTranscriptEnabled mirrors Account#email_transcript_enabled?. Community // accounts allow transcripts by default; enterprise tests may disable it via // limits or custom attributes. func (a *Account) EmailTranscriptEnabled() bool { if a == nil { return true } for _, attrs := range []map[string]any{a.LimitsMap(), a.CustomAttributesMap()} { if value, ok := firstPresent(attrs, "email_transcript_enabled", "email_transcript"); ok { return truthy(value, true) } } return true } // EmailRateLimit returns the configured daily transcript/outbound email limit. // A zero value means unlimited, matching non-cloud Chatwoot behavior. func (a *Account) EmailRateLimit() int { if a == nil { return 0 } for _, attrs := range []map[string]any{a.LimitsMap(), a.CustomAttributesMap()} { if value, ok := firstPresent(attrs, "emails", "email_rate_limit", "outbound_email_limit"); ok { if n := intValue(value); n > 0 { return n } } } return 0 } func (a *Account) EmailsSentToday(now time.Time) int { attrs := a.CustomAttributesMap() value, ok := attrs[accountOutboundEmailCountKey] if !ok { return intValue(attrs["emails_sent_today"]) } record, ok := value.(map[string]any) if !ok || strings.TrimSpace(stringValue(record["date"])) != now.Format("2006-01-02") { return 0 } return intValue(record["count"]) } func (a *Account) IncrementEmailSentCount(now time.Time) error { attrs := a.CustomAttributesMap() count := a.EmailsSentToday(now) + 1 attrs[accountOutboundEmailCountKey] = map[string]any{ "date": now.Format("2006-01-02"), "count": count, } return a.SetCustomAttributesMap(attrs) } func jsonMap(raw datatypes.JSON) map[string]any { attrs := map[string]any{} if len(raw) > 0 { _ = json.Unmarshal(raw, &attrs) } return attrs } func firstPresent(attrs map[string]any, keys ...string) (any, bool) { for _, key := range keys { if value, ok := attrs[key]; ok { return value, true } } return nil, false } func truthy(value any, defaultValue bool) bool { switch v := value.(type) { case bool: return v case string: s := strings.TrimSpace(strings.ToLower(v)) if s == "" { return defaultValue } return s == "true" || s == "1" || s == "yes" || s == "enabled" case float64: return v != 0 case int: return v != 0 default: return defaultValue } } func intValue(value any) int { switch v := value.(type) { case int: return v case int64: return int(v) case float64: return int(v) case json.Number: n, _ := v.Int64() return int(n) case string: n, _ := strconv.Atoi(strings.TrimSpace(v)) return n default: return 0 } } func stringValue(value any) string { if s, ok := value.(string); ok { return s } return "" }