@startuml
title Notifier 模块一期类图

skinparam shadowing false
skinparam class {
  BackgroundColor #F8FBFF
  BorderColor #4C6A92
  ArrowColor #4C6A92
  FontName Noto Sans CJK SC
}
skinparam note {
  BackgroundColor #FFF7E6
  BorderColor #B7791F
  FontName Noto Sans CJK SC
}
skinparam defaultFontName Noto Sans CJK SC

class "Notifier\n通知器配置" as Notifier {
  +id: BigAutoField
  +merchant: Merchant
  +name: CharField
  +event_key: CharField
  +channel: CharField
  +template_key: CharField
  +is_enabled: Boolean = true
  +config: JSON
  +description: Text?
  +created_at: DateTime
  +updated_at: DateTime
}

class "Merchant\nbasic_info.Merchant" as Merchant {
  +id: BigAutoField
  +name: CharField
}

class "Mission Signals\n领域信号" as MissionSignals <<service>> {
  +mission_created
  +mission_replied
  +mission_completed
  +mission_reply_rejected
  +mission_reopened
  +mission_cancelled
}

class "MissionHandlers\n信号处理器" as MissionHandlers <<service>> {
  +on_mission_created(...)
  +on_mission_replied(...)
  +on_mission_completed(...)
  +on_mission_reply_rejected(...)
  +on_mission_reopened(...)
  +on_mission_cancelled(...)
}

class "NotifierDispatcher\n通知分发器" as NotifierDispatcher <<service>> {
  +dispatch(event_key, merchant_id, payload)
}

class "TemplateRenderer\n模板渲染器" as TemplateRenderer <<service>> {
  +render(template_key, payload): dict
}

abstract class "BaseNotifier\n渠道后端抽象" as BaseNotifier {
  +channel: str
  +notify(endpoint, event_key, payload, rendered): dict
}

class "WeComWebhookNotifier\n企业微信通知器" as WeComWebhookNotifier {
  +channel = \"wecom_webhook\"
  +notify(endpoint, event_key, payload, rendered): dict
}

class "EmailNotifier\n邮件通知器" as EmailNotifier {
  +channel = \"email\"
  +notify(endpoint, event_key, payload, rendered): dict
}

class "NotifierRegistry\n通知器注册表" as NotifierRegistry <<service>> {
  +get(channel): BaseNotifier
}

class "NotifierTask\n异步任务入口" as NotifierTask <<task>> {
  +dispatch_notification_event(...)
}

MissionSignals ..> MissionHandlers : connect
MissionHandlers ..> NotifierTask : on_commit + delay
NotifierTask ..> NotifierDispatcher : dispatch(...)
NotifierDispatcher --> "0..*" Notifier : query by\nmerchant + event_key + is_enabled
NotifierDispatcher ..> TemplateRenderer : render(...)
NotifierDispatcher ..> NotifierRegistry : get(channel)
NotifierRegistry ..> BaseNotifier
BaseNotifier <|-- WeComWebhookNotifier
BaseNotifier <|-- EmailNotifier
Notifier "0..*" --> "1" Merchant : merchant

note right of Notifier
  一期简化设计：
  - 不拆 Endpoint / Subscription
  - 直接把 event_key 绑定在 Notifier 对象上
  - 直接把 template_key 配置在 Notifier 对象上

  适合通过 Django Admin 做：
  - 添加 / 删除
  - 启用 / 禁用
  - 编辑渠道参数
end note

note bottom of Notifier
  config 为 JSON：
  企业微信示例可包含：
  - webhook_key
  - mentioned_list
  - mentioned_mobile_list

  同一商户下可配置多个 Notifier，
  同一个 event_key 可命中多个 Notifier。
end note

note right of TemplateRenderer
  模板文件不落数据库。
  Notifier 仅保存 template_key。

  模板集中存放在固定目录，例如：
  notifier/templates/
end note

note left of MissionHandlers
  handler 不直接拼企业微信内容，
  也不直接读取 settings.py 中的静态 key。

  handler 只负责把领域事件
  转成 event_key + payload，
  再交给 notifier 模块。
end note

note bottom of NotifierDispatcher
  一期暂不引入 NotificationDelivery 表。
  发送结果通过日志记录即可，
  不做数据库级发送审计。

  风险：
  - 无法在数据库中重放 / 检索历史发送记录
  - 排查依赖日志系统
end note

note left of Notifier
  设计取舍风险：
  把 template_key 配到 Notifier 对象上，
  会让“同一渠道目标用于多个事件”时产生配置重复。

  但这能显著降低一期复杂度，
  且更贴合 Admin 直接维护。
end note
@enduml
