rec23-rs 0.2.82

A library for REC23 CRM.
Documentation
use std::error::Error;
use bb8_postgres::bb8::Pool;
use bb8_postgres::PostgresConnectionManager;
use bb8_postgres::tokio_postgres::{GenericClient, NoTls, Row};
use chrono::NaiveDateTime;
use paperclip::actix::Apiv2Schema;
use serde::{Deserialize, Serialize};
use crate::user::User;

const INSERT_TASK: &str = "INSERT INTO rec23.task (header, dt_cre, dt_beg, mark_color_id, main_color_id, content, note, task_status_id, assignee_id, employee_id, dept_id, task_type) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id";
const UPDATE_TASK: &str = "UPDATE rec23.task SET header = $2, dt_cre = $3, dt_beg = $4, mark_color_id = $5, main_color_id = $6, content = $7, note = $8, task_status_id = $9, assignee_id = $10, employee_id = $11, dept_id = $12, task_type = $13 WHERE id = $1";
const COMPLETE_TASK_BY_ID: &str = "UPDATE rec23.task SET main_color_id = 6, task_status_id = 2 WHERE id = $1";
const COMPLETE_TASK_BY_ID_SUB: &str = "INSERT INTO rec23.task_sub_completed (task_id) VALUES ($1)";
const GET_TASK_BY_ID: &str = "SELECT task.id, task.header, task.dt_cre, task.dt_beg, task.mark_color_id, task.main_color_id, task.content, task.note, task.task_status_id, task.assignee_id, task.employee_id, task.dept_id, task.task_type FROM rec23.task task where task.id = $1 AND dt_cre IS NOT NULL";
const SET_TASK_EMPLOYEE: &str = "UPDATE rec23.task SET employee_id = $2, dept_id = $3 WHERE id = $1";
const GET_TASK_SUBSCRIBERS: &str = "SELECT employee.id, employee.name, employee.dept_id, employee.post_id, employee.telegram_name FROM rec23.notify_subscription notify_subscription JOIN rec23.employee employee ON employee.id = notify_subscription.user_id WHERE notify_subscription.task_id = $1";
const GET: &str = "SELECT id, header, dt_cre, dt_beg, mark_color_id, main_color_id, content, note, task_status_id, assignee_id, employee_id, dept_id, task_type FROM rec23.task WHERE dt_cre IS NOT NULL";
#[derive(Serialize, Deserialize, Apiv2Schema)]
pub struct Task {
    pub id: Option<i32>,
    pub header: Option<String>,
    pub dt_cre: NaiveDateTime,
    pub dt_beg: Option<NaiveDateTime>,
    pub mark_color_id: i32,
    pub main_color_id: i32,
    pub content: Option<String>,
    pub note: Option<String>,
    pub task_status_id: i32,
    pub assignee_id: i32,
    pub employee_id: Option<i32>,
    pub dept_id: i32,
    pub task_type: i32,
}

#[derive(Debug, Clone)]
pub struct ClientTask {
    pub header: Option<String>,
    pub dt_cre: NaiveDateTime,
    pub dt_beg: Option<NaiveDateTime>,
    pub mark_color_id: i32,
    pub main_color_id: i32,
    pub content: Option<String>,
    pub note: Option<String>,
    pub task_status_id: i32,
    pub assignee_id: i32,
    pub employee_id: Option<i32>,
    pub dept_id: i32,
    pub task_type: i32,
}

impl Into<Task> for ClientTask {
    fn into(self) -> Task {
        Task {
            id: None,
            header: self.header,
            dt_cre: self.dt_cre,
            dt_beg: self.dt_beg,
            mark_color_id: self.mark_color_id,
            main_color_id: self.main_color_id,
            content: self.content,
            note: self.note,
            task_status_id: self.task_status_id,
            assignee_id: self.assignee_id,
            employee_id: self.employee_id,
            dept_id: self.dept_id,
            task_type: self.task_type,
        }
    }
}

impl ClientTask {
    /// Creates a new instance of `ClientTaskData` using provided parameters.
    ///
    /// # Arguments
    ///
    /// * `header` - Option<String> referring to header of the task.
    /// * `dt_cre` - NaiveDateTime referring to creation date of the task.
    /// * `dt_beg` - Option<NaiveDateTime> referring to beginning date of the task.
    /// * `mark_color_id` - i32 referring to mark color id of the task.
    /// * `main_color_id` - i32 referring to main color id of the task.
    /// * `content` - Option<String> referring to content of the task.
    /// * `note` - Option<String> referring to note related to the task.
    /// * `task_status_id` - i32 referring to status id of the task.
    /// * `assignee_id` - i32 referring to assignee id of the task.
    /// * `employee_id` - Option<i32> referring to employee id of the task.
    /// * `dept_id` - i32 referring to department id of the task.
    /// * `task_type` - i32 referring to type of the task.
    ///
    /// # Returns
    ///
    /// This function returns a `ClientTaskData` instance with fields populated from the input parameters.
    pub fn new(header: Option<String>, dt_cre: NaiveDateTime, dt_beg: Option<NaiveDateTime>, mark_color_id: i32, main_color_id: i32, content: Option<String>, note: Option<String>, task_status_id: i32, assignee_id: i32, employee_id: Option<i32>, dept_id: i32, task_type: i32) -> Self {
        Self {
            header,
            dt_cre,
            dt_beg,
            mark_color_id,
            main_color_id,
            content,
            note,
            task_status_id,
            assignee_id,
            employee_id,
            dept_id,
            task_type,
        }
    }

    /// This function saves the instance of `ClientTaskData` into the `rec23.task` table in Postgres database, thus converting the instance into `TaskData`.
    ///
    /// As an async function, it needs to be awaited. It commits all the instance fields into according Postgres fields. If an insert operation is successful,
    /// the function converts the `ClientTaskData` instance to `TaskData` instance by setting the task ID which is returned from the insert operation.
    /// On failure, the function returns the Err variant of the `Result` containing the error.
    ///
    /// # Arguments
    ///
    /// * `self` - Instance of ClientTaskData that is to be saved to the database.
    /// * `pool` - Instance of `Pool<PostgresConnectionManager<NoTls>>` which is a connection to the database.
    ///
    /// # Returns
    ///
    /// This function returns `Result<TaskData, Box<dyn Error>>`. On success, Ok variant containing `TaskData` instance is returned; on failure, Err variant containing the error.
    ///
    /// # Example
    ///
    /// ```Rust
    /// let pool = Pool::new(manager, 10).await?;
    /// let client_task = ClientTaskData::new(Some("Task header".to_string()), Utc::now(), None, 1, 2, Some("Task content".to_string()), Some("Task note".to_string()), 1, 2, None, 1, 1);
    /// let result = client_task.save(&pool).await?;
    /// ```
    pub async fn save(self, pool: &Pool<PostgresConnectionManager<NoTls>>) -> Result<Task, Box<dyn Error + Sync + Send>> {
        let connection = pool.get().await?;
        let client = connection.client();
        let row = client.query_one(INSERT_TASK, &[&self.header, &self.dt_cre, &self.dt_beg, &self.mark_color_id, &self.main_color_id, &self.content, &self.note, &self.task_status_id, &self.assignee_id, &self.employee_id, &self.dept_id, &self.task_type]).await?;
        let mut result: Task = self.into();
        result.id = Some(row.get(0));
        Ok(result)
    }
}

impl Task {
    pub async fn get(pool: &Pool<PostgresConnectionManager<NoTls>>) -> Result<Vec<Self>, Box<dyn Error + Sync + Send>> {
        let connection = pool.get().await?;
        let rows = connection.query(GET, &[]).await?;
        Ok(rows.iter().map(|row|Self::convert_from_row(row)).collect())
    }
    /// Converts a `Row` instance into a `TaskData` instance.
    ///
    /// This function takes a reference to a `Row` instance and returns a new
    /// `TaskData` instance where all fields are populated from the corresponding columns
    /// in the `Row`. The conversion from the PostgreSQL types to the Rust types is done
    /// using the `get` method of the `Row` struct.
    ///
    /// # Arguments
    ///
    /// * `row` - A reference to a `Row` instance that represents a row from a `rec23.task`
    ///           table in the PostgreSQL database.
    ///
    /// # Returns
    ///
    /// This function returns a `TaskData` instance derived from the `Row` instance.
    pub fn convert_from_row(row: &Row) -> Self {
        Self {
            id: row.get("id"),
            header: row.get("header"),
            dt_cre: row.get("dt_cre"),
            dt_beg: row.get("dt_beg"),
            mark_color_id: row.get("mark_color_id"),
            main_color_id: row.get("main_color_id"),
            content: row.get("content"),
            note: row.get("note"),
            task_status_id: row.get("task_status_id"),
            assignee_id: row.get("assignee_id"),
            employee_id: row.get("employee_id"),
            dept_id: row.get("dept_id"),
            task_type: row.get("task_type"),
        }
    }

    /// Updates an existing task in the `rec23.task` table of a PostgreSQL database.
    ///
    /// This asynchronous function takes ownership of `self`, a `TaskData` instance, and a reference
    /// to a `Pool<PostgresConnectionManager<NoTls>>` instance. It extracts data from `self` and
    /// the connection pool and sends an SQL `UPDATE` query to the database.
    ///
    /// The `UPDATE` query modifies all the columns of the row in the `rec23.task` table that has
    /// `id` field equal to `self.id`.
    ///
    /// # Arguments
    ///
    /// * `self` - An owned `TaskData` instance containing all the data needed to update a row.
    /// * `pool` - A shared reference to a `Pool<PostgresConnectionManager<NoTls>>` instance that represents a database connection.
    ///
    /// # Returns
    ///
    /// The function returns a `Result` with the `Err` variant containing a `Box<dyn Error>` in case of any issues.
    /// If the operation is successful, the function simply returns `Ok(())`.
    ///
    /// # Example
    ///
    /// ```Rust
    /// let pool = Pool::new(manager, 10).await?;
    /// let task_data = TaskData {
    ///     id: Some(1),
    ///     header: Some("Updated Task".to_string()),
    ///     // Other fields omitted for simplicity.
    /// };
    /// task_data.save(&pool).await?;
    /// ```
    ///
    /// # Errors
    ///
    /// The function may return an error if:
    ///
    /// * The connection to the database could not be established or retrieved from the pool.
    /// * The `UPDATE` query failed.
    /// * There are issues with the data from `self`.
    pub async fn save(&self, pool: &Pool<PostgresConnectionManager<NoTls>>) -> Result<(), Box<dyn Error + Sync + Send>> {
        let connection = pool.get().await?;
        let client = connection.client();
        client.execute(UPDATE_TASK, &[&self.id, &self.header, &self.dt_cre, &self.dt_beg, &self.mark_color_id, &self.main_color_id, &self.content, &self.note, &self.task_status_id, &self.assignee_id, &self.employee_id, &self.dept_id, &self.task_type]).await?;
        Ok(())
    }

    /// Completes a task by updating its completion status and main color ID in the database.
    ///
    /// # Arguments
    ///
    /// * `pool` - A reference to the database connection pool.
    ///
    /// # Returns
    ///
    /// * `Result<(), Box<dyn Error>>` - A `Result` indicating success or failure.
    pub async fn complete(&mut self, pool: &Pool<PostgresConnectionManager<NoTls>>) -> Result<(), Box<dyn Error + Sync + Send>> {
        let connection = pool.get().await?;
        let client = connection.client();
        client.execute(COMPLETE_TASK_BY_ID, &[&self.id]).await?;
        client.execute(COMPLETE_TASK_BY_ID_SUB, &[&self.id]).await?;
        self.main_color_id = 6;
        self.task_status_id = 2;
        Ok(())
    }

    /// Sets the employee and department ID for a task.
    ///
    /// # Arguments
    ///
    /// - `pool`: The connection pool to PostgreSQL database.
    /// - `employee_id`: The ID of the employee to be set for the task.
    ///
    /// # Returns
    ///
    /// - `Ok(())` if the operation is successful.
    /// - `Err(Box<dyn Error>)` if an error occurs during the operation.
    ///
    /// # Example
    ///
    /// ```Rust
    /// use tokio_postgres::{NoTls, Client};
    /// use bb_tracker::models::{Pool, PostgresConnectionManager, Task};
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let pool = Pool::new(PostgresConnectionManager::new("postgres://username:password@localhost/db", NoTls).unwrap());
    ///
    ///     let mut task = Task::new();
    ///     task.set_employee(&pool, 42).await.unwrap();
    /// }
    /// ```
    pub async fn set_employee(&mut self, pool: &Pool<PostgresConnectionManager<NoTls>>, employee_id: i32) -> Result<(), Box<dyn Error + Sync + Send>> {
        let connection = pool.get().await?;
        let client = connection.client();
        if let Some(employee) = User::get_user_by_id(employee_id, pool).await? {
            client.execute(SET_TASK_EMPLOYEE, &[&self.id, &employee.id, &employee.dept_id]).await?;
            self.employee_id = Some(employee.id);
            self.dept_id = employee.dept_id.unwrap();
        }
        Ok(())
    }

    /// Sets the employee and department for the task.
    ///
    /// # Arguments
    ///
    /// * `pool` - A reference to the database connection pool.
    /// * `employee_id` - The ID of the employee to associate with the task.
    /// * `dept_id` - The ID of the department to associate with the task.
    ///
    /// # Returns
    ///
    /// This function returns `Ok(())` if the operation is successful.
    /// Otherwise, it returns an error of type `Box<dyn Error>`.
    ///
    /// # Example
    ///
    /// ```Rust
    /// use postgresql::Pool;
    /// use tokio_postgres::NoTls;
    /// use std::error::Error;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn Error>> {
    ///     // Create a database connection pool
    ///     let pool = Pool::new().await?;
    ///
    ///     // Get a task from the database
    ///     let mut task = get_task();
    ///
    ///     // Set the employee and department for the task
    ///     let employee_id = 1;
    ///     let dept_id = 1;
    ///     task.set_employee_and_dept(&pool, &employee_id, &dept_id).await?;
    ///
    ///     Ok(())
    /// }
    /// ```
    pub async fn set_employee_and_dept(&mut self, pool: &Pool<PostgresConnectionManager<NoTls>>, employee_id: &i32, dept_id: &i32) -> Result<(), Box<dyn Error + Sync + Send>> {
        let connection = pool.get().await?;
        let client = connection.client();
        client.execute(SET_TASK_EMPLOYEE, &[&self.id, employee_id, dept_id]).await?;
        self.employee_id = Some(employee_id.clone());
        self.dept_id = dept_id.clone();
        Ok(())
    }

    /// Fetches a task by its ID from the `rec23.task` table of a PostgreSQL database.
    ///
    /// This asynchronous function takes an `id` input of type `i32` and a reference
    /// to a `Pool<PostgresConnectionManager<NoTls>>` instance. It uses the `GET_TASK_BY_ID`
    /// SQL query defined earlier.
    ///
    /// # Arguments
    ///
    /// * `id` - An i32 that represents the ID of the task.
    /// * `pool` - A shared reference to a `Pool<PostgresConnectionManager<NoTls>>` instance that represents a database connection.
    ///
    /// # Returns
    ///
    /// The function returns a `Result<TaskData, Box<dyn Error>>`. On success, `Ok(TaskData)` is returned;
    /// on failure, `Err` variant containing the error.
    ///
    /// # Example
    ///
    /// ```Rust
    /// let pool = Pool::new(manager, 10).await?;
    /// let result = TaskData::get_by_id(1, &pool).await?;
    /// ```
    pub async fn get_by_id(id: i32, pool: &Pool<PostgresConnectionManager<NoTls>>) -> Result<Self, Box<dyn Error + Sync + Send>> {
        let connection = pool.get().await?;
        let client = connection.client();
        let row = client.query_one(GET_TASK_BY_ID, &[&id]).await?;
        Ok(Self::convert_from_row(&row))
    }

    pub fn get_task_color_from_str(str: &str) -> i32 {
        let mut color_id = 11;
        if str.to_uppercase().contains("ПЛОТН") {
            color_id = 5;
        }
        if str.to_uppercase().contains("САНТЕ") {
            color_id = 7;
        } else if str.to_uppercase().contains("ТАКЕЛ") || str.to_uppercase().contains("ГРУЗ") || str.to_uppercase().contains("НОСИЛ") {
            color_id = 10;
        } else if str.to_uppercase().contains("ЭЛЕКТ") {
            color_id = 1;
        } else if str.to_uppercase().contains("IT") || str.to_uppercase().contains("АТИ") || str.to_uppercase().contains("АЙТИ") || str.to_uppercase().contains("КОМП") {
            color_id = 2;
        } else if str.to_uppercase().contains("СТРОИ") {
            color_id = 3;
        }

        color_id
    }

    pub async fn get_subscribers(&self, pool: &Pool<PostgresConnectionManager<NoTls>>) -> Result<Vec<User>, Box<dyn Error + Sync + Send>> {
        let connection = pool.get().await?;
        let client = connection.client();
        let rows = client.query(GET_TASK_SUBSCRIBERS, &[&self.id]).await?;
        let result = rows.into_iter().map(|row| User::convert_from_row(&row)).collect();
        Ok(result)
    }
}