Files
MailUI4Agents/server/internal/models/calendar.go

177 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package models
import (
"strings"
"time"
)
// CalendarEvent 日历事件。
//
// 设计参照 Outlook事件有时间、提醒、收件人触发时产生一封邮件。
// 事件本身是日历实体,提醒是触发器,邮件是投递通道 —— 三者分离。
type CalendarEvent struct {
EventID string `json:"event_id"`
Title string `json:"title"`
Description string `json:"description"`
ReminderText string `json:"reminder_text"`
// AgentName / ToAddress 是**单收件人时代的字段**,保留作兼容与兜底:
// Recipients 为空时用它们。新代码一律读 EffectiveRecipients()。
AgentName string `json:"agent_name"`
ToAddress string `json:"to_address"`
// Recipients 是完整的收件人列表(每项是完整三维地址串)。
//
// 为什么不用 []Address 而用 []string地址的三段语义尤其 session 位的
// new/别名三态)在**触发那一刻**才该被解析 —— 存结构化的话,
// 「.new」这种一次性语义在建事件时就被固化而重复事件每次触发都该
// 重新决定落到哪条会话。存原始串让 ParseAddress 在投递时做这个决定。
Recipients []string `json:"recipients"`
// DeliveryMode 决定多收件人怎么投:
// "separate"(默认)—— 每人各发一封,落在各自的会话里,互相看不到
// "together" —— 第一个是主收件人,其余进 cc_list共享同一条线索
//
// 两种语义都需要而不是二选一:「让三个 Agent 各自独立汇报」与
// 「让 pi 主办、dsh 知情」是完全不同的任务形态,用错会让协作失败 ——
// 前者用 together 会让三个 Agent 互相看到对方的回复而趋同,
// 后者用 separate 会让 dsh 完全不知道 pi 在做什么。
DeliveryMode string `json:"delivery_mode"`
EventTime time.Time `json:"event_time"`
RemindBefore int `json:"remind_before"` // 提前多少分钟
// Recurrence公历 none/daily/weekly/monthly/yearly + 农历两种
// lunar_monthly —— 每农历月同一日(如每月十五)
// lunar_yearly —— 每农历年同月同日(过农历生日/祭日)
//
// lunar_daily 不存在:农历的「日」与公历同长,那就是 daily。
// lunar_weekly 也不存在:农历没有「周」这个单位。
Recurrence string `json:"recurrence"`
RecurrenceEnd *time.Time `json:"recurrence_end,omitempty"`
Status string `json:"status"` // active/paused/cancelled
LastFiredAt *time.Time `json:"last_fired_at,omitempty"`
// PermissionMode 是事件触发时新建会话应采用的档位plan / workspace / full
// 空 = workspace默认
// 复用已有会话时不能直接搬用:要 ModeAtMost(会话现档, 事件档) ——
// 事件档表示「这件事允许到什么程度」而会话现档可能更严plan 档派出的
// 任务不该因为日程触发就偷偷升到 workspace
PermissionMode string `json:"permission_mode"`
// FiredFor 是已触发的那个 occurrence值 = 当时的 EventTime
// 去重靠它与 EventTime 相等判断,不是拿 LastFiredAt 比大小 ——
// DueEvents 有 60 秒 lookahead后者在窗口内恒为真会导致每 tick 重发。
FiredFor *time.Time `json:"fired_for,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CreatedBy string `json:"created_by"`
}
// 重复规则常量。农历规则单独一组:它们的推进要经过 internal/lunar
// 不能像公历那样 AddDate 固定天数(农历月 29~30 天、闰年 13 个月)。
const (
RecurNone = "none"
RecurDaily = "daily"
RecurWeekly = "weekly"
RecurMonthly = "monthly"
RecurYearly = "yearly"
RecurLunarMonthly = "lunar_monthly"
RecurLunarYearly = "lunar_yearly"
)
// IsLunarRecurrence 判断一条重复规则是否按农历推进。
func IsLunarRecurrence(r string) bool {
return r == RecurLunarMonthly || r == RecurLunarYearly
}
// 事件状态常量。
//
// 此前这三个值只以裸字符串形式散落在 handler、scheduler 与前端里,而更新端点
// 把 `status` 原样写进库 —— 于是一个拼错的值(比如 "pause")会变成一个
// **调度器不认识的状态**DueEvents 只查 status='active',那条提醒于是静默失效。
// 人以为自己只是暂停了它,实际上再也恢复不了(界面的下拉框里没有这个选项)。
//
// 提成常量后handler.validEventStatus 能对着这一份清单校验。
const (
// EventActive 生效中:到点会触发提醒。
EventActive = "active"
// EventPaused 暂停:保留事件与重复规则,但不触发。
EventPaused = "paused"
// EventCancelled 已取消:保留历史记录,不再触发也不再推进重复。
EventCancelled = "cancelled"
)
// ValidEventStatus 判断状态取值是否合法。
func ValidEventStatus(s string) bool {
switch s {
case EventActive, EventPaused, EventCancelled:
return true
}
return false
}
// 投递模式常量。
const (
DeliverSeparate = "separate"
DeliverTogether = "together"
)
// EffectiveRecipients 返回真正要投的收件人列表。
//
// Recipients 优先;为空时退回 ToAddress再退回 AgentName。
// 这个兜底链让旧数据(只有 agent_name 的事件)继续工作 ——
// 历史事件不迁移,读的时候归一化。
func (e *CalendarEvent) EffectiveRecipients() []string {
if len(e.Recipients) > 0 {
out := make([]string, 0, len(e.Recipients))
for _, r := range e.Recipients {
if r = strings.TrimSpace(r); r != "" {
out = append(out, r)
}
}
if len(out) > 0 {
return out
}
}
if a := strings.TrimSpace(e.ToAddress); a != "" {
return []string{a}
}
if a := strings.TrimSpace(e.AgentName); a != "" {
return []string{a}
}
return nil
}
// EffectiveDeliveryMode 归一化投递模式,未知值按 separate 处理。
//
// 默认 separate 而不是 togetherseparate 的失败是「Agent 各干各的」,
// together 的失败是「本该独立的 Agent 互相污染了上下文」——
// 后者更难发现也更难挽回。
func (e *CalendarEvent) EffectiveDeliveryMode() string {
if e.DeliveryMode == DeliverTogether {
return DeliverTogether
}
return DeliverSeparate
}
// CalendarAttachment 事件附件。
type CalendarAttachment struct {
AttachmentID string `json:"attachment_id"`
EventID string `json:"event_id"`
Filename string `json:"filename"`
SHA256 string `json:"sha256"`
SizeBytes int64 `json:"size_bytes"`
CreatedAt time.Time `json:"created_at"`
}
// CalendarView 日历视图(月/周/日)。
type CalendarView struct {
Events []CalendarEvent `json:"events"`
// 当前时间线上的事件数(用于统计徽章)
UpcomingCount int `json:"upcoming_count"`
TodayCount int `json:"today_count"`
}