use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::convert::From;
use uuid::Uuid;
use super::MOD_PATH;
use crate::common::event::{Event, EventPayload};
use crate::common::related_party::RelatedParty;
use crate::tmf673::geographic_address::GeographicAddress;
use crate::{gen_code, HasDescription, HasId, HasName, HasValidity, TMFEvent, TimePeriod};
use tmflib_derive::{HasDescription, HasId, HasName, HasValidity};
const CLASS_PATH: &str = "geographicSite";
const DEFAULT_TZ: &str = "AEST";
const CODE_PREFIX: &str = "S-";
const CALENDAR_WEEKDAYS: &str = "weekdays";
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaceRefOrValue {
id: String,
href: String,
name: String,
}
impl From<GeographicAddress> for PlaceRefOrValue {
fn from(value: GeographicAddress) -> Self {
PlaceRefOrValue {
id: value.get_id(),
href: value.get_href(),
name: value.get_name(),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasValidity)]
#[serde(rename_all = "camelCase")]
pub struct GeographicSiteRelationship {
id: String,
href: String,
relationship_type: String,
role: String,
valid_for: Option<TimePeriod>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HourPeriod {
start_hour: String,
end_hour: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CalendarPeriod {
day: Option<String>,
status: Option<String>,
time_zone: Option<String>,
hour_period: Option<Vec<HourPeriod>>,
}
impl CalendarPeriod {
pub fn business_hours() -> CalendarPeriod {
CalendarPeriod {
day: Some(CALENDAR_WEEKDAYS.to_string()),
status: Some("open".to_string()),
time_zone: Some(DEFAULT_TZ.to_string()),
hour_period: Some(vec![HourPeriod {
start_hour: "09:00 am".to_string(),
end_hour: "05:00 pm".to_string(),
}]),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, HasId, HasName, HasDescription, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GeographicSite {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
place: Option<Vec<PlaceRefOrValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub site_relationship: Option<Vec<GeographicSiteRelationship>>,
#[serde(skip_serializing_if = "Option::is_none")]
calendar: Option<Vec<CalendarPeriod>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_party: Option<Vec<RelatedParty>>,
}
impl GeographicSite {
pub fn new(name: impl Into<String>) -> GeographicSite {
let mut site = GeographicSite::create();
site.name = Some(name.into());
site.generate_code(None);
site
}
pub fn place(mut self, place: PlaceRefOrValue) -> GeographicSite {
match self.place.as_mut() {
Some(v) => v.push(place),
None => self.place = Some(vec![place]),
}
self
}
pub fn code(mut self, code: String) -> GeographicSite {
self.code = Some(code);
self
}
pub fn calendar(mut self, calendar: CalendarPeriod) -> GeographicSite {
match self.calendar.as_mut() {
Some(v) => v.push(calendar),
None => self.calendar = Some(vec![calendar]),
}
self
}
pub fn generate_code(&mut self, offset: Option<u32>) {
let (code, _hash) = gen_code(
self.get_name(),
self.get_id(),
offset,
Some(CODE_PREFIX.to_string()),
None,
);
self.code = Some(code);
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum GeographicSiteEventType {
GeographicSiteCreateEvent,
GeographicSiteAttributeValueChangeEvent,
GeographicSiteStatusChangeEvent,
GeographicSiteDeleteEvent,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GeographicSiteEvent {
pub geographic_site: GeographicSite,
}
impl TMFEvent<GeographicSiteEvent> for GeographicSite {
fn event(&self) -> GeographicSiteEvent {
GeographicSiteEvent {
geographic_site: self.clone(),
}
}
}
impl EventPayload<GeographicSiteEvent> for GeographicSite {
type Subject = GeographicSite;
type EventType = GeographicSiteEventType;
fn to_event(&self, event_type: Self::EventType) -> Event<GeographicSiteEvent, Self::EventType> {
let now = Utc::now();
let event_time = chrono::DateTime::from_timestamp(now.timestamp(), 0).unwrap();
let desc = format!(
"{:?} for {} [{}]",
event_type,
self.get_name(),
self.get_id()
);
Event {
correlation_id: self.code.clone(),
description: Some(desc),
domain: Some(GeographicSite::get_class()),
event_id: Uuid::new_v4().to_string(),
field_path: None,
href: Some(self.get_href()),
id: Some(self.get_id()),
title: Some(self.get_name()),
event_time: event_time.to_string(),
priority: None,
time_occurred: Some(event_time.to_string()),
event_type,
event: self.event(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::tmf673::geographic_address::GeographicAddress;
const SITE: &str = "ASites";
const ADDRESS: &str = "AnAddress";
const SITE_CODE: &str = "SiteCode";
const PLACEREF_JSON: &str = "{
\"id\" : \"P123\",
\"href\" : \"http://example.com/tmf674/place/P123\",
\"name\" : \"PlaceName\"
}";
const GEOSITEREL_JSON: &str = "{
\"id\" : \"G123\",
\"href\" : \"http://example.com/tmf674/site/G123\",
\"relationshipType\" : \"RelationshipType\",
\"role\" : \"Role\"
}";
const HOURPERIOD_JSON: &str = "{
\"startHour\" : \"09:00\",
\"endHour\" : \"17:00\"
}";
const CALENDAR_JSON: &str = "{
\"day\" : \"Monday\",
\"status\" :\"Status\"
}";
#[test]
fn test_site_new_name() {
let site = GeographicSite::new(SITE);
assert_eq!(site.name, Some(SITE.into()));
}
#[test]
fn test_site_new_place() {
let place = GeographicAddress::new(ADDRESS);
let place2 = place.clone();
let site = GeographicSite::new(SITE).place(place.into());
assert_eq!(site.place.unwrap()[0], PlaceRefOrValue::from(place2));
}
#[test]
fn test_path() {
let path = GeographicSite::get_class_href();
assert_eq!(path.contains("geographicSiteManagement"), true);
}
#[test]
fn test_site_business_hours() {
let bus_hours = CalendarPeriod::business_hours();
assert_eq!(bus_hours.day, Some(CALENDAR_WEEKDAYS.to_string()));
assert_eq!(bus_hours.hour_period.is_some(), true);
}
#[test]
fn test_site_code() {
let site = GeographicSite::new(SITE).code(SITE_CODE.to_string());
assert_eq!(site.code.is_some(), true);
assert_eq!(site.code.unwrap().as_str(), SITE_CODE);
}
#[test]
fn test_site_calendar() {
let site = GeographicSite::new(SITE)
.calendar(CalendarPeriod::business_hours())
.calendar(CalendarPeriod::business_hours());
assert_eq!(site.calendar.is_some(), true);
assert_eq!(site.calendar.unwrap().len(), 2);
}
#[test]
fn test_placeref_deserialize() {
let placeref: PlaceRefOrValue =
serde_json::from_str(PLACEREF_JSON).expect("Cannot parse PLACEREF_JSON");
assert_eq!(placeref.id.as_str(), "P123");
assert_eq!(placeref.name.as_str(), "PlaceName");
}
#[test]
fn test_geositerel_deserialize() {
let geositerel: GeographicSiteRelationship = serde_json::from_str(GEOSITEREL_JSON).unwrap();
assert_eq!(geositerel.id.as_str(), "G123");
assert_eq!(geositerel.relationship_type.as_str(), "RelationshipType");
assert_eq!(geositerel.role.as_str(), "Role");
}
#[test]
fn test_geosite_hasvalidity() {
let mut geositerel = GeographicSiteRelationship::default();
geositerel.set_validity(TimePeriod::period_30days());
assert_eq!(geositerel.valid_for.is_some(), true);
}
#[test]
fn test_hourperiod_deserialize() {
let hourperiod: HourPeriod = serde_json::from_str(HOURPERIOD_JSON).unwrap();
assert_eq!(hourperiod.start_hour.as_str(), "09:00");
assert_eq!(hourperiod.end_hour.as_str(), "17:00");
}
#[test]
fn test_calendar_deseralize() {
let calendar: CalendarPeriod = serde_json::from_str(CALENDAR_JSON).unwrap();
assert_eq!(calendar.day.is_some(), true);
assert_eq!(calendar.day.unwrap().as_str(), "Monday");
assert_eq!(calendar.status.is_some(), true);
assert_eq!(calendar.status.unwrap().as_str(), "Status");
}
}