hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
use crate::ticket_tally::TicketTally;

use super::*;

#[cfg(any(feature = "query_message", feature = "ticket"))]
#[derive(
    Deserialize_repr,
    Serialize_repr,
    Debug,
    Clone,
    Copy,
    Default,
    Hash,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    strum::AsRefStr,
    strum::EnumIter,
)]
#[repr(u32)]
/// In-production `QueryMsg`s in existing DB, -- sent by old Python clients --,
/// use `BeautifulSoup` to format HTML for images and rich text.
/// In addition, this custom `SoupFormat`, -- embedded in the `QueryMsg` upon being sent --,
/// is used later on the recipient-side to determine the type of production event
/// the message was about.
/// LEGACY DESIGN: DO NOT change the `repr` values.
pub enum MsgTopic {
    #[default]
    #[strum(serialize = "Chat")]
    CustomMessage = 2_u32.pow(0),

    #[strum(serialize = "Brief Updated")]
    BriefUpdated = 2_u32.pow(1),

    #[strum(serialize = "Staff Assigned")]
    StaffAssigned = 2_u32.pow(2),

    #[strum(serialize = "WIP|Daily")]
    WipSnapshot = 2_u32.pow(3),

    #[strum(serialize = "Feedback Addressed")]
    /// E.g., when a single `SubTicket` is marked as Addressed by assignees.
    FeedbackSnapshot = 2_u32.pow(4),

    #[strum(serialize = "Asset Uploaded")]
    AssetUploaded = 2_u32.pow(5),

    #[strum(serialize = "Asset Replaced")]
    AssetReplaced = 2_u32.pow(6),

    #[strum(serialize = "AD Feedback")]
    AdFeedback = 2_u32.pow(7),

    #[strum(serialize = "AD Approval")]
    AdApproval = 2_u32.pow(8),

    #[strum(serialize = "Client Feedback")]
    ClientFeedback = 2_u32.pow(9),

    #[strum(serialize = "Client Approval")]
    ClientApproval = 2_u32.pow(10),

    #[strum(serialize = "Submit for Review")]
    /// E.g., when all `SubTicket`s within a container `Ticket` are closed.
    SubmitForReview = 2_u32.pow(11),

    #[strum(serialize = "QC Run")]
    QcRun = 2_u32.pow(12),

    #[strum(serialize = "P4 Bridge")]
    P4Bridge = 2_u32.pow(13),

    // #[strum(serialize = "Unity Bridge")]
    // UnityBridge = 2_u32.pow(14),
    //
    #[strum(serialize = "Outgoing Delivery")]
    OutgoingDelivery = 2_u32.pow(15),

    #[strum(serialize = "Internal Publish")]
    InternalPublish = 2_u32.pow(16),

    #[strum(serialize = "Asset Splitted by Previz")]
    AssetSplittedByPreviz = 2_u32.pow(17),

    #[strum(serialize = "SubTicket Closed")]
    /// E.g., when a single `SubTicket` is marked as Closed by supervisors.
    AddressingAccepted = 2_u32.pow(18),

    // __Placeholder = 2_u32.pow(19),
    //
    /// LEGACY DESIGN: this is used by a separate Defect Tracking system.
    Defect = 2_u32.pow(19),
}

#[cfg(any(feature = "query_message", feature = "ticket"))]
impl MsgTopic {
    pub fn is_handwritten(&self) -> bool {
        matches!(&self, Self::CustomMessage)
    }

    // #[cfg(feature = "query_message")]
    // fn is_topical_asset_name_in_legacy_subject_missing(&self) -> bool {
    //     matches!(&self, Self::AssetUploaded | Self::AssetReplaced)
    // }

    /// Filters out [`MsgTopic`] of "manually composed" or other "non-system" types.
    pub fn allow_subscription(&self) -> bool {
        match self {
            Self::CustomMessage | Self::Defect
            // | Self::__Placeholder 
            => false,
            _ => true,
        }
    }

    pub fn system() -> BTreeSet<Self> {
        Self::iter().filter(|t| t.allow_subscription()).collect()
    }

    /// Filters out `MsgTopic` of all "system", -- auto-sent --, types.
    pub fn handwritten() -> BTreeSet<Self> {
        BTreeSet::from([Self::CustomMessage])
    }

    pub fn all() -> BTreeSet<Self> {
        Self::iter().collect()
    }

    pub fn as_vec_u32(topics: &BTreeSet<Self>) -> Vec<u32> {
        topics.iter().map(|t| *t as u32).collect()
    }

    pub fn into_vec_u32(topics: BTreeSet<Self>) -> Vec<u32> {
        topics.into_iter().map(|t| t as u32).collect()
    }

    /// Makes subject line with the `MsgTopic` and the asset name.
    pub fn subject(&self, asset: &AssetExcerpt) -> String {
        if let Self::AddressingAccepted = &self {
            format!("GREAT JOB! {}", asset.name)
        } else {
            format!("{}: {}", self.as_ref().to_uppercase(), asset.name)
        }
    }

    /// Makes contextual message body. This is intended to be used SOLELY with sytem -- headlessly sent -- message.
    pub fn receipt_content(
        &self,
        assignees: &[Staff],
        working_step: Option<&AssetStatus>,
        phase: Option<&VcsLiteSession>,
        ticket_tally: Option<&TicketTally>,
    ) -> AnyResult<String> {
        let empty_content = || Ok(String::new());

        let content = match self {
            // empty
            Self::BriefUpdated | Self::WipSnapshot | Self::FeedbackSnapshot => empty_content(),

            // uses assignees
            Self::StaffAssigned => Ok(crate::user::make_assignee_str(assignees.iter())),

            // `Defect Tracking`-related
            Self::AdFeedback | Self::ClientFeedback => Ok(if let Some(tally) = ticket_tally {
                tally.genesis_headline()
            } else {
                "Please check".to_owned()
            }),

            Self::AddressingAccepted => Ok(if let Some(tally) = ticket_tally {
                tally.progress_headline()
            } else {
                "Please continue addressing the rest of the Subtickets".to_owned()
            }),

            Self::AdApproval | Self::ClientApproval => Ok("Thanks and Congratz".to_string()),

            Self::SubmitForReview => Ok("Feedback is AD Approved".to_string()),

            // uses phase
            Self::AssetUploaded | Self::AssetReplaced => Ok(format!(
                "Phase: {}",
                phase.context("No working phase")?.as_ref()
            )),

            // unimplemented
            Self::AssetSplittedByPreviz
            | Self::QcRun
            | Self::P4Bridge
            | Self::OutgoingDelivery
            | Self::InternalPublish => Err(anyhow!(
                "Receipt content is unimplemented for {}",
                self.as_ref()
            )),

            // unreachable
            Self::CustomMessage | Self::Defect => {
                Err(anyhow!("Unexpected request of receipt content"))
            }
        };

        if content.is_ok() {
            if let Some(step) = working_step {
                // includes `working_step` in all content
                Ok(format!(
                    "{}; {}",
                    step.as_ref().to_uppercase(),
                    content.unwrap()
                ))
            } else {
                Ok(format!("No Step 😨; {}", content.unwrap()))
            }
        } else {
            content
        }
    }

    /// Makes contextual recipients.
    pub fn receipt_recipients(
        &self,
        assignees: &[Staff],
        roles: &RoleMap,
    ) -> AnyResult<HashSet<Staff>> {
        let a = || assignees.iter().map(|s| s.clone()).collect();
        match self {
            // assignees and all supervisors
            Self::AdFeedback
            | Self::AdApproval
            | Self::BriefUpdated
            | Self::StaffAssigned
            | Self::WipSnapshot
            | Self::FeedbackSnapshot
            | Self::ClientFeedback
            | Self::ClientApproval
            | Self::Defect => Ok(roles.assignees_and_supervisors(&a(), true, false)),

            // everyone except directors
            Self::AddressingAccepted => Ok(roles.assignees_and_supervisors(&a(), false, false)),

            // just assignees and team leads
            Self::SubmitForReview => Ok(roles.assignees_and_team_leads(&a())),

            // all supervisors
            Self::AssetUploaded | Self::AssetReplaced | Self::AssetSplittedByPreviz => {
                Ok(roles.supervisors(true))
            }

            // all supervisors and also watchers
            Self::OutgoingDelivery => Ok(roles.supervisors_and_watchers(true)),

            // supervisors except directors
            Self::InternalPublish => Ok(roles.supervisors(false)),

            // unimplemented
            Self::QcRun | Self::P4Bridge => Err(anyhow!(
                "Receipt recipients is unimplemented for {}",
                self.as_ref()
            )),

            // unreachable
            Self::CustomMessage => Err(anyhow!("Unexpected request of receipt recipients")),
        }
    }
}

#[cfg(all(any(feature = "query_message", feature = "ticket"), feature = "gui"))]
pub fn msg_topic_options_ui(topic: &mut MsgTopic, ui: &mut egui::Ui) {
    egui::ComboBox::from_label("Topic")
        .selected_text(topic.as_ref())
        .show_ui(ui, |ui| {
            for t in MsgTopic::iter() {
                ui.selectable_value(topic, t, t.as_ref());
            }
        });
}

// ----------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    #[cfg(any(feature = "query_message", feature = "ticket"))]
    fn system_qms_topics() {
        let s = MsgTopic::system();
        eprintln!("System message topics: {:?}", s);
        eprintln!("Topics as Vec<u32>: {:?}", MsgTopic::into_vec_u32(s));
    }
}