use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OmsDocument {
pub oms_version: String,
pub metadata: Metadata,
pub vendor: Vendor,
pub items: Vec<Item>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order: Option<Order>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<Extensions>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Metadata {
pub created: DateTime<Utc>,
pub source: String,
pub locale: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Vendor {
pub id: String,
pub name: String,
pub r#type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub location_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<Address>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contact: Option<Contact>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hours: Option<Vec<BusinessHours>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cuisine: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub services: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Address {
pub street: String,
pub city: String,
pub region: String,
pub postal_code: String,
pub country: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Contact {
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub website: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BusinessHours {
pub day: DayOfWeek,
pub ranges: Vec<TimeRange>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum DayOfWeek {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TimeRange {
pub open: String,
pub close: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Item {
pub id: String,
pub name: String,
pub category: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub vendor_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subcategory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_price: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nutrition: Option<Nutrition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub customizations: Option<Vec<Customization>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub selected_customizations: Option<Vec<SelectedCustomization>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quantity: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub item_note: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub calculated: Option<CalculatedValues>,
#[serde(skip_serializing_if = "Option::is_none")]
pub components: Option<Vec<Item>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub availability: Option<Availability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub popularity: Option<Popularity>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Nutrition {
#[serde(skip_serializing_if = "Option::is_none")]
pub serving_size: Option<MeasurementValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub calories: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub protein: Option<MeasurementValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fat: Option<NutrientWithDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub carbohydrates: Option<NutrientWithDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sodium: Option<MeasurementValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cholesterol: Option<MeasurementValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vitamins: Option<Vec<VitaminMineral>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minerals: Option<Vec<VitaminMineral>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allergens: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dietary_flags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub health_claims: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ingredients: Option<Vec<IngredientGroup>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nutrition_standards: Option<NutritionStandards>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MeasurementValue {
pub value: f64,
pub unit: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NutrientWithDetails {
pub value: f64,
pub unit: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<HashMap<String, NutrientValue>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum NutrientValue {
Simple(MeasurementValue),
Detailed(NutrientWithDetails),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VitaminMineral {
pub name: String,
pub value: f64,
pub unit: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub daily_value_percent: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IngredientGroup {
pub name: String,
pub ingredients: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NutritionStandards {
#[serde(skip_serializing_if = "Option::is_none")]
pub us_fda: Option<UsFdaInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub eu_regulation: Option<EuRegulationInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UsFdaInfo {
pub serving_size_description: String,
pub daily_value_year: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EuRegulationInfo {
pub reference_intake_description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Customization {
pub id: String,
pub name: String,
pub r#type: CustomizationType,
#[serde(default)]
pub required: bool,
pub default: CustomizationDefault,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_selections: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_selections: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub step: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unit_price_adjustment: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unit_nutrition_adjustments: Option<HashMap<String, NutrientValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<CustomizationOption>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum CustomizationType {
SingleSelect,
MultiSelect,
Quantity,
Boolean,
Text,
Range,
}
impl FromStr for CustomizationType {
type Err = crate::OmsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"single_select" => Ok(CustomizationType::SingleSelect),
"multi_select" => Ok(CustomizationType::MultiSelect),
"quantity" => Ok(CustomizationType::Quantity),
"boolean" => Ok(CustomizationType::Boolean),
"text" => Ok(CustomizationType::Text),
"range" => Ok(CustomizationType::Range),
_ => Err(crate::OmsError::InvalidCustomizationType(s.to_string())),
}
}
}
impl fmt::Display for CustomizationType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CustomizationType::SingleSelect => write!(f, "single_select"),
CustomizationType::MultiSelect => write!(f, "multi_select"),
CustomizationType::Quantity => write!(f, "quantity"),
CustomizationType::Boolean => write!(f, "boolean"),
CustomizationType::Text => write!(f, "text"),
CustomizationType::Range => write!(f, "range"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum CustomizationDefault {
String(String),
StringArray(Vec<String>),
Number(f64),
Boolean(bool),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CustomizationOption {
pub id: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub price_adjustment: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nutrition_adjustments: Option<HashMap<String, NutrientValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allergens: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dietary_flags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SelectedCustomization {
pub customization_id: String,
pub selection: CustomizationSelection,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum CustomizationSelection {
String(String),
StringArray(Vec<String>),
Number(f64),
Boolean(bool),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CalculatedValues {
pub item_price: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub adjusted_nutrition: Option<HashMap<String, f64>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Availability {
#[serde(skip_serializing_if = "Option::is_none")]
pub start_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub times_of_day: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub days_of_week: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Popularity {
#[serde(skip_serializing_if = "Option::is_none")]
pub rank: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Order {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<OrderStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pickup_time: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delivery_time: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<OrderType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub customer_notes: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment: Option<Payment>,
#[serde(skip_serializing_if = "Option::is_none")]
pub customer: Option<Customer>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delivery: Option<Delivery>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum OrderStatus {
Draft,
Submitted,
Confirmed,
InProgress,
Ready,
Completed,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum OrderType {
Pickup,
Delivery,
DineIn,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Payment {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<PaymentStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subtotal: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tax: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tip: Option<f64>,
pub total: f64,
pub currency: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PaymentStatus {
Unpaid,
Paid,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Customer {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Delivery {
pub address: Address,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
pub type Extensions = HashMap<String, serde_json::Value>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_customization_type_display() {
assert_eq!(CustomizationType::SingleSelect.to_string(), "single_select");
assert_eq!(CustomizationType::MultiSelect.to_string(), "multi_select");
assert_eq!(CustomizationType::Quantity.to_string(), "quantity");
assert_eq!(CustomizationType::Boolean.to_string(), "boolean");
assert_eq!(CustomizationType::Text.to_string(), "text");
assert_eq!(CustomizationType::Range.to_string(), "range");
}
#[test]
fn test_customization_type_from_str() {
assert_eq!(
CustomizationType::from_str("single_select").unwrap(),
CustomizationType::SingleSelect
);
assert_eq!(
CustomizationType::from_str("multi_select").unwrap(),
CustomizationType::MultiSelect
);
assert!(CustomizationType::from_str("invalid").is_err());
}
}