Skip to content

Hearing scheduling and calendar sync

Persists a legal-case hearing schedule and keeps the assigned attorney/ALJ Outlook/Exchange calendars in sync with the schedule's lifecycle.

Field Value
Operation ID hearing-scheduling-and-calendar-sync
Trigger Save or delete on clsLegalSchedule
Inputs Schedule fields (type, status, date/time, attorney, ALJ, hearing location, duration)
Outputs Persisted schedule row, Outlook/Exchange calendar appointments
Primary components clsLegalSchedule.BeforeInsert/AfterInsert/BeforeUpdate/AfterUpdate/BeforeDelete/AfterDelete, clsLegalSchedule.UpdateCalendars, clsLegalSchedule.RemoveFromCalendars
Entry point SAIF.Business.Claim.Legal\LegalNet\clsLegalSchedule.vb

Steps

  1. Rule validation runs on Save(), checking these real conditions on the object's fields:
  2. Rule_ValidTypeCode/Rule_ValidStatusCode — ScheduleTypeCode.Trim.Length > 0 and ScheduleStatusCode.Trim.Length > 0; either field left blank blocks the save (BR-SC-001).
  3. Rule_ValidDate — only runs when IsSetorReset (status is Set or Reset) and the object is dirty; requires ScheduleDate > Today (BR-SC-002).
  4. Rule_ValidAttorney — fails when both IsAssignedToOutsideCounsel (an other-firm ID is set) and IsAssignedToSaifAttorney (a non-blank, non-"0" attorney employee ID is set) are true at the same time (BR-SC-003).
  5. Rule_ValidOutsideCounsel — fails when AssignedOtherFirmID > 0 but AssignedPersonID = 0, or when AssignedOtherFirmID = 0 but AssignedPersonID > 0; a firm and an attorney-at-the-firm must both be set or both be blank (BR-SC-004).
  6. Rule_ValidScheduleDate/Rule_ValidTime — require ScheduleDate > Date.MinValue and ScheduleTime.TimeOfDay.Hours > 0 (BR-SC-005).
  7. Rule_ValidTimeRange (LevelTwo) — requires the scheduled hour to be between 7 and 18 inclusive (BR-SC-006).
  8. Rule_ValidTimeDuration (LevelTwo) — when DurationCode = "AM", the hour must be in [7, 12); when DurationCode = "PM", the hour must be in [12, 18]; any other duration code passes without a time-range check (BR-SC-007).
  9. Rule_ValidDuration — requires DurationCode.Trim.Length > 0 (BR-SC-008).
  10. On insert: Create() (invoked before BeforeInsert) sets LegalCaseID from the criteria, assigns a new ScheduleID from the SCHEDULE_SEQ sequence, stamps CreateDate = Now and CreateUser from the portal user, and defaults ScheduleStatusCode = "SET". BeforeInsert then builds the insert parameter list from every schedule field (type, status, keep-attorney flag, date/time, ALJ, hearing location, attorney/other-firm/person IDs, duration, assigned/reminder dates, schedule text), clearing the attorney employee ID to empty when it is "0" and clearing the other-firm/person IDs to empty when they are 0 so blank associations aren't stored as literal zeros. The framework issues the insert stored procedure, and AfterInsert checks the RowsAffected return parameter: if it is not exactly 1 it adds an InsertError, otherwise it marks the object no longer new. Calendar sync is not triggered directly from AfterInsert in this class — UpdateCalendars() is available as a public method that callers (such as the assignment process) invoke to push schedule data into Outlook/Exchange.
  11. On update: BeforeUpdate builds the same field list as BeforeInsert (minus create-only fields, plus UpdateUser/UpdateDate, which OnSave() stamps to Now/the current portal user just before the base save runs) for the update stored procedure. AfterUpdate performs the identical RowsAffected <> 1 check, adding an InsertError message (the same error key/text as the insert path is reused here) on failure or marking the object clean on success. UpdateCalendars() calls clsAssignmentProcess.updateOutlook("CASE", LegalCaseID, UserID), which re-reads the case's clsOutlookFields rows and, for each one, rebuilds a saifAppt keyed by "S" + ScheduleID (zero padded to 8 digits): the subject is composed from schedule-type initial, status code (when not HearingSet), claim number, claimant name, schedule time, and either the ALJ name or the free-text schedule note, with " - WT 3" appended for weight-3 cases and " - CONSOL" appended for consolidated cases; the appointment's mailbox (UserName) is the assigned attorney's mail ID; start and end times are derived from DurationCode (AM → 8:00 AM–12:00 PM, PM → 1:00 PM–5:00 PM, FULL → 8:00 AM–5:00 PM same day, any multi-day duration code → 8:00 AM start through 5:00 PM on ScheduleDate + (DaysNum - 1) days); if the schedule date is in the future and the status is Postponed, Cancelled, or Dismissed the appointment is deleted instead of upserted. The actual Exchange call (clsAssignmentProcess.callSaifExUpdate) then creates or updates the appointment via clsExchangeUpdate.UpdateAppointment — there's no separate lookup step to decide create-vs-update; the Exchange service resolves that by the appointment's ExID. In non-production environments the mailbox ID is remapped to one of several fixed test mailboxes (ztstatt2/ztstatt3/ztstatt4) based on the first letter of the real mail ID, so calendar writes never reach a real attorney's mailbox outside production (BR-SC-009).
  12. On delete: BeforeDelete builds the delete stored-procedure call keyed by ScheduleID and writes an audit-log entry ("Deleting Schedule Record From Oracle") recording the schedule ID and user ID; it performs no additional business validation beyond that. AfterDelete checks RowsAffected <> 1 (adds a DeleteError on failure) and otherwise marks the object new and calls RemoveFromCalendars(). RemoveFromCalendars() only acts if the object is dirty and the attorney employee ID's original (pre-change) value is non-blank: it resolves that original attorney's mail ID, builds an appointment ID of "S" + ScheduleID, and calls callSaifExUpdate with the delete flag set, searching an Exchange window from the original schedule date through 7 days later. For multi-day durations (DurationCode ending in "DAY"), it repeats the delete once per day, appending a zero-padded day suffix (-00, -01, and so on) to each appointment ID so every day's calendar entry is individually removed.
  13. GetLegalSecretaryID(inAssignedAttorneyID) runs SELECT secretary_emplid FROM attorney_profile WHERE profile_emplid = :ID for the given attorney ID (or the OUTSIDECNCL constant when the schedule is assigned to outside counsel), returning an empty string if no row is found; it backs the LegalSecretary property, not the calendar appointment itself. GetALJName() runs SELECT last_name || ', ' || first_name FROM legal_person WHERE legal_person_seq_num = <ALJPersonID> and returns an empty string if no match is found; its result is what updateOutlook uses to append " - <ALJName>" to the appointment subject when an ALJ is assigned.

Process flow

flowchart TD
    Start(["Save or delete<br>clsLegalSchedule"]) --> Rules{"Rules valid?"}
    Rules -->|No| Err(["Save blocked,<br>errors surfaced"])
    Rules -->|Yes| Op{"Operation?"}
    Op -->|Insert| BI["BeforeInsert:<br>build insert params,<br>blank zero IDs"] --> Ins["Framework insert"] --> AI["AfterInsert:<br>check RowsAffected,<br>MarkNew"]
    Op -->|Update| BU["BeforeUpdate:<br>build update params"] --> Upd["Framework update"] --> AU["AfterUpdate:<br>check RowsAffected"] --> UC["UpdateCalendars:<br>rebuild Outlook appt<br>via updateOutlook"]
    Op -->|Delete| BD["BeforeDelete:<br>audit log entry"] --> Del["Framework delete"] --> AD["AfterDelete:<br>check RowsAffected,<br>MarkNew"] --> RC["RemoveFromCalendars:<br>delete Exchange appt(s)"]
    AI --> EndOk(["Save complete"])
    UC --> EndOk
    RC --> EndOk

Failure and edge-case behavior

  • AfterInsert/AfterUpdate/AfterDelete check RowsAffected <> 1 and raise an error message (InsertError on insert/update, DeleteError on delete) when the check fails, consistent with the library's optimistic-concurrency pattern.
  • RemoveFromCalendars() only removes an appointment when the object is dirty and the attorney employee ID had a non-blank original value; a delete with no prior attorney assignment leaves no Exchange appointment to remove and is a no-op.
  • Non-production environments redirect calendar mailbox operations to one of several fixed test mailboxes (ztstatt2/ztstatt3/ztstatt4, selected by the first letter of the real mail ID) rather than the real attorney/ALJ mailbox.
  • callSaifExUpdate and findAppointment swallow Exchange-service exceptions with clsLogError when running in a web request context, but re-throw them when running in batch mode (no HttpContext.Current).*

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • SAIF.Business.Claim.Legal\LegalNet\clsLegalSchedule.vb
  • SAIF.Business.Claim.Legal\LegalNet\clsAssignmentProcess.vb