Class SupportCase
- Namespace
- Tharga.Team
- Assembly
- Tharga.Team.dll
A support conversation belonging to a team.
public record SupportCase : IEquatable<SupportCase>
- Inheritance
-
SupportCase
- Implements
- Inherited Members
Remarks
The team owns the case; the author is a link that may dangle. A case is raised by a person but is
about the team's account, and the team is what still exists a year later. Deleting a user runs
ITeamService.RemoveUserFromAllTeamsAsync, which strips that person's membership from every team —
so a case keyed on a member would be orphaned by design. Authorization keys on TeamKey
alone, and no check requires the author to still resolve.
The transcript is not carried here. A case can accumulate years of messages, so history is read separately and paged; see GetMessagesAsync(string, string, string, int, CancellationToken). This record is the header.
Properties
AnswersRatedDown
How many of the assistant's answers in this case the person it answered found unhelpful.
public int AnswersRatedDown { get; init; }
Property Value
AnswersRatedUp
How many of the assistant's answers in this case the person it answered found helpful.
public int AnswersRatedUp { get; init; }
Property Value
AssistantGaps
What the assistant was asked in this case and had no way to do, oldest first, in its own words.
public string[] AssistantGaps { get; init; }
Property Value
- string[]
Remarks
For the people who build the assistant, so they can see what to add. Empty on every case it could help with, and on every case raised before this existed.
AssistantState
Whether an assistant is answering this case.
public SupportAssistantState AssistantState { get; init; }
Property Value
Remarks
Defaults to None, which is what every case raised before assistants existed deserializes as — so nothing already stored changes meaning.
AuthorIdentity
The stable authentication subject of whoever raised the case. May no longer resolve to a user or a member, which is expected rather than exceptional.
public required string AuthorIdentity { get; init; }
Property Value
AuthorName
The author's display name as it was when the case was raised.
public required string AuthorName { get; init; }
Property Value
AwaitingSupport
Whether the case is open and its newest entry is from the one who raised it — waiting on support.
public bool AwaitingSupport { get; init; }
Property Value
Bindings
External projections. Empty until the channel work lands.
public SupportChannelBinding[] Bindings { get; init; }
Property Value
ClosedAt
public DateTime? ClosedAt { get; init; }
Property Value
ClosedBy
The stable subject of whoever closed the case, or null while it is open.
public string ClosedBy { get; init; }
Property Value
Remarks
May be AutoClose, when the case closed itself rather than being closed by a person.
ClosedReason
Why the case is closed, or null while it is open.
public SupportCaseClosureReason? ClosedReason { get; }
Property Value
Remarks
Derived from ClosedBy rather than stored, and deliberately so. The store already records who closed a case; adding a reason to CloseCaseAsync(string, string, DateTime, string, SupportMessage, CancellationToken) would change a signature that hosts implement for their own storage, which is a compile-time break in somebody else's repository for a value that can be read from what is already there.
It follows that the reason is exactly as trustworthy as the actor, which is the right coupling: a case closed by the sweeper is closed for inactivity by definition, not by a flag that could disagree with who closed it.
CreatedAt
public required DateTime CreatedAt { get; init; }
Property Value
ExternalContact
Who wrote, when the case was raised through a public form by somebody who was not signed in; null for every
other case.
public ExternalSupportContact ExternalContact { get; init; }
Property Value
Remarks
What the visitor typed, unverified, kept so staff see the phone number, the organisation and whether consent was
given. A case raised before this existed reads null.
Id
public required string Id { get; init; }
Property Value
LastMessageAt
When the newest entry was written, so a list can put the liveliest conversation first.
public DateTime? LastMessageAt { get; init; }
Property Value
MessageCount
Number of entries in the transcript, so a list can be rendered without reading it.
public required int MessageCount { get; init; }
Property Value
Status
public required SupportCaseStatus Status { get; init; }
Property Value
Subject
public required string Subject { get; init; }
Property Value
SuggestedTeams
Teams an unassigned case may belong to, found from the sender's address when it arrived by mail; empty when nothing matched or the case opened in a team.
public SupportCaseTeam[] SuggestedTeams { get; init; }
Property Value
Remarks
A suggestion, never an assignment. Staff decide, and giving the case one of these goes through AssignCaseAsync(string, string, CancellationToken) exactly as choosing any other team does.
SuggestionReason
Why SuggestedTeams are suggestions rather than the case's team; null when there are none.
public SupportTeamSuggestionReason? SuggestionReason { get; init; }
Property Value
TeamKey
The owning team, or null when the case is not assigned to one.
public string TeamKey { get; init; }
Property Value
Remarks
Unassigned is a durable state, not a staging area. A case that arrived by mail from somebody
who belongs to several teams — or to none the toolkit can see — belongs to no team until a support
agent says which, and it can be answered, closed and reopened meanwhile. The person who knows which
tenant a problem concerns is a human reading it, not an algorithm reading a From: header.
The invariant it replaces still holds where there is a team. A case with one is loaded through it and authorized by membership exactly as before, so holding a case id from another tenant still gains nothing. What changed is that "no team" became expressible, not that team scoping became optional.
An unassigned case is governed system-wide, because a team scope has no team to be held against. See AssignCaseAsync(string, string, CancellationToken).
The one mail that opens in a team is one whose sender the transport verified and whose address
belongs to a single account that is an active member of a single team. A From header alone never
places a case; anything short of that opens here with SuggestedTeams instead.
TeamName
The team's name as it was when the case was raised, for wording a message about it; null when it
could not be read, and for a case that arrived with no team.
public string TeamName { get; init; }
Property Value
Visibility
Whether the team's support readers may read this case, when the host lets each case decide.
public SupportCaseVisibility? Visibility { get; init; }
Property Value
Remarks
null when nobody chose — every case under team-wide visibility, and any case written before
visibility existed. Under per-case visibility a null reads as Private,
because nobody ever shared it.