canvas 0.61.0__py3-none-any.whl → 0.63.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {canvas-0.61.0.dist-info → canvas-0.63.0.dist-info}/METADATA +1 -1
  2. {canvas-0.61.0.dist-info → canvas-0.63.0.dist-info}/RECORD +29 -19
  3. canvas_generated/messages/effects_pb2.py +2 -2
  4. canvas_generated/messages/effects_pb2.pyi +30 -0
  5. canvas_generated/messages/events_pb2.py +2 -2
  6. canvas_generated/messages/events_pb2.pyi +28 -0
  7. canvas_sdk/effects/appointments_metadata/__init__.py +13 -0
  8. canvas_sdk/effects/appointments_metadata/appointments_metadata_create_form.py +12 -0
  9. canvas_sdk/effects/appointments_metadata/base.py +30 -0
  10. canvas_sdk/effects/calendar/__init__.py +4 -0
  11. canvas_sdk/effects/calendar/create_calendar.py +40 -0
  12. canvas_sdk/effects/calendar/create_event.py +43 -0
  13. canvas_sdk/effects/form.py +69 -0
  14. canvas_sdk/effects/generate_full_chart_pdf.py +56 -0
  15. canvas_sdk/effects/metadata.py +26 -0
  16. canvas_sdk/effects/note/base.py +24 -8
  17. canvas_sdk/effects/note/note.py +17 -7
  18. canvas_sdk/effects/patient_metadata/base.py +2 -18
  19. canvas_sdk/effects/patient_metadata/patient_metadata_create_form.py +3 -61
  20. canvas_sdk/handlers/application.py +13 -1
  21. canvas_sdk/v1/data/__init__.py +12 -1
  22. canvas_sdk/v1/data/appointment.py +19 -0
  23. canvas_sdk/v1/data/immunization.py +159 -0
  24. canvas_sdk/v1/data/patient.py +3 -3
  25. plugin_runner/allowed-module-imports.json +45 -0
  26. protobufs/canvas_generated/messages/effects.proto +20 -0
  27. protobufs/canvas_generated/messages/events.proto +18 -0
  28. {canvas-0.61.0.dist-info → canvas-0.63.0.dist-info}/WHEEL +0 -0
  29. {canvas-0.61.0.dist-info → canvas-0.63.0.dist-info}/entry_points.txt +0 -0
@@ -863,6 +863,7 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
863
863
  PLUGIN_CREATED: _ClassVar[EventType]
864
864
  PLUGIN_UPDATED: _ClassVar[EventType]
865
865
  APPLICATION__ON_OPEN: _ClassVar[EventType]
866
+ APPLICATION__ON_CONTEXT_CHANGE: _ClassVar[EventType]
866
867
  PATIENT_PORTAL__GET_FORMS: _ClassVar[EventType]
867
868
  PATIENT_PORTAL__APPOINTMENT_CANCELED: _ClassVar[EventType]
868
869
  PATIENT_PORTAL__APPOINTMENT_RESCHEDULED: _ClassVar[EventType]
@@ -899,6 +900,19 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
899
900
  PATIENT_EXTERNAL_IDENTIFIER_DELETED: _ClassVar[EventType]
900
901
  PATIENT_METADATA_CREATED: _ClassVar[EventType]
901
902
  PATIENT_METADATA_UPDATED: _ClassVar[EventType]
903
+ APPOINTMENT__FORM__PROVIDERS__PRE_SEARCH: _ClassVar[EventType]
904
+ APPOINTMENT__FORM__LOCATIONS__PRE_SEARCH: _ClassVar[EventType]
905
+ APPOINTMENT__FORM__VISIT_TYPES__PRE_SEARCH: _ClassVar[EventType]
906
+ APPOINTMENT__FORM__DURATIONS__PRE_SEARCH: _ClassVar[EventType]
907
+ APPOINTMENT__FORM__REASON_FOR_VISIT__PRE_SEARCH: _ClassVar[EventType]
908
+ APPOINTMENT__FORM__PROVIDERS__POST_SEARCH: _ClassVar[EventType]
909
+ APPOINTMENT__FORM__LOCATIONS__POST_SEARCH: _ClassVar[EventType]
910
+ APPOINTMENT__FORM__VISIT_TYPES__POST_SEARCH: _ClassVar[EventType]
911
+ APPOINTMENT__FORM__DURATIONS__POST_SEARCH: _ClassVar[EventType]
912
+ APPOINTMENT__FORM__REASON_FOR_VISIT__POST_SEARCH: _ClassVar[EventType]
913
+ APPOINTMENT__FORM__GET_ADDITIONAL_FIELDS: _ClassVar[EventType]
914
+ APPOINTMENT_METADATA_CREATED: _ClassVar[EventType]
915
+ APPOINTMENT_METADATA_UPDATED: _ClassVar[EventType]
902
916
  DOCUMENT_REFERENCE_CREATED: _ClassVar[EventType]
903
917
  DOCUMENT_REFERENCE_UPDATED: _ClassVar[EventType]
904
918
  DOCUMENT_REFERENCE_DELETED: _ClassVar[EventType]
@@ -1763,6 +1777,7 @@ CLAIM__CONDITIONS: EventType
1763
1777
  PLUGIN_CREATED: EventType
1764
1778
  PLUGIN_UPDATED: EventType
1765
1779
  APPLICATION__ON_OPEN: EventType
1780
+ APPLICATION__ON_CONTEXT_CHANGE: EventType
1766
1781
  PATIENT_PORTAL__GET_FORMS: EventType
1767
1782
  PATIENT_PORTAL__APPOINTMENT_CANCELED: EventType
1768
1783
  PATIENT_PORTAL__APPOINTMENT_RESCHEDULED: EventType
@@ -1799,6 +1814,19 @@ PATIENT_EXTERNAL_IDENTIFIER_UPDATED: EventType
1799
1814
  PATIENT_EXTERNAL_IDENTIFIER_DELETED: EventType
1800
1815
  PATIENT_METADATA_CREATED: EventType
1801
1816
  PATIENT_METADATA_UPDATED: EventType
1817
+ APPOINTMENT__FORM__PROVIDERS__PRE_SEARCH: EventType
1818
+ APPOINTMENT__FORM__LOCATIONS__PRE_SEARCH: EventType
1819
+ APPOINTMENT__FORM__VISIT_TYPES__PRE_SEARCH: EventType
1820
+ APPOINTMENT__FORM__DURATIONS__PRE_SEARCH: EventType
1821
+ APPOINTMENT__FORM__REASON_FOR_VISIT__PRE_SEARCH: EventType
1822
+ APPOINTMENT__FORM__PROVIDERS__POST_SEARCH: EventType
1823
+ APPOINTMENT__FORM__LOCATIONS__POST_SEARCH: EventType
1824
+ APPOINTMENT__FORM__VISIT_TYPES__POST_SEARCH: EventType
1825
+ APPOINTMENT__FORM__DURATIONS__POST_SEARCH: EventType
1826
+ APPOINTMENT__FORM__REASON_FOR_VISIT__POST_SEARCH: EventType
1827
+ APPOINTMENT__FORM__GET_ADDITIONAL_FIELDS: EventType
1828
+ APPOINTMENT_METADATA_CREATED: EventType
1829
+ APPOINTMENT_METADATA_UPDATED: EventType
1802
1830
  DOCUMENT_REFERENCE_CREATED: EventType
1803
1831
  DOCUMENT_REFERENCE_UPDATED: EventType
1804
1832
  DOCUMENT_REFERENCE_DELETED: EventType
@@ -0,0 +1,13 @@
1
+ from canvas_sdk.effects.appointments_metadata.appointments_metadata_create_form import (
2
+ AppointmentsMetadataCreateFormEffect,
3
+ FormField,
4
+ InputType,
5
+ )
6
+ from canvas_sdk.effects.appointments_metadata.base import AppointmentsMetadata
7
+
8
+ __all__ = __exports__ = (
9
+ "FormField",
10
+ "InputType",
11
+ "AppointmentsMetadata",
12
+ "AppointmentsMetadataCreateFormEffect",
13
+ )
@@ -0,0 +1,12 @@
1
+ from canvas_sdk.effects import EffectType
2
+ from canvas_sdk.effects.form import BaseCreateFormEffect, FormField, InputType # noqa
3
+
4
+
5
+ class AppointmentsMetadataCreateFormEffect(BaseCreateFormEffect):
6
+ """An Effect that will create a form."""
7
+
8
+ class Meta:
9
+ effect_type = EffectType.APPOINTMENT__FORM__CREATE_ADDITIONAL_FIELDS
10
+
11
+
12
+ __exports__ = ("AppointmentsMetadataCreateFormEffect", "FormField", "InputType")
@@ -0,0 +1,30 @@
1
+ from typing import Any
2
+
3
+ from canvas_sdk.effects.metadata import BaseMetadata
4
+ from canvas_sdk.v1.data import Appointment
5
+
6
+
7
+ class AppointmentsMetadata(BaseMetadata):
8
+ """Effect to upsert an Appointment Metadata record."""
9
+
10
+ class Meta:
11
+ effect_type = "APPOINTMENT_METADATA"
12
+
13
+ appointment_id: str
14
+
15
+ def _get_error_details(self, method: Any) -> list:
16
+ errors = super()._get_error_details(method)
17
+
18
+ if not Appointment.objects.filter(id=self.appointment_id).exists():
19
+ errors.append(
20
+ self._create_error_detail(
21
+ "appointment_id",
22
+ f"Appointment with id: {self.appointment_id} does not exist.",
23
+ self.appointment_id,
24
+ )
25
+ )
26
+
27
+ return errors
28
+
29
+
30
+ __exports__ = ("AppointmentsMetadata",)
@@ -0,0 +1,4 @@
1
+ from canvas_sdk.effects.calendar.create_calendar import CalendarType, CreateCalendar
2
+ from canvas_sdk.effects.calendar.create_event import CreateEvent, EventRecurrence
3
+
4
+ __all__ = __exports__ = ("CreateCalendar", "CalendarType", "CreateEvent", "EventRecurrence")
@@ -0,0 +1,40 @@
1
+ from enum import StrEnum
2
+ from typing import Any
3
+ from uuid import UUID
4
+
5
+ from canvas_sdk.effects import EffectType, _BaseEffect
6
+
7
+
8
+ class CalendarType(StrEnum):
9
+ """Calendar type."""
10
+
11
+ Clinic = "Clinic"
12
+ Administrative = "Admin"
13
+
14
+
15
+ class CreateCalendar(_BaseEffect):
16
+ """Effect to create a Calendar."""
17
+
18
+ class Meta:
19
+ effect_type = EffectType.CALENDAR__CREATE
20
+
21
+ id: str | UUID | None = None
22
+ provider: str | UUID
23
+ type: CalendarType
24
+ description: str | None = None
25
+
26
+ @property
27
+ def values(self) -> dict[str, Any]:
28
+ """The Calendar's values."""
29
+ return {
30
+ "id": self.id,
31
+ "provider": self.provider,
32
+ "type": self.type,
33
+ "description": self.description,
34
+ }
35
+
36
+
37
+ __exports__ = (
38
+ "CreateCalendar",
39
+ "CalendarType",
40
+ )
@@ -0,0 +1,43 @@
1
+ from datetime import datetime
2
+ from enum import StrEnum
3
+ from typing import Any
4
+ from uuid import UUID
5
+
6
+ from canvas_sdk.effects import EffectType, _BaseEffect
7
+
8
+
9
+ class EventRecurrence(StrEnum):
10
+ """Calendar event recurrence."""
11
+
12
+ Daily = "FREQ=DAILY"
13
+ Weekly = "FREQ=WEEKLY"
14
+
15
+
16
+ class CreateEvent(_BaseEffect):
17
+ """Effect to create a Calendar event."""
18
+
19
+ class Meta:
20
+ effect_type = EffectType.CALENDAR__EVENT__CREATE
21
+
22
+ calendar_id: str | UUID
23
+ title: str
24
+ starts_at: datetime
25
+ ends_at: datetime
26
+ recurrence: EventRecurrence
27
+
28
+ @property
29
+ def values(self) -> dict[str, Any]:
30
+ """The event's values."""
31
+ return {
32
+ "calendar_id": self.calendar_id,
33
+ "title": self.title,
34
+ "starts_at": self.starts_at.isoformat(),
35
+ "ends_at": self.ends_at.isoformat(),
36
+ "recurrence": self.recurrence,
37
+ }
38
+
39
+
40
+ __exports__ = (
41
+ "CreateEvent",
42
+ "EventRecurrence",
43
+ )
@@ -0,0 +1,69 @@
1
+ from dataclasses import dataclass
2
+ from enum import StrEnum
3
+ from typing import Any
4
+
5
+ from pydantic_core import InitErrorDetails
6
+
7
+ from canvas_sdk.effects import _BaseEffect
8
+
9
+
10
+ class InputType(StrEnum):
11
+ """Type of input for a form field."""
12
+
13
+ TEXT = "text"
14
+ SELECT = "select"
15
+ DATE = "date"
16
+
17
+
18
+ @dataclass
19
+ class FormField:
20
+ """A class representing a Field."""
21
+
22
+ key: str | None = None
23
+ label: str | None = None
24
+ required: bool = False
25
+ editable: bool = True
26
+ type: InputType = InputType.TEXT
27
+ options: list[str] | None = None
28
+ value: str | None = None
29
+
30
+ def to_dict(self) -> dict[str, Any]:
31
+ """Convert the Field to a dictionary."""
32
+ return {
33
+ "key": self.key,
34
+ "label": self.label,
35
+ "required": self.required,
36
+ "editable": self.editable,
37
+ "type": self.type.value,
38
+ "options": self.options,
39
+ "value": self.value,
40
+ }
41
+
42
+
43
+ class BaseCreateFormEffect(_BaseEffect):
44
+ """Base class for form creation effects."""
45
+
46
+ form_fields: list[FormField]
47
+
48
+ @property
49
+ def values(self) -> dict[str, Any]:
50
+ """Return the values of the form as a dictionary."""
51
+ return {"form": [field.to_dict() for field in self.form_fields]}
52
+
53
+ def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
54
+ errors = super()._get_error_details(method)
55
+
56
+ for field in self.form_fields:
57
+ if field.type != InputType.SELECT and field.options:
58
+ errors.append(
59
+ self._create_error_detail(
60
+ "value",
61
+ "The options attribute is only used for fields of type select",
62
+ field.key,
63
+ )
64
+ )
65
+
66
+ return errors
67
+
68
+
69
+ __exports__ = ()
@@ -0,0 +1,56 @@
1
+ from typing import Any
2
+
3
+ from pydantic import Field
4
+ from pydantic_core import InitErrorDetails
5
+
6
+ from canvas_generated.messages.effects_pb2 import EffectType
7
+ from canvas_sdk.effects.base import _BaseEffect
8
+ from canvas_sdk.v1.data import Patient, Staff
9
+
10
+
11
+ class GenerateFullChartPDFEffect(_BaseEffect):
12
+ """
13
+ An Effect that will generate a full chart PDF for a patient.
14
+
15
+ Generated PDF will appear in task list assigned to requestor with "chart pdf" label within 10 minutes.
16
+ """
17
+
18
+ class Meta:
19
+ effect_type = EffectType.GENERATE_FULL_CHART_PDF
20
+
21
+ patient_id: str = Field(min_length=1)
22
+ requestor_staff_id: str = Field(min_length=1)
23
+
24
+ @property
25
+ def values(self) -> dict[str, Any]:
26
+ """The GenerateFullChartPDFEffect's values."""
27
+ return {
28
+ "patient_id": self.patient_id,
29
+ "requestor_staff_id": self.requestor_staff_id,
30
+ }
31
+
32
+ def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
33
+ errors = super()._get_error_details(method)
34
+
35
+ if not Patient.objects.filter(id=self.patient_id).exists():
36
+ errors.append(
37
+ self._create_error_detail(
38
+ "value",
39
+ f"Patient with ID {self.patient_id} does not exist.",
40
+ self.patient_id,
41
+ )
42
+ )
43
+
44
+ if not Staff.objects.filter(id=self.requestor_staff_id).exists():
45
+ errors.append(
46
+ self._create_error_detail(
47
+ "value",
48
+ f"Requestor staff with ID {self.requestor_staff_id} does not exist.",
49
+ self.requestor_staff_id,
50
+ )
51
+ )
52
+
53
+ return errors
54
+
55
+
56
+ __exports__ = ("GenerateFullChartPDFEffect",)
@@ -0,0 +1,26 @@
1
+ import json
2
+
3
+ from canvas_generated.messages.effects_pb2 import Effect
4
+ from canvas_sdk.base import TrackableFieldsModel
5
+
6
+
7
+ class BaseMetadata(TrackableFieldsModel):
8
+ """Base class for metadata effects."""
9
+
10
+ key: str
11
+
12
+ def upsert(self, value: str) -> Effect:
13
+ """Upsert the metadata."""
14
+ self._validate_before_effect("upsert")
15
+
16
+ return Effect(
17
+ type=f"UPSERT_{self.Meta.effect_type}", # type: ignore[attr-defined]
18
+ payload=json.dumps(
19
+ {
20
+ "data": {**self.values, "value": value},
21
+ }
22
+ ),
23
+ )
24
+
25
+
26
+ __exports__ = ()
@@ -57,14 +57,6 @@ class NoteOrAppointmentABC(TrackableFieldsModel, ABC):
57
57
  errors = super()._get_error_details(method)
58
58
 
59
59
  if method == "create":
60
- if self.instance_id:
61
- errors.append(
62
- self._create_error_detail(
63
- "value",
64
- "Instance ID should not be provided for create effects.",
65
- self.instance_id,
66
- )
67
- )
68
60
  if not self.practice_location_id:
69
61
  errors.append(
70
62
  self._create_error_detail(
@@ -167,11 +159,35 @@ class AppointmentABC(NoteOrAppointmentABC, ABC):
167
159
  duration_minutes: int | None = None
168
160
  status: AppointmentProgressStatus | None = None
169
161
  external_identifiers: list[AppointmentIdentifier] | None = None
162
+ parent_appointment_id: str | UUID | None = None
170
163
 
171
164
  def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
172
165
  """Validates the appointment instance and returns a list of error details if validation fails."""
173
166
  errors = super()._get_error_details(method)
174
167
 
168
+ if method != "create" and self.parent_appointment_id:
169
+ errors.append(
170
+ self._create_error_detail(
171
+ "value",
172
+ "parent_appointment_id can only be set when creating an appointment.",
173
+ self.parent_appointment_id,
174
+ )
175
+ )
176
+
177
+ # parent_appointment_id needs to be a different appointment
178
+ if (
179
+ self.parent_appointment_id
180
+ and self.instance_id
181
+ and self.parent_appointment_id == self.instance_id
182
+ ):
183
+ errors.append(
184
+ self._create_error_detail(
185
+ "value",
186
+ "parent_appointment_id cannot be the same as instance_id",
187
+ self.parent_appointment_id,
188
+ )
189
+ )
190
+
175
191
  if method == "create":
176
192
  # Additional required fields for appointment creation
177
193
  if not self.start_time:
@@ -67,6 +67,16 @@ class Note(NoteOrAppointmentABC):
67
67
  None,
68
68
  )
69
69
  )
70
+
71
+ if self.instance_id and NoteModel.objects.filter(id=self.instance_id).exists():
72
+ errors.append(
73
+ self._create_error_detail(
74
+ "value",
75
+ f"Note with ID {self.instance_id} already exists.",
76
+ self.instance_id,
77
+ )
78
+ )
79
+
70
80
  elif method == "update":
71
81
  if self.note_type_id:
72
82
  errors.append(
@@ -85,14 +95,14 @@ class Note(NoteOrAppointmentABC):
85
95
  )
86
96
  )
87
97
 
88
- if self.instance_id and not NoteModel.objects.filter(id=self.instance_id).exists():
89
- errors.append(
90
- self._create_error_detail(
91
- "value",
92
- f"Note with ID {self.instance_id} does not exist.",
93
- self.instance_id,
98
+ if self.instance_id and not NoteModel.objects.filter(id=self.instance_id).exists():
99
+ errors.append(
100
+ self._create_error_detail(
101
+ "value",
102
+ f"Note with ID {self.instance_id} does not exist.",
103
+ self.instance_id,
104
+ )
94
105
  )
95
- )
96
106
 
97
107
  if self.note_type_id:
98
108
  note_type_category = (
@@ -1,21 +1,18 @@
1
- import json
2
1
  from typing import Any
3
2
 
4
3
  from pydantic_core import InitErrorDetails
5
4
 
6
- from canvas_generated.messages.effects_pb2 import Effect
7
- from canvas_sdk.base import TrackableFieldsModel
5
+ from canvas_sdk.effects.metadata import BaseMetadata
8
6
  from canvas_sdk.v1.data import Patient
9
7
 
10
8
 
11
- class PatientMetadata(TrackableFieldsModel):
9
+ class PatientMetadata(BaseMetadata):
12
10
  """Effect to upsert a Patient Metadata record."""
13
11
 
14
12
  class Meta:
15
13
  effect_type = "PATIENT_METADATA"
16
14
 
17
15
  patient_id: str
18
- key: str
19
16
 
20
17
  def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
21
18
  errors = super()._get_error_details(method)
@@ -31,18 +28,5 @@ class PatientMetadata(TrackableFieldsModel):
31
28
 
32
29
  return errors
33
30
 
34
- def upsert(self, value: str) -> Effect:
35
- """Upsert the patient metadata."""
36
- self._validate_before_effect("upsert")
37
-
38
- return Effect(
39
- type=f"UPSERT_{self.Meta.effect_type}",
40
- payload=json.dumps(
41
- {
42
- "data": {**self.values, "value": value},
43
- }
44
- ),
45
- )
46
-
47
31
 
48
32
  __exports__ = ("PatientMetadata",)
@@ -1,70 +1,12 @@
1
- from dataclasses import dataclass
2
- from enum import StrEnum
3
- from typing import Any
1
+ from canvas_sdk.effects import EffectType
2
+ from canvas_sdk.effects.form import BaseCreateFormEffect, FormField, InputType # noqa
4
3
 
5
- from pydantic_core import InitErrorDetails
6
4
 
7
- from canvas_sdk.effects import EffectType, _BaseEffect
8
-
9
-
10
- class InputType(StrEnum):
11
- """Type of input for a form field."""
12
-
13
- TEXT = "text"
14
- SELECT = "select"
15
- DATE = "date"
16
-
17
-
18
- @dataclass
19
- class FormField:
20
- """A class representing a Field."""
21
-
22
- key: str | None = None
23
- label: str | None = None
24
- required: bool = False
25
- editable: bool = True
26
- type: InputType = InputType.TEXT
27
- options: list[str] | None = None
28
-
29
- def to_dict(self) -> dict[str, Any]:
30
- """Convert the Field to a dictionary."""
31
- return {
32
- "key": self.key,
33
- "label": self.label,
34
- "required": self.required,
35
- "editable": self.editable,
36
- "type": self.type.value,
37
- "options": self.options,
38
- }
39
-
40
-
41
- class PatientMetadataCreateFormEffect(_BaseEffect):
5
+ class PatientMetadataCreateFormEffect(BaseCreateFormEffect):
42
6
  """An Effect that will create a form."""
43
7
 
44
8
  class Meta:
45
9
  effect_type = EffectType.PATIENT_METADATA__CREATE_ADDITIONAL_FIELDS
46
10
 
47
- form_fields: list[FormField]
48
-
49
- @property
50
- def values(self) -> dict[str, Any]:
51
- """Return the values of the form as a dictionary."""
52
- return {"form": [field.to_dict() for field in self.form_fields]}
53
-
54
- def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
55
- errors = super()._get_error_details(method)
56
-
57
- for field in self.form_fields:
58
- if field.type != InputType.SELECT and field.options:
59
- errors.append(
60
- self._create_error_detail(
61
- "value",
62
- "The options attribute is only used for fields of type select",
63
- field.key,
64
- )
65
- )
66
-
67
- return errors
68
-
69
11
 
70
12
  __exports__ = ("PatientMetadataCreateFormEffect", "FormField", "InputType")
@@ -8,13 +8,21 @@ from canvas_sdk.handlers import BaseHandler
8
8
  class Application(BaseHandler, ABC):
9
9
  """An embeddable application that can be registered to Canvas."""
10
10
 
11
- RESPONDS_TO = [EventType.Name(EventType.APPLICATION__ON_OPEN)]
11
+ RESPONDS_TO = [
12
+ EventType.Name(EventType.APPLICATION__ON_OPEN),
13
+ EventType.Name(EventType.APPLICATION__ON_CONTEXT_CHANGE),
14
+ ]
12
15
 
13
16
  def compute(self) -> list[Effect]:
14
17
  """Handle the application events."""
15
18
  match self.event.type:
16
19
  case EventType.APPLICATION__ON_OPEN:
17
20
  return [self.on_open()] if self.event.target.id == self.identifier else []
21
+ case EventType.APPLICATION__ON_CONTEXT_CHANGE:
22
+ if self.event.target.id == self.identifier:
23
+ effect = self.on_context_change()
24
+ return [effect] if effect is not None else []
25
+ return []
18
26
  case _:
19
27
  return []
20
28
 
@@ -23,6 +31,10 @@ class Application(BaseHandler, ABC):
23
31
  """Handle the application open event."""
24
32
  ...
25
33
 
34
+ def on_context_change(self) -> Effect | None:
35
+ """Handle the application context change event."""
36
+ return None
37
+
26
38
  @property
27
39
  def identifier(self) -> str:
28
40
  """The application identifier."""
@@ -1,5 +1,5 @@
1
1
  from .allergy_intolerance import AllergyIntolerance, AllergyIntoleranceCoding
2
- from .appointment import Appointment, AppointmentExternalIdentifier
2
+ from .appointment import Appointment, AppointmentExternalIdentifier, AppointmentMetadata
3
3
  from .assessment import Assessment
4
4
  from .banner_alert import BannerAlert
5
5
  from .billing import BillingLineItem, BillingLineItemModifier
@@ -18,6 +18,12 @@ from .discount import Discount
18
18
  from .facility import Facility
19
19
  from .goal import Goal
20
20
  from .imaging import ImagingOrder, ImagingReport, ImagingReview
21
+ from .immunization import (
22
+ Immunization,
23
+ ImmunizationCoding,
24
+ ImmunizationStatement,
25
+ ImmunizationStatementCoding,
26
+ )
21
27
  from .invoice import Invoice
22
28
  from .lab import (
23
29
  LabOrder,
@@ -93,6 +99,7 @@ from .user import CanvasUser
93
99
 
94
100
  __all__ = __exports__ = (
95
101
  "Appointment",
102
+ "AppointmentMetadata",
96
103
  "AppointmentExternalIdentifier",
97
104
  "AllergyIntolerance",
98
105
  "AllergyIntoleranceCoding",
@@ -129,6 +136,10 @@ __all__ = __exports__ = (
129
136
  "ImagingOrder",
130
137
  "ImagingReport",
131
138
  "ImagingReview",
139
+ "Immunization",
140
+ "ImmunizationCoding",
141
+ "ImmunizationStatement",
142
+ "ImmunizationStatementCoding",
132
143
  "InstallmentPlan",
133
144
  "Interview",
134
145
  "InterviewQuestionnaireMap",
@@ -29,6 +29,11 @@ class Appointment(IdentifiableModel):
29
29
  related_name="appointments",
30
30
  null=True,
31
31
  )
32
+
33
+ parent_appointment = models.ForeignKey(
34
+ "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
35
+ )
36
+
32
37
  appointment_rescheduled_from = models.ForeignKey(
33
38
  "self",
34
39
  on_delete=models.DO_NOTHING,
@@ -74,8 +79,22 @@ class AppointmentExternalIdentifier(IdentifiableModel):
74
79
  )
75
80
 
76
81
 
82
+ class AppointmentMetadata(IdentifiableModel):
83
+ """A class representing Appointment Metadata."""
84
+
85
+ class Meta:
86
+ db_table = "canvas_sdk_data_api_appointmentmetadata_001"
87
+
88
+ appointment = models.ForeignKey(
89
+ "v1.Appointment", on_delete=models.CASCADE, related_name="metadata", null=True
90
+ )
91
+ key = models.CharField(max_length=32)
92
+ value = models.CharField(max_length=256)
93
+
94
+
77
95
  __exports__ = (
78
96
  "AppointmentProgressStatus",
79
97
  "Appointment",
98
+ "AppointmentMetadata",
80
99
  "AppointmentExternalIdentifier",
81
100
  )