use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfessionalServicesRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_date: Option<String>,
pub employees: Vec<Employee>,
pub tasks: Vec<Task>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub contracts: Vec<Contract>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Employee {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
pub shifts: Vec<Shift>,
pub skills: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hourly_rate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contract_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_zone_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dedicated_client_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub availability_time_spans: Vec<AvailabilityTimeSpan>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Shift {
pub id: String,
pub min_start_time: String,
pub max_end_time: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Task {
pub id: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deadline: Option<String>,
pub duration: String,
pub priority: String,
pub required_skills: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub preferred_skills: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_zone_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub depends_on: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub preferred_employees: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub prohibited_employees: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Contract {
#[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 max_hours_per_day: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_hours_per_week: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_rest_between_shifts: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_utilization: Option<f64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AvailabilityTimeSpan {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfessionalServicesStartResponse {
pub job_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub solver_job_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfessionalServicesResultResponse {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub job_id: Option<String>,
pub employees: Vec<ScheduledEmployee>,
pub tasks: Vec<ScheduledTask>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<String>,
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub weights: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScheduledEmployee {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
pub shifts: Vec<Shift>,
pub skills: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hourly_rate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contract_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_zone_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dedicated_client_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub availability_time_spans: Vec<AvailabilityTimeSpan>,
pub tasks: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScheduledTask {
pub id: String,
pub name: String,
pub start_time: String,
pub end_time: String,
#[serde(default)]
pub duration: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub deadline: Option<String>,
pub priority: String,
pub required_skills: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serializes_expected_keys() {
let json = serde_json::to_string(&ProfessionalServicesRequest::default()).unwrap();
assert!(json.contains("\"employees\""));
assert!(json.contains("\"tasks\""));
assert!(!json.contains("\"startDate\""));
}
#[test]
fn availability_span_uses_reserved_type_key() {
let span = AvailabilityTimeSpan {
kind: Some("VACATION".into()),
..Default::default()
};
let json = serde_json::to_string(&span).unwrap();
assert!(json.contains("\"type\":\"VACATION\""));
assert!(!json.contains("\"kind\""));
let parsed: AvailabilityTimeSpan = serde_json::from_str(r#"{"type":"SICK"}"#).unwrap();
assert_eq!(parsed.kind.as_deref(), Some("SICK"));
}
#[test]
fn deserializes_result_response() {
let json = r#"{
"employees": [
{"id": "e1", "shifts": [], "skills": ["dev"], "tasks": ["t1"]}
],
"tasks": [
{"id": "t1", "name": "Build", "startTime": "2024-01-15T09:00:00",
"endTime": "2024-01-15T11:00:00", "duration": 2.0, "priority": "HIGH",
"requiredSkills": ["dev"]}
],
"score": "0hard/0medium/-5soft"
}"#;
let result: ProfessionalServicesResultResponse = serde_json::from_str(json).unwrap();
assert_eq!(result.employees.len(), 1);
assert_eq!(result.employees[0].tasks, vec!["t1".to_string()]);
assert_eq!(result.tasks[0].start_time, "2024-01-15T09:00:00");
assert_eq!(result.tasks[0].duration, 2.0);
assert_eq!(result.score.as_deref(), Some("0hard/0medium/-5soft"));
}
}