objets_metier_rs 1.0.2

Bibliothèque Rust moderne et sûre pour l'API COM Objets Métier Sage 100c - Production Ready
use crate::com::{FromDispatchNew, SafeDispatch, SafeVariant};
use crate::errors::{SageError, SageResult};
use crate::wrappers::cpta::objects::CompteG;
use windows::Win32::System::Com::IDispatch;

/// Types de journal disponibles dans Sage 100c
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum JournalType {
    /// Journal des achats
    Achat = 0,
    /// Journal des ventes
    Vente = 1,
    /// Journal de trésorerie
    Tresorerie = 2,
    /// Journal des opérations diverses
    OperationsDiverses = 3,
    /// Journal des à-nouveaux
    ANouveaux = 4,
}

impl JournalType {
    /// Convertit une valeur i32 en JournalType
    pub fn from_i32(value: i32) -> Option<Self> {
        match value {
            0 => Some(JournalType::Achat),
            1 => Some(JournalType::Vente),
            2 => Some(JournalType::Tresorerie),
            3 => Some(JournalType::OperationsDiverses),
            4 => Some(JournalType::ANouveaux),
            _ => None,
        }
    }
}

/// Wrapper pour l'objet Journal de Sage 100c (IBOJournal3)
#[derive(Debug)]
pub struct Journal {
    pub dispatch: IDispatch,
}

impl Journal {
    /// Crée un SafeDispatch temporaire pour les appels
    fn dispatch(&self) -> SafeDispatch<'_> {
        SafeDispatch::new(&self.dispatch)
    }

    /// Récupère le numéro/code du journal
    pub fn jo_num(&self) -> SageResult<String> {
        self.dispatch()
            .call_method_by_name("JO_Num", &[])?
            .to_string()
    }

    pub fn jo_intitule(&self) -> SageResult<String> {
        self.dispatch()
            .call_method_by_name("JO_Intitule", &[])?
            .to_string()
    }

    pub fn jo_contrepartie(&self) -> SageResult<bool> {
        self.dispatch()
            .call_method_by_name("JO_Contrepartie", &[])?
            .to_bool()
    }

    pub fn jo_ifrs(&self) -> SageResult<bool> {
        self.dispatch()
            .call_method_by_name("JO_IFRS", &[])?
            .to_bool()
    }

    pub fn jo_reglement(&self) -> SageResult<bool> {
        self.dispatch()
            .call_method_by_name("JO_Reglement", &[])?
            .to_bool()
    }

    pub fn jo_saisi_anal(&self) -> SageResult<bool> {
        self.dispatch()
            .call_method_by_name("JO_SaisAnal", &[])?
            .to_bool()
    }

    pub fn jo_sommeil(&self) -> SageResult<bool> {
        self.dispatch()
            .call_method_by_name("JO_Sommeil", &[])?
            .to_bool()
    }

    pub fn jo_type(&self) -> SageResult<i32> {
        self.dispatch()
            .call_method_by_name("JO_Type", &[])?
            .to_i32()
    }

    /// Accède à l'objet CompteG
    /// Retourne directement un CompteG prêt à utiliser
    /// Retourne None si le journal n'a pas de compte général associé
    pub fn compte_general(&self) -> SageResult<Option<CompteG>> {
        let variant = self.dispatch().call_method_by_name("CompteG", &[])?;

        // Vérifier si c'est null/empty avant de tenter la conversion
        if variant.is_empty_or_null() {
            return Ok(None);
        }

        if !variant.is_object() {
            return Err(SageError::ConversionError {
                from_type: variant.type_name().to_string(),
                to_type: "CompteG".to_string(),
                value: format!(
                    "Propriété CompteG n'est pas un objet COM valide: {}",
                    variant.type_name()
                ),
            });
        }

        match variant.to_dispatch() {
            Ok(compte_general_dispatch) => Ok(Some(CompteG {
                dispatch: compte_general_dispatch,
            })),
            Err(_) => {
                // Si la conversion échoue, c'est probablement null
                Ok(None)
            }
        }
    }

    pub fn compte_general_raw(&self) -> SageResult<SafeVariant> {
        self.dispatch().call_method_by_name("CompteG", &[])
    }

    /// Retourne le prochain numéro de pièce pour le journal à une date donnée
    ///
    /// # Arguments
    /// * `date_periode` - Date pour laquelle obtenir le prochain numéro de pièce (format YYYY-MM-DD)
    ///
    /// # Retour
    /// Retourne le prochain numéro de pièce disponible selon les règles de numérotation du journal
    ///
    /// # Exemple
    /// ```no_run
    /// # use objets_metier_rs::{CptaApplication, SageResult};
    /// # fn exemple() -> SageResult<()> {
    /// # let app = CptaApplication::new("309DE0FB-9FB8-4F4E-8295-CC60C60DAA33")?;
    /// # app.set_name(r"D:\TMP\BIJOU.MAE")?;
    /// # let loggable = app.loggable()?;
    /// # loggable.set_user_name("<Administrateur>")?;
    /// # loggable.set_user_pwd("")?;
    /// # app.open()?;
    /// let factory_journal = app.factory_journal()?;
    /// let journal = factory_journal.read_numero("BQ")?;
    ///
    /// // Obtenir le prochain numéro de pièce pour le 31 décembre 2024
    /// let num_piece = journal.next_ec_piece("2024-12-31")?;
    /// println!("Prochain n° de pièce : {}", num_piece);
    /// # Ok(())
    /// # }
    /// ```
    pub fn next_ec_piece(&self, date_periode: &str) -> SageResult<String> {
        let date_variant = SafeVariant::from_string(date_periode);
        self.dispatch()
            .call_method_by_name("NextEC_Piece", &[date_variant])?
            .to_string()
    }

    // ==================== SETTERS POUR PROPRIÉTÉS ====================

    /// Définit le code/numéro du journal
    pub fn set_jo_num(&self, num: &str) -> SageResult<()> {
        let num_variant = SafeVariant::from_string(num);
        self.dispatch()
            .call_property_put("JO_Num", &[num_variant])?;
        Ok(())
    }

    /// Définit l'intitulé du journal
    pub fn set_jo_intitule(&self, intitule: &str) -> SageResult<()> {
        let intitule_variant = SafeVariant::from_string(intitule);
        self.dispatch()
            .call_property_put("JO_Intitule", &[intitule_variant])?;
        Ok(())
    }

    /// Définit si le journal utilise la contrepartie automatique
    pub fn set_jo_contrepartie(&self, contrepartie: bool) -> SageResult<()> {
        let contrepartie_variant = SafeVariant::from_bool(contrepartie);
        self.dispatch()
            .call_property_put("JO_Contrepartie", &[contrepartie_variant])?;
        Ok(())
    }

    /// Définit si le journal est IFRS
    pub fn set_jo_ifrs(&self, ifrs: bool) -> SageResult<()> {
        let ifrs_variant = SafeVariant::from_bool(ifrs);
        self.dispatch()
            .call_property_put("JO_IFRS", &[ifrs_variant])?;
        Ok(())
    }

    /// Définit si le journal gère les règlements
    pub fn set_jo_reglement(&self, reglement: bool) -> SageResult<()> {
        let reglement_variant = SafeVariant::from_bool(reglement);
        self.dispatch()
            .call_property_put("JO_Reglement", &[reglement_variant])?;
        Ok(())
    }

    /// Définit si le journal permet la saisie analytique
    pub fn set_jo_saisi_anal(&self, saisi_anal: bool) -> SageResult<()> {
        let saisi_anal_variant = SafeVariant::from_bool(saisi_anal);
        self.dispatch()
            .call_property_put("JO_SaisAnal", &[saisi_anal_variant])?;
        Ok(())
    }

    /// Définit si le journal est en sommeil
    pub fn set_jo_sommeil(&self, sommeil: bool) -> SageResult<()> {
        let sommeil_variant = SafeVariant::from_bool(sommeil);
        self.dispatch()
            .call_property_put("JO_Sommeil", &[sommeil_variant])?;
        Ok(())
    }

    /// Définit le type du journal
    /// 0 = Achat, 1 = Vente, 2 = Trésorerie, 3 = OD, 4 = A-nouveaux
    pub fn set_jo_type(&self, jo_type: i32) -> SageResult<()> {
        let type_variant = SafeVariant::from_i32(jo_type);
        self.dispatch()
            .call_property_put("JO_Type", &[type_variant])?;
        Ok(())
    }

    /// Définit le type du journal avec l'enum JournalType
    pub fn set_jo_type_enum(&self, jo_type: JournalType) -> SageResult<()> {
        self.set_jo_type(jo_type as i32)
    }

    /// Définit le compte général associé au journal
    pub fn set_compte_general(&self, compte: &CompteG) -> SageResult<()> {
        let compte_variant = SafeVariant::from_dispatch(compte.dispatch.clone());
        self.dispatch()
            .call_property_put("CompteG", &[compte_variant])?;
        Ok(())
    }
}

// Implémentation du trait FromDispatchNew pour permettre l'utilisation avec TypedComCollection
impl FromDispatchNew for Journal {
    fn from_dispatch_new(dispatch: IDispatch) -> SageResult<Self> {
        Ok(Self { dispatch })
    }
}