diff --git a/server/planning/assignments/assignments.py b/server/planning/assignments/assignments.py index ac32c498a..826791f14 100644 --- a/server/planning/assignments/assignments.py +++ b/server/planning/assignments/assignments.py @@ -67,6 +67,7 @@ from planning.common import format_address, get_assginment_name from apps.content import push_content_notification from .assignments_history import ASSIGNMENT_HISTORY_ACTIONS +from planning.utils import get_event_formatted_dates, get_formatted_contacts logger = logging.getLogger(__name__) planning_type = deepcopy(superdesk.Resource.rel("planning", type="string")) @@ -453,6 +454,10 @@ def send_assignment_notification(self, updates, original=None, force=False): # Add the ICS object to the assignment assignment["planning"]["ics_data"] = ical.to_ical() + # get formatted contacts and event date time for email templates + formatted_contacts = get_formatted_contacts(event_item) if event_item else [] + fomatted_event_date = get_event_formatted_dates(event_item) if event_item else "" + # The assignment is to an external contact or a user if assigned_to.get("contact") or assigned_to.get("user"): # If it is a reassignment @@ -473,6 +478,9 @@ def send_assignment_notification(self, updates, original=None, force=False): event=event_item, is_link=True, contact_id=assigned_to.get("contact"), + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) # notify the desk if assigned_to.get("desk"): @@ -489,6 +497,9 @@ def send_assignment_notification(self, updates, original=None, force=False): event=event_item, omit_user=True, is_link=True, + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) else: @@ -525,6 +536,9 @@ def send_assignment_notification(self, updates, original=None, force=False): omit_user=True, is_link=True, contact_id=assigned_to.get("contact"), + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) else: # it is being reassigned by someone else so notify both the new assignee and the old @@ -547,6 +561,9 @@ def send_assignment_notification(self, updates, original=None, force=False): omit_user=True, is_link=True, contact_id=original.get("assigned_to").get("contact"), + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) # notify the assignee assigned_from = original.get("assigned_to") @@ -570,6 +587,9 @@ def send_assignment_notification(self, updates, original=None, force=False): omit_user=True, is_link=True, contact_id=assigned_to.get("contact"), + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) else: # A new assignment # Notify the user the assignment has been made to unless assigning to your self @@ -592,6 +612,9 @@ def send_assignment_notification(self, updates, original=None, force=False): omit_user=True, is_link=True, contact_id=assigned_to.get("contact"), + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) else: # Assigned/Reassigned to a desk, notify all desk members # if it was assigned to a desk before, test if there has been a change of desk @@ -620,6 +643,9 @@ def send_assignment_notification(self, updates, original=None, force=False): omit_user=True, is_link=True, contact_id=assigned_to.get("contact"), + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) else: PlanningNotifications().notify_assignment( @@ -637,6 +663,9 @@ def send_assignment_notification(self, updates, original=None, force=False): event=event_item, is_link=True, contact_id=assigned_to.get("contact"), + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) else: assign_type = "reassigned" if original.get("assigned_to") else "assigned" @@ -656,6 +685,9 @@ def send_assignment_notification(self, updates, original=None, force=False): omit_user=True, is_link=True, contact_id=assigned_to.get("contact"), + contacts=formatted_contacts, + location=event.get("LOCATION", ""), + event_date_time=fomatted_event_date, ) def send_assignment_cancellation_notification( diff --git a/server/planning/tests/utils_tests.py b/server/planning/tests/utils_tests.py new file mode 100644 index 000000000..45f240736 --- /dev/null +++ b/server/planning/tests/utils_tests.py @@ -0,0 +1,33 @@ +from planning.tests import TestCase +from datetime import datetime +from planning.utils import get_event_formatted_dates + + +class TestGetEventFormattedDates(TestCase): + def test_multi_day_event(self): + start = datetime(2024, 5, 28, 5, 00, 00) + end = datetime(2024, 5, 29, 6, 00, 00) + event = {"dates": {"start": start, "end": end}} + result = get_event_formatted_dates(event) + self.assertEqual(result, "05:00 28/05/2024 - 06:00 29/05/2024") + + def test_all_day_event(self): + start = datetime(2024, 4, 27, 22, 00, 00) + end = datetime(2024, 4, 28, 21, 59, 59) + event = {"dates": {"start": start, "end": end}} + result = get_event_formatted_dates(event) + self.assertEqual(result, "ALL DAY 27/04/2024") + + def test_same_start_end(self): + start = datetime(2024, 4, 1, 14, 45) + end = datetime(2024, 4, 1, 14, 45) + event = {"dates": {"start": start, "end": end}} + result = get_event_formatted_dates(event) + self.assertEqual(result, "14:45 01/04/2024") + + def test_dates_same_and_different_time(self): + start = datetime(2024, 5, 28, 6, 00, 00) + end = datetime(2024, 5, 28, 5, 00, 00) + event = {"dates": {"start": start, "end": end}} + result = get_event_formatted_dates(event) + self.assertEqual(result, "06:00 - 05:00, 28/05/2024") diff --git a/server/planning/utils.py b/server/planning/utils.py index d5f4e92ce..d958f7eb7 100644 --- a/server/planning/utils.py +++ b/server/planning/utils.py @@ -1,6 +1,24 @@ -from typing import Union +from typing import Union, List, Dict, Any, TypedDict from bson.objectid import ObjectId from bson.errors import InvalidId +from datetime import datetime +from flask_babel import format_time, format_datetime, lazy_gettext +from eve.utils import str_to_date +import arrow +from flask import current_app as app + + +class FormattedContact(TypedDict): + name: str + organisation: str + email: List[str] + phone: List[str] + mobile: List[str] + website: str + + +MULTI_DAY_SECONDS = 24 * 60 * 60 # Number of seconds for an multi-day event +ALL_DAY_SECONDS = MULTI_DAY_SECONDS - 1 # Number of seconds for an all-day event def try_cast_object_id(value: str) -> Union[ObjectId, str]: @@ -8,3 +26,72 @@ def try_cast_object_id(value: str) -> Union[ObjectId, str]: return ObjectId(value) except InvalidId: return value + + +def get_formatted_contacts(event: Dict[str, Any]) -> List[FormattedContact]: + contacts = event.get("event_contact_info", []) + formatted_contacts: List[FormattedContact] = [] + + for contact in contacts: + if contact.get("public", False): + formatted_contact: FormattedContact = { + "name": " ".join( + [ + c + for c in [ + contact.get("first_name", ""), + contact.get("last_name", ""), + ] + if c + ] + ), + "organisation": contact.get("organisation", ""), + "email": contact.get("contact_email", []), + "phone": [c.get("number", "") for c in contact.get("contact_phone", []) if c.get("public")], + "mobile": [c.get("number", "") for c in contact.get("mobile", []) if c.get("public")], + "website": contact.get("website", ""), + } + formatted_contacts.append(formatted_contact) + + return formatted_contacts + + +def parse_date(datetime: Union[str, datetime]) -> datetime: + """Return datetime instance for datetime.""" + if isinstance(datetime, str): + try: + return str_to_date(datetime) + except ValueError: + return arrow.get(datetime).datetime + return datetime + + +def time_short(datetime: datetime): + if datetime: + return format_time(parse_date(datetime), app.config.get("TIME_FORMAT_SHORT", "HH:mm")) + + +def date_short(datetime: datetime): + if datetime: + return format_datetime(parse_date(datetime), app.config.get("DATE_FORMAT_SHORT", "dd/MM/yyyy")) + + +def get_event_formatted_dates(event: Dict[str, Any]) -> str: + start = event.get("dates", {}).get("start") + end = event.get("dates", {}).get("end") + + duration_seconds = int((end - start).total_seconds()) + + if duration_seconds == ALL_DAY_SECONDS: + # All day event + return "{} {}".format(lazy_gettext("ALL DAY"), date_short(start)) + + if duration_seconds >= MULTI_DAY_SECONDS: + # Multi day event + return "{} {} - {} {}".format(time_short(start), date_short(start), time_short(end), date_short(end)) + + if start == end: + # start and end are the same + return "{} {}".format(time_short(start), date_short(start)) + + return "{} - {}, {}".format(time_short(start), time_short(end), date_short(start))