@startuml title 中立 mission 及跟进模块类图 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 "Mission\n任务" as Mission { +id: BigAutoField +merchant: Merchant +description: Text +category: CharField +created_at: DateTime +is_urgent: Boolean = false +is_completed: Boolean = false +is_cancelled: Boolean = false +cancelled_at: DateTime? = null +content_type: ContentType? = null +content_id: PositiveBigInteger? = null +content_object: GenericForeignKey? +creator: Employee +cancelled_by: Employee? = null -- +participants: QuerySet +has_ending_reply: Boolean <> +can_reply: Boolean <> +get_participants(): QuerySet +filter_participants(...): QuerySet +reopen(): Mission } class "MissionParticipant\n任务参与者" as MissionParticipant { +id: BigAutoField +merchant: Merchant +mission: Mission +employee: Employee +created_at: DateTime } class "MissionReply\n任务回应" as MissionReply { +id: BigAutoField +merchant: Merchant +mission: Mission +responder: Employee +content: Text +replied_at: DateTime +ends_task: Boolean = false +is_rejected: Boolean = false +rejected_by: Employee? = null +rejected_at: DateTime? = null } class "Merchant\nbasic_info.Merchant" as Merchant { +id: BigAutoField +name: CharField } class "Employee\nbasic_info.Employee" as Employee { +id: BigAutoField +name: CharField +merchant: Merchant } class "ContentType\ndjango.contrib.contenttypes" as ContentType { +id: AutoField +app_label: CharField +model: CharField } class "Any Business Model\n任意业务模型" as AnyBusinessModel { +id: ... } Mission "1" o-- "0..*" MissionParticipant : participants Mission "0..*" --> "1" Merchant : merchant MissionParticipant "0..*" --> "1" Merchant : merchant MissionReply "0..*" --> "1" Merchant : merchant MissionParticipant "0..*" --> "1" Employee : employee Mission "1" --> "1" Employee : creator Mission "0..*" --> "0..1" Employee : cancelled_by Mission "1" o-- "0..*" MissionReply : replies MissionReply "0..*" --> "1" Employee : responder MissionReply "0..*" --> "0..1" Employee : rejected_by Mission "0..*" --> "0..1" ContentType : content_type Mission ..> AnyBusinessModel : content_object\nGenericForeignKey note right of Mission content_type 与 content_id 均可为空: - 为空:任务不绑定具体业务对象 - 非空:任务可关联系统中任意业务模型 所有人物字段均关联 basic_info.Employee, 不直接关联 request.user / auth.User。 Mission / MissionParticipant / MissionReply 都冗余保存 merchant,用于严格多商户隔离。 end note note bottom of Mission 参与者通过 MissionParticipant 中间表维护。 Mission 模型应提供便捷方法用于获取和筛选参与者, 但关系本身不直接写成裸 ManyToMany 字段, 方便后续扩展参与者状态、角色、加入时间等信息。 end note note right of MissionReply 当任意关联回应 ends_task = true 时: - Mission.has_ending_reply = true - Mission.can_reply = false - 后续不允许继续创建 MissionReply 创建 ends_task = true 的回应时, 本模块 service 同步设置: Mission.is_completed = true end note note left of Mission is_cancelled 默认为 false。 取消任务时写入: - cancelled_by - cancelled_at end note note bottom of MissionReply reopen 独立业务函数的前置条件: - Mission.is_cancelled = false - Mission.is_completed = true - 能查询到 ends_task = true 的 MissionReply reopen 执行效果: - Mission.is_completed = false - 对 ends_task = true 的回应设置 ends_task = false - 同时记录该回应 is_rejected = true、 rejected_by = reopened_by、rejected_at = now end note @enduml