implement governance and flow functionality
This commit is contained in:
378
actix_mvc_app/src/models/flow.rs
Normal file
378
actix_mvc_app/src/models/flow.rs
Normal file
@@ -0,0 +1,378 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Status of a flow
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum FlowStatus {
|
||||
/// Flow is in progress
|
||||
InProgress,
|
||||
/// Flow is completed
|
||||
Completed,
|
||||
/// Flow is stuck at a step
|
||||
Stuck,
|
||||
/// Flow is cancelled
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FlowStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FlowStatus::InProgress => write!(f, "In Progress"),
|
||||
FlowStatus::Completed => write!(f, "Completed"),
|
||||
FlowStatus::Stuck => write!(f, "Stuck"),
|
||||
FlowStatus::Cancelled => write!(f, "Cancelled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of flow
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum FlowType {
|
||||
/// Company registration flow
|
||||
CompanyRegistration,
|
||||
/// User onboarding flow
|
||||
UserOnboarding,
|
||||
/// Service activation flow
|
||||
ServiceActivation,
|
||||
/// Payment processing flow
|
||||
PaymentProcessing,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FlowType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FlowType::CompanyRegistration => write!(f, "Company Registration"),
|
||||
FlowType::UserOnboarding => write!(f, "User Onboarding"),
|
||||
FlowType::ServiceActivation => write!(f, "Service Activation"),
|
||||
FlowType::PaymentProcessing => write!(f, "Payment Processing"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter for flows
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum FlowFilter {
|
||||
/// All flows
|
||||
All,
|
||||
/// Only in progress flows
|
||||
InProgress,
|
||||
/// Only completed flows
|
||||
Completed,
|
||||
/// Only stuck flows
|
||||
Stuck,
|
||||
/// Only cancelled flows
|
||||
Cancelled,
|
||||
/// Flows of a specific type
|
||||
ByType(FlowType),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FlowFilter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FlowFilter::All => write!(f, "All"),
|
||||
FlowFilter::InProgress => write!(f, "In Progress"),
|
||||
FlowFilter::Completed => write!(f, "Completed"),
|
||||
FlowFilter::Stuck => write!(f, "Stuck"),
|
||||
FlowFilter::Cancelled => write!(f, "Cancelled"),
|
||||
FlowFilter::ByType(flow_type) => write!(f, "Type: {}", flow_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A step in a flow
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FlowStep {
|
||||
/// Step ID
|
||||
pub id: String,
|
||||
/// Step name
|
||||
pub name: String,
|
||||
/// Step description
|
||||
pub description: String,
|
||||
/// Step status
|
||||
pub status: StepStatus,
|
||||
/// Step order in the flow
|
||||
pub order: u32,
|
||||
/// Step started at
|
||||
pub started_at: Option<DateTime<Utc>>,
|
||||
/// Step completed at
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
/// Step logs
|
||||
pub logs: Vec<FlowLog>,
|
||||
}
|
||||
|
||||
impl FlowStep {
|
||||
/// Creates a new flow step
|
||||
pub fn new(name: String, description: String, order: u32) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
name,
|
||||
description,
|
||||
status: StepStatus::Pending,
|
||||
order,
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
logs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the step
|
||||
pub fn start(&mut self) {
|
||||
self.status = StepStatus::InProgress;
|
||||
self.started_at = Some(Utc::now());
|
||||
self.add_log("Step started".to_string());
|
||||
}
|
||||
|
||||
/// Completes the step
|
||||
pub fn complete(&mut self) {
|
||||
self.status = StepStatus::Completed;
|
||||
self.completed_at = Some(Utc::now());
|
||||
self.add_log("Step completed".to_string());
|
||||
}
|
||||
|
||||
/// Marks the step as stuck
|
||||
pub fn mark_stuck(&mut self, reason: String) {
|
||||
self.status = StepStatus::Stuck;
|
||||
self.add_log(format!("Step stuck: {}", reason));
|
||||
}
|
||||
|
||||
/// Adds a log entry to the step
|
||||
pub fn add_log(&mut self, message: String) {
|
||||
self.logs.push(FlowLog::new(message));
|
||||
}
|
||||
}
|
||||
|
||||
/// Status of a step in a flow
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum StepStatus {
|
||||
/// Step is pending
|
||||
Pending,
|
||||
/// Step is in progress
|
||||
InProgress,
|
||||
/// Step is completed
|
||||
Completed,
|
||||
/// Step is stuck
|
||||
Stuck,
|
||||
/// Step is skipped
|
||||
Skipped,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for StepStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
StepStatus::Pending => write!(f, "Pending"),
|
||||
StepStatus::InProgress => write!(f, "In Progress"),
|
||||
StepStatus::Completed => write!(f, "Completed"),
|
||||
StepStatus::Stuck => write!(f, "Stuck"),
|
||||
StepStatus::Skipped => write!(f, "Skipped"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A log entry in a flow step
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FlowLog {
|
||||
/// Log ID
|
||||
pub id: String,
|
||||
/// Log message
|
||||
pub message: String,
|
||||
/// Log timestamp
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl FlowLog {
|
||||
/// Creates a new flow log
|
||||
pub fn new(message: String) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
message,
|
||||
timestamp: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A flow with multiple steps
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Flow {
|
||||
/// Flow ID
|
||||
pub id: String,
|
||||
/// Flow name
|
||||
pub name: String,
|
||||
/// Flow description
|
||||
pub description: String,
|
||||
/// Flow type
|
||||
pub flow_type: FlowType,
|
||||
/// Flow status
|
||||
pub status: FlowStatus,
|
||||
/// Flow owner ID
|
||||
pub owner_id: String,
|
||||
/// Flow owner name
|
||||
pub owner_name: String,
|
||||
/// Flow created at
|
||||
pub created_at: DateTime<Utc>,
|
||||
/// Flow updated at
|
||||
pub updated_at: DateTime<Utc>,
|
||||
/// Flow completed at
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
/// Flow steps
|
||||
pub steps: Vec<FlowStep>,
|
||||
/// Progress percentage
|
||||
pub progress_percentage: u8,
|
||||
/// Current step
|
||||
pub current_step: Option<FlowStep>,
|
||||
}
|
||||
|
||||
impl Flow {
|
||||
/// Creates a new flow
|
||||
pub fn new(name: &str, description: &str, flow_type: FlowType, owner_id: &str, owner_name: &str) -> Self {
|
||||
let id = Uuid::new_v4().to_string();
|
||||
let now = Utc::now();
|
||||
let steps = vec![
|
||||
FlowStep::new("Initialization".to_string(), "Setting up the flow".to_string(), 1),
|
||||
FlowStep::new("Processing".to_string(), "Processing the flow data".to_string(), 2),
|
||||
FlowStep::new("Finalization".to_string(), "Completing the flow".to_string(), 3),
|
||||
];
|
||||
|
||||
// Set the first step as in progress
|
||||
let mut flow = Self {
|
||||
id,
|
||||
name: name.to_string(),
|
||||
description: description.to_string(),
|
||||
flow_type,
|
||||
status: FlowStatus::InProgress,
|
||||
owner_id: owner_id.to_string(),
|
||||
owner_name: owner_name.to_string(),
|
||||
steps,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
completed_at: None,
|
||||
progress_percentage: 0,
|
||||
current_step: None,
|
||||
};
|
||||
|
||||
// Calculate progress and set current step
|
||||
flow.update_progress();
|
||||
|
||||
flow
|
||||
}
|
||||
|
||||
fn update_progress(&mut self) {
|
||||
// Calculate progress percentage
|
||||
let total_steps = self.steps.len();
|
||||
if total_steps == 0 {
|
||||
self.progress_percentage = 100;
|
||||
return;
|
||||
}
|
||||
|
||||
let completed_steps = self.steps.iter().filter(|s| s.status == StepStatus::Completed).count();
|
||||
self.progress_percentage = ((completed_steps as f32 / total_steps as f32) * 100.0) as u8;
|
||||
|
||||
// Find current step
|
||||
self.current_step = self.steps.iter()
|
||||
.find(|s| s.status == StepStatus::InProgress)
|
||||
.cloned();
|
||||
|
||||
// Update flow status based on steps
|
||||
if self.progress_percentage == 100 {
|
||||
self.status = FlowStatus::Completed;
|
||||
} else if self.steps.iter().any(|s| s.status == StepStatus::Stuck) {
|
||||
self.status = FlowStatus::Stuck;
|
||||
} else {
|
||||
self.status = FlowStatus::InProgress;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_step(&mut self) -> Result<(), String> {
|
||||
let current_index = self.steps.iter().position(|s| s.status == StepStatus::InProgress);
|
||||
|
||||
if let Some(index) = current_index {
|
||||
// Mark current step as completed
|
||||
self.steps[index].status = StepStatus::Completed;
|
||||
self.steps[index].completed_at = Some(Utc::now());
|
||||
|
||||
// If there's a next step, mark it as in progress
|
||||
if index + 1 < self.steps.len() {
|
||||
self.steps[index + 1].status = StepStatus::InProgress;
|
||||
self.steps[index + 1].started_at = Some(Utc::now());
|
||||
}
|
||||
|
||||
self.updated_at = Utc::now();
|
||||
self.update_progress();
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("No step in progress to advance".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_step_stuck(&mut self, reason: &str) -> Result<(), String> {
|
||||
let current_index = self.steps.iter().position(|s| s.status == StepStatus::InProgress);
|
||||
|
||||
if let Some(index) = current_index {
|
||||
// Mark current step as stuck
|
||||
self.steps[index].status = StepStatus::Stuck;
|
||||
|
||||
// Add a log entry for the stuck reason
|
||||
self.steps[index].add_log(reason.to_string());
|
||||
|
||||
self.updated_at = Utc::now();
|
||||
self.update_progress();
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("No step in progress to mark as stuck".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_log_to_step(&mut self, step_id: &str, message: &str) -> Result<(), String> {
|
||||
if let Some(step) = self.steps.iter_mut().find(|s| s.id == step_id) {
|
||||
step.add_log(message.to_string());
|
||||
self.updated_at = Utc::now();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Step with ID {} not found", step_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow statistics
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FlowStatistics {
|
||||
/// Total number of flows
|
||||
pub total_flows: usize,
|
||||
/// Number of in progress flows
|
||||
pub in_progress_flows: usize,
|
||||
/// Number of completed flows
|
||||
pub completed_flows: usize,
|
||||
/// Number of stuck flows
|
||||
pub stuck_flows: usize,
|
||||
/// Number of cancelled flows
|
||||
pub cancelled_flows: usize,
|
||||
}
|
||||
|
||||
impl FlowStatistics {
|
||||
/// Creates new flow statistics
|
||||
pub fn new(flows: &[Flow]) -> Self {
|
||||
let total_flows = flows.len();
|
||||
let in_progress_flows = flows.iter()
|
||||
.filter(|flow| flow.status == FlowStatus::InProgress)
|
||||
.count();
|
||||
let completed_flows = flows.iter()
|
||||
.filter(|flow| flow.status == FlowStatus::Completed)
|
||||
.count();
|
||||
let stuck_flows = flows.iter()
|
||||
.filter(|flow| flow.status == FlowStatus::Stuck)
|
||||
.count();
|
||||
let cancelled_flows = flows.iter()
|
||||
.filter(|flow| flow.status == FlowStatus::Cancelled)
|
||||
.count();
|
||||
|
||||
Self {
|
||||
total_flows,
|
||||
in_progress_flows,
|
||||
completed_flows,
|
||||
stuck_flows,
|
||||
cancelled_flows,
|
||||
}
|
||||
}
|
||||
}
|
248
actix_mvc_app/src/models/governance.rs
Normal file
248
actix_mvc_app/src/models/governance.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Represents the status of a governance proposal
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ProposalStatus {
|
||||
/// Proposal is in draft status, not yet open for voting
|
||||
Draft,
|
||||
/// Proposal is active and open for voting
|
||||
Active,
|
||||
/// Proposal has been approved by the community
|
||||
Approved,
|
||||
/// Proposal has been rejected by the community
|
||||
Rejected,
|
||||
/// Proposal has been cancelled by the creator
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ProposalStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ProposalStatus::Draft => write!(f, "Draft"),
|
||||
ProposalStatus::Active => write!(f, "Active"),
|
||||
ProposalStatus::Approved => write!(f, "Approved"),
|
||||
ProposalStatus::Rejected => write!(f, "Rejected"),
|
||||
ProposalStatus::Cancelled => write!(f, "Cancelled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a vote on a governance proposal
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum VoteType {
|
||||
/// Vote in favor of the proposal
|
||||
Yes,
|
||||
/// Vote against the proposal
|
||||
No,
|
||||
/// Abstain from voting on the proposal
|
||||
Abstain,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VoteType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VoteType::Yes => write!(f, "Yes"),
|
||||
VoteType::No => write!(f, "No"),
|
||||
VoteType::Abstain => write!(f, "Abstain"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a governance proposal in the system
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Proposal {
|
||||
/// Unique identifier for the proposal
|
||||
pub id: String,
|
||||
/// User ID of the proposal creator
|
||||
pub creator_id: i32,
|
||||
/// Name of the proposal creator
|
||||
pub creator_name: String,
|
||||
/// Title of the proposal
|
||||
pub title: String,
|
||||
/// Detailed description of the proposal
|
||||
pub description: String,
|
||||
/// Current status of the proposal
|
||||
pub status: ProposalStatus,
|
||||
/// Date and time when the proposal was created
|
||||
pub created_at: DateTime<Utc>,
|
||||
/// Date and time when the proposal was last updated
|
||||
pub updated_at: DateTime<Utc>,
|
||||
/// Date and time when voting starts
|
||||
pub voting_starts_at: Option<DateTime<Utc>>,
|
||||
/// Date and time when voting ends
|
||||
pub voting_ends_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl Proposal {
|
||||
/// Creates a new proposal
|
||||
pub fn new(creator_id: i32, creator_name: String, title: String, description: String) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
creator_id,
|
||||
creator_name,
|
||||
title,
|
||||
description,
|
||||
status: ProposalStatus::Draft,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
voting_starts_at: None,
|
||||
voting_ends_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the proposal status
|
||||
pub fn update_status(&mut self, status: ProposalStatus) {
|
||||
self.status = status;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Sets the voting period for the proposal
|
||||
pub fn set_voting_period(&mut self, starts_at: DateTime<Utc>, ends_at: DateTime<Utc>) {
|
||||
self.voting_starts_at = Some(starts_at);
|
||||
self.voting_ends_at = Some(ends_at);
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Activates the proposal for voting
|
||||
pub fn activate(&mut self) {
|
||||
self.status = ProposalStatus::Active;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Cancels the proposal
|
||||
pub fn cancel(&mut self) {
|
||||
self.status = ProposalStatus::Cancelled;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a vote cast on a proposal
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Vote {
|
||||
/// Unique identifier for the vote
|
||||
pub id: String,
|
||||
/// ID of the proposal being voted on
|
||||
pub proposal_id: String,
|
||||
/// User ID of the voter
|
||||
pub voter_id: i32,
|
||||
/// Name of the voter
|
||||
pub voter_name: String,
|
||||
/// Type of vote cast
|
||||
pub vote_type: VoteType,
|
||||
/// Optional comment explaining the vote
|
||||
pub comment: Option<String>,
|
||||
/// Date and time when the vote was cast
|
||||
pub created_at: DateTime<Utc>,
|
||||
/// Date and time when the vote was last updated
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Vote {
|
||||
/// Creates a new vote
|
||||
pub fn new(proposal_id: String, voter_id: i32, voter_name: String, vote_type: VoteType, comment: Option<String>) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
proposal_id,
|
||||
voter_id,
|
||||
voter_name,
|
||||
vote_type,
|
||||
comment,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the vote type
|
||||
pub fn update_vote(&mut self, vote_type: VoteType, comment: Option<String>) {
|
||||
self.vote_type = vote_type;
|
||||
self.comment = comment;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a filter for searching proposals
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProposalFilter {
|
||||
/// Filter by proposal status
|
||||
pub status: Option<String>,
|
||||
/// Filter by creator ID
|
||||
pub creator_id: Option<i32>,
|
||||
/// Search term for title and description
|
||||
pub search: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ProposalFilter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
status: None,
|
||||
creator_id: None,
|
||||
search: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the voting results for a proposal
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VotingResults {
|
||||
/// Proposal ID
|
||||
pub proposal_id: String,
|
||||
/// Number of yes votes
|
||||
pub yes_count: usize,
|
||||
/// Number of no votes
|
||||
pub no_count: usize,
|
||||
/// Number of abstain votes
|
||||
pub abstain_count: usize,
|
||||
/// Total number of votes
|
||||
pub total_votes: usize,
|
||||
}
|
||||
|
||||
impl VotingResults {
|
||||
/// Creates a new empty voting results object
|
||||
pub fn new(proposal_id: String) -> Self {
|
||||
Self {
|
||||
proposal_id,
|
||||
yes_count: 0,
|
||||
no_count: 0,
|
||||
abstain_count: 0,
|
||||
total_votes: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a vote to the results
|
||||
pub fn add_vote(&mut self, vote_type: &VoteType) {
|
||||
match vote_type {
|
||||
VoteType::Yes => self.yes_count += 1,
|
||||
VoteType::No => self.no_count += 1,
|
||||
VoteType::Abstain => self.abstain_count += 1,
|
||||
}
|
||||
self.total_votes += 1;
|
||||
}
|
||||
|
||||
/// Calculates the percentage of yes votes
|
||||
pub fn yes_percentage(&self) -> f64 {
|
||||
if self.total_votes == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
(self.yes_count as f64 / self.total_votes as f64) * 100.0
|
||||
}
|
||||
|
||||
/// Calculates the percentage of no votes
|
||||
pub fn no_percentage(&self) -> f64 {
|
||||
if self.total_votes == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
(self.no_count as f64 / self.total_votes as f64) * 100.0
|
||||
}
|
||||
|
||||
/// Calculates the percentage of abstain votes
|
||||
pub fn abstain_percentage(&self) -> f64 {
|
||||
if self.total_votes == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
(self.abstain_count as f64 / self.total_votes as f64) * 100.0
|
||||
}
|
||||
}
|
@@ -2,8 +2,12 @@
|
||||
pub mod user;
|
||||
pub mod ticket;
|
||||
pub mod calendar;
|
||||
pub mod governance;
|
||||
pub mod flow;
|
||||
|
||||
// Re-export models for easier imports
|
||||
pub use user::User;
|
||||
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority, TicketFilter};
|
||||
pub use calendar::{CalendarEvent, CalendarViewMode};
|
||||
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority};
|
||||
pub use calendar::{CalendarEvent, CalendarViewMode};
|
||||
pub use governance::{Proposal, ProposalStatus, Vote, VoteType, VotingResults, ProposalFilter};
|
||||
pub use flow::{Flow, FlowStep, FlowLog, FlowStatus, FlowType, StepStatus, FlowFilter, FlowStatistics};
|
Reference in New Issue
Block a user