use chrono::NaiveDate;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::graph_properties::{GraphPropertyValue, ToNodeProperties};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MovementType {
#[default]
GoodsReceipt,
GoodsIssue,
Transfer,
Return,
Scrap,
Adjustment,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InventoryMovement {
pub id: String,
pub entity_code: String,
pub material_code: String,
pub material_description: String,
pub movement_date: NaiveDate,
pub period: String,
pub movement_type: MovementType,
#[serde(with = "crate::serde_decimal")]
pub quantity: Decimal,
pub unit: String,
#[serde(with = "crate::serde_decimal")]
pub value: Decimal,
pub currency: String,
pub storage_location: String,
pub reference_doc: String,
}
impl InventoryMovement {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: impl Into<String>,
entity_code: impl Into<String>,
material_code: impl Into<String>,
material_description: impl Into<String>,
movement_date: NaiveDate,
period: impl Into<String>,
movement_type: MovementType,
quantity: Decimal,
unit: impl Into<String>,
value: Decimal,
currency: impl Into<String>,
storage_location: impl Into<String>,
reference_doc: impl Into<String>,
) -> Self {
Self {
id: id.into(),
entity_code: entity_code.into(),
material_code: material_code.into(),
material_description: material_description.into(),
movement_date,
period: period.into(),
movement_type,
quantity,
unit: unit.into(),
value,
currency: currency.into(),
storage_location: storage_location.into(),
reference_doc: reference_doc.into(),
}
}
}
impl ToNodeProperties for InventoryMovement {
fn node_type_name(&self) -> &'static str {
"inventory_movement"
}
fn node_type_code(&self) -> u16 {
105
}
fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
let mut p = HashMap::new();
p.insert(
"entityCode".into(),
GraphPropertyValue::String(self.entity_code.clone()),
);
p.insert(
"materialCode".into(),
GraphPropertyValue::String(self.material_code.clone()),
);
p.insert(
"materialDescription".into(),
GraphPropertyValue::String(self.material_description.clone()),
);
p.insert(
"movementDate".into(),
GraphPropertyValue::Date(self.movement_date),
);
p.insert(
"period".into(),
GraphPropertyValue::String(self.period.clone()),
);
p.insert(
"movementType".into(),
GraphPropertyValue::String(format!("{:?}", self.movement_type)),
);
p.insert(
"quantity".into(),
GraphPropertyValue::Decimal(self.quantity),
);
p.insert("unit".into(), GraphPropertyValue::String(self.unit.clone()));
p.insert("value".into(), GraphPropertyValue::Decimal(self.value));
p.insert(
"currency".into(),
GraphPropertyValue::String(self.currency.clone()),
);
p.insert(
"storageLocation".into(),
GraphPropertyValue::String(self.storage_location.clone()),
);
p.insert(
"referenceDoc".into(),
GraphPropertyValue::String(self.reference_doc.clone()),
);
p
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_inventory_movement_properties() {
let mv = InventoryMovement::new(
"MV-001",
"C001",
"MAT-100",
"Widget A",
NaiveDate::from_ymd_opt(2024, 6, 15).unwrap(),
"2024-06",
MovementType::GoodsReceipt,
Decimal::new(100, 0),
"EA",
Decimal::new(5000, 0),
"USD",
"WH01",
"PO-12345",
);
let props = mv.to_node_properties();
assert_eq!(mv.node_type_name(), "inventory_movement");
assert_eq!(mv.node_type_code(), 105);
assert!(props.contains_key("movementType"));
assert!(props.contains_key("storageLocation"));
assert_eq!(
props["quantity"],
GraphPropertyValue::Decimal(Decimal::new(100, 0))
);
}
}