using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications; using ZB.MOM.WW.ScadaBridge.Commons.Types.Notifications; namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; /// /// Data access for the central notification outbox — the queue of /// rows the outbox actor drains, retries, and audits. Distinct from /// , which manages notification list configuration. /// /// /// Persistence model: and commit /// internally, so each call is its own transaction — suited to the outbox actor committing one /// row's status transition at a time. The standalone is available /// for callers that stage multiple changes and want to flush them together. /// public interface INotificationOutboxRepository { /// /// Inserts only if no row with the same /// exists. Returns true when a new /// row was inserted, false when an existing row was left untouched. /// Commits internally — this call is its own transaction. /// /// The notification to insert. /// Cancellation token. /// True if inserted, false if already exists. Task InsertIfNotExistsAsync(Notification n, CancellationToken cancellationToken = default); /// /// Returns notifications ready for a delivery attempt: Pending rows, plus /// Retrying rows whose NextAttemptAt is at or before . /// Terminal rows are excluded. Ordered by CreatedAt ascending, capped at /// . /// /// The current time for evaluating due retries. /// Maximum number of rows to return. /// Cancellation token. /// A list of notifications ready for delivery. Task> GetDueAsync(DateTimeOffset now, int batchSize, CancellationToken cancellationToken = default); /// Returns the notification with the given id, or null. /// The notification identifier. /// Cancellation token. /// The notification, or null if not found. Task GetByIdAsync(string notificationId, CancellationToken cancellationToken = default); /// /// Persists 's delivery-state columns — /// Status, RetryCount, LastError, ResolvedTargets, /// LastAttemptAt, NextAttemptAt, DeliveredAt. Commits /// internally — this call is its own transaction. /// /// /// Scope is deliberately narrow. Those seven columns are the ONLY /// mutable state a notification has: everything else (identity, type, list, /// subject, body, type data, source/origin attribution, enqueue and creation /// timestamps) is written once at ingest and is immutable by contract. Every /// caller — the dispatcher's per-attempt write and the operator retry/discard /// one-shots — touches only this set. Implementations are therefore free to /// issue a targeted UPDATE of these columns rather than rewriting the whole /// row, which matters because the row carries nvarchar(max) body and /// payload columns and the dispatcher writes on EVERY attempt. A future /// caller that needs to change an immutable column must add its own method /// rather than widening this one. /// /// The return value is the not-found signal. A targeted UPDATE against /// a row that no longer exists (the retention purge removed it between the /// read and the write) affects zero rows and is otherwise indistinguishable /// from success — which is how an operator Retry/Discard came to report /// success against a vanished notification. Implementations MUST return /// when no row matched. Callers that speak to a human /// (the operator one-shots) must surface that as "not found"; the dispatcher /// treats it as a lost race and logs. /// /// /// The notification whose delivery state should be persisted. /// Cancellation token. /// /// when the row was found and its delivery state /// persisted; when no row with that /// NotificationId exists any more. /// Task UpdateAsync(Notification n, CancellationToken cancellationToken = default); /// /// Returns a page of notifications matching , ordered by /// CreatedAt descending, together with the total matching count. /// /// The query filter. /// The page number (1-based). /// The page size. /// Cancellation token. /// A tuple of rows and total count. Task<(IReadOnlyList Rows, int TotalCount)> QueryAsync( NotificationOutboxFilter filter, int pageNumber, int pageSize, CancellationToken cancellationToken = default); /// /// Bulk-deletes terminal rows (Delivered/Parked/Discarded) whose CreatedAt is /// older than . Returns the number of rows deleted. /// /// The cutoff time for deletion. /// Cancellation token. /// The number of rows deleted. Task DeleteTerminalOlderThanAsync(DateTimeOffset cutoff, CancellationToken cancellationToken = default); /// /// Computes a point-in-time . The stuck and /// delivered cutoffs are supplied by the caller; the current time used for /// OldestPendingAge is captured inside the method. /// /// The time threshold for marking notifications as stuck. /// The time threshold for counting delivered notifications. /// Cancellation token. /// A KPI snapshot. Task ComputeKpisAsync( DateTimeOffset stuckCutoff, DateTimeOffset deliveredSince, CancellationToken cancellationToken = default); /// /// Computes a point-in-time per source site. /// Sites with no notification rows at all are omitted. The stuck and delivered cutoffs /// are supplied by the caller; the current time used for OldestPendingAge is /// captured inside the method. /// /// The time threshold for marking notifications as stuck. /// The time threshold for counting delivered notifications. /// Cancellation token. /// A list of per-site KPI snapshots. Task> ComputePerSiteKpisAsync( DateTimeOffset stuckCutoff, DateTimeOffset deliveredSince, CancellationToken cancellationToken = default); /// /// Computes a point-in-time per originating node. /// Nodes with no notification rows at all are omitted; rows with a NULL /// SourceNode are excluded. The stuck and delivered cutoffs are supplied by the /// caller; the current time used for OldestPendingAge is captured inside the method. /// /// The time threshold for marking notifications as stuck. /// The time threshold for counting delivered notifications. /// Cancellation token. /// A list of per-node KPI snapshots, ordered by node name. Task> ComputePerNodeKpisAsync( DateTimeOffset stuckCutoff, DateTimeOffset deliveredSince, CancellationToken cancellationToken = default); /// /// Persists pending changes tracked on the underlying context. Use this when staging /// multiple changes for a single commit; the individual mutating methods on this /// interface already commit on their own. /// /// Cancellation token. /// The number of changes persisted. Task SaveChangesAsync(CancellationToken cancellationToken = default); }