use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::models::intercompany::ConsolidationMethod;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FiscalYearVariant {
pub code: String,
pub description: String,
pub periods: u8,
pub special_periods: u8,
pub first_month: u8,
}
impl FiscalYearVariant {
pub fn calendar_year() -> Self {
Self {
code: "K4".to_string(),
description: "Calendar Year".to_string(),
periods: 12,
special_periods: 4,
first_month: 1,
}
}
pub fn us_federal() -> Self {
Self {
code: "V3".to_string(),
description: "US Federal Fiscal Year".to_string(),
periods: 12,
special_periods: 4,
first_month: 10,
}
}
pub fn april_year() -> Self {
Self {
code: "K1".to_string(),
description: "April Fiscal Year".to_string(),
periods: 12,
special_periods: 4,
first_month: 4,
}
}
}
impl Default for FiscalYearVariant {
fn default() -> Self {
Self::calendar_year()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompanyCode {
pub code: String,
pub name: String,
pub legal_name: String,
pub currency: String,
pub country: String,
pub city: Option<String>,
pub fiscal_year_variant: FiscalYearVariant,
pub coa_id: String,
pub parent_company: Option<String>,
pub is_group_parent: bool,
pub controlling_area: Option<String>,
pub credit_control_area: Option<String>,
pub time_zone: String,
pub vat_number: Option<String>,
pub tax_jurisdiction: Option<String>,
}
impl CompanyCode {
pub fn new(code: String, name: String, currency: String, country: String) -> Self {
Self {
code: code.clone(),
name: name.clone(),
legal_name: name,
currency,
country,
city: None,
fiscal_year_variant: FiscalYearVariant::default(),
coa_id: "OPER".to_string(),
parent_company: None,
is_group_parent: false,
controlling_area: Some(code.clone()),
credit_control_area: Some(code),
time_zone: "UTC".to_string(),
vat_number: None,
tax_jurisdiction: None,
}
}
pub fn us(code: &str, name: &str) -> Self {
Self::new(
code.to_string(),
name.to_string(),
"USD".to_string(),
"US".to_string(),
)
}
pub fn de(code: &str, name: &str) -> Self {
Self::new(
code.to_string(),
name.to_string(),
"EUR".to_string(),
"DE".to_string(),
)
}
pub fn gb(code: &str, name: &str) -> Self {
Self::new(
code.to_string(),
name.to_string(),
"GBP".to_string(),
"GB".to_string(),
)
}
pub fn ch(code: &str, name: &str) -> Self {
Self::new(
code.to_string(),
name.to_string(),
"CHF".to_string(),
"CH".to_string(),
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnterpriseGroup {
pub group_id: String,
pub name: String,
pub group_currency: String,
pub companies: Vec<CompanyCode>,
pub intercompany_links: Vec<(String, String)>,
}
impl EnterpriseGroup {
pub fn new(group_id: String, name: String, group_currency: String) -> Self {
Self {
group_id,
name,
group_currency,
companies: Vec::new(),
intercompany_links: Vec::new(),
}
}
pub fn add_company(&mut self, company: CompanyCode) {
self.companies.push(company);
}
pub fn add_intercompany_link(&mut self, from_code: String, to_code: String) {
self.intercompany_links.push((from_code, to_code));
}
pub fn get_company(&self, code: &str) -> Option<&CompanyCode> {
self.companies.iter().find(|c| c.code == code)
}
pub fn company_codes(&self) -> Vec<&str> {
self.companies.iter().map(|c| c.code.as_str()).collect()
}
pub fn get_intercompany_partners(&self, code: &str) -> Vec<&str> {
self.intercompany_links
.iter()
.filter_map(|(from, to)| {
if from == code {
Some(to.as_str())
} else if to == code {
Some(from.as_str())
} else {
None
}
})
.collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Company {
pub company_code: String,
pub company_name: String,
pub country: String,
pub local_currency: String,
pub functional_currency: String,
pub is_parent: bool,
pub parent_company: Option<String>,
pub ownership_percentage: Option<Decimal>,
pub consolidation_method: ConsolidationMethod,
}
impl Company {
pub fn new(
company_code: impl Into<String>,
company_name: impl Into<String>,
country: impl Into<String>,
local_currency: impl Into<String>,
) -> Self {
let currency = local_currency.into();
Self {
company_code: company_code.into(),
company_name: company_name.into(),
country: country.into(),
local_currency: currency.clone(),
functional_currency: currency,
is_parent: false,
parent_company: None,
ownership_percentage: None,
consolidation_method: ConsolidationMethod::Full,
}
}
pub fn parent(
company_code: impl Into<String>,
company_name: impl Into<String>,
country: impl Into<String>,
currency: impl Into<String>,
) -> Self {
let mut company = Self::new(company_code, company_name, country, currency);
company.is_parent = true;
company
}
pub fn subsidiary(
company_code: impl Into<String>,
company_name: impl Into<String>,
country: impl Into<String>,
currency: impl Into<String>,
parent_code: impl Into<String>,
ownership_pct: Decimal,
) -> Self {
let mut company = Self::new(company_code, company_name, country, currency);
company.parent_company = Some(parent_code.into());
company.ownership_percentage = Some(ownership_pct);
company
}
}
impl From<&CompanyCode> for Company {
fn from(cc: &CompanyCode) -> Self {
Self {
company_code: cc.code.clone(),
company_name: cc.name.clone(),
country: cc.country.clone(),
local_currency: cc.currency.clone(),
functional_currency: cc.currency.clone(),
is_parent: cc.is_group_parent,
parent_company: cc.parent_company.clone(),
ownership_percentage: None,
consolidation_method: ConsolidationMethod::Full,
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_company_creation() {
let company = CompanyCode::us("1000", "US Operations");
assert_eq!(company.code, "1000");
assert_eq!(company.currency, "USD");
assert_eq!(company.country, "US");
}
#[test]
fn test_enterprise_group() {
let mut group = EnterpriseGroup::new(
"CORP".to_string(),
"Global Corp".to_string(),
"USD".to_string(),
);
group.add_company(CompanyCode::us("1000", "US HQ"));
group.add_company(CompanyCode::de("2000", "EU Operations"));
group.add_intercompany_link("1000".to_string(), "2000".to_string());
assert_eq!(group.companies.len(), 2);
assert_eq!(group.get_intercompany_partners("1000"), vec!["2000"]);
}
}