In enterprise customer support and internal IT operations, the Ticketing Subsystem addresses intricate lifecycle management and contractual SLA agreements:
- SLA Breach Prevention: Critical P0 incidents must trigger automated escalation workflows and multi-channel alarms if unacknowledged within 15 minutes;
- Concurrent Assignment Conflicts: Multiple agents clicking “Claim Ticket” concurrently must not corrupt ticket ownership;
- Strict Multi-Tenant Boundaries: Tenant A’s internal diagnostic attachments must never be accessible across tenant perimeters.
BitzOrcas.Modern Ticketing Module combines “Unified Aggregate Golden Standard + SLA State Machines + Event-Driven Alerts”:
Ticket Lifecycle and SLA Escalation Topology
Step 1: Core Domain Entity Ticket
using System;using System.ComponentModel;using BitzOrcas.Domain.Entities;using BitzOrcas.Domain.Results;using BitzOrcas.Persistence.Metadata;
namespace BitzOrcas.Ticket.Domain;
public static class TicketErrors{ public static readonly Error AlreadyClosed = Error.Conflict("Ticket.AlreadyClosed", "Closed tickets cannot be assigned.");
public static readonly Error NotFound = Error.NotFound("Ticket.NotFound", "Ticket not found.");}
[BitzTable("SysTicket", IsTenant = true, IsSoftDelete = true, Description = "Business Tickets Table")][BitzIndex("IX_SysTicket_Status_Tenant", nameof(Status), nameof(TenantId))]public sealed class Ticket : TenantAggregateRoot<string>{ [BitzColumn(Length = 200, IsRequired = true)] public string Title { get; private set; } = string.Empty;
[BitzColumn(Length = 4000, IsRequired = true)] public string Description { get; private set; } = string.Empty;
[BitzColumn(IsRequired = true)] public TicketPriority Priority { get; private set; } = TicketPriority.Medium;
[BitzColumn(IsRequired = true)] public TicketStatus Status { get; private set; } = TicketStatus.Open;
[BitzColumn(Length = 64)] public string? AssigneeUserId { get; private set; }
[BitzColumn(IsRequired = true)] public DateTimeOffset SlaResponseDueAt { get; private set; }
[Obsolete("For ORM materialization only. Use Create.", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] public Ticket() : base("0") { }
// Domain method: assigns ticket to engineer and sets in-progress state public Result AssignTo(string engineerUserId, string operatorId, DateTimeOffset now) { if (Status == TicketStatus.Closed || Status == TicketStatus.Resolved) { return Result.Failure(TicketErrors.AlreadyClosed); }
AssigneeUserId = engineerUserId; Status = TicketStatus.InProgress;
// Emit domain event AddDomainEvent(new TicketAssignedDomainEvent(Id, TenantId, engineerUserId, operatorId, now));
return Result.Success(); }}Step 2: Assign Ticket Command Slice
using System.Threading;using System.Threading.Tasks;using BitzOrcas.Application.Abstractions.Security;using BitzOrcas.Domain.Abstractions;using BitzOrcas.Domain.Results;using BitzOrcas.Ticket.Domain;
public sealed class AssignTicketCommandHandler( ICommandRepository<Ticket, string> ticketRepo, ICurrentUser currentUser, IAppClock clock){ public async ValueTask<Result> Handle(AssignTicketCommand command, CancellationToken ct) { // 1. Load target ticket aggregate var ticketResult = await ticketRepo.FindAsync(command.TicketId, ct); if (ticketResult.IsFailure) { return Result.Failure(TicketErrors.NotFound); }
var ticket = ticketResult.Value;
// 2. Execute domain assignment var assignResult = ticket.AssignTo(command.AssigneeUserId, currentUser.UserId, clock.UtcNow); if (assignResult.IsFailure) return assignResult;
// 3. Persist aggregate (auto-validates optimistic concurrency version) await ticketRepo.UpdateAsync(ticket, ct);
return Result.Success(); }}Summary
The Ticketing Module supercharges operations:
- Optimistic Concurrency: Eliminates assignment race conditions;
- Automated SLA Lifecycle: Point-in-time clocks trigger automated escalations;
- Event-Driven Integration: Real-time notifications keep agents and customers aligned.