Files
KArtSell.Aegis/docs/Design/kbx-foundation-v36/backend/Shared/Operations/OperationsProjectionWriter.cs
T

92 lines
4.4 KiB
C#

using Dapper;
using Npgsql;
using System.Text.Json;
namespace Shared.Operations;
public sealed record UpsertWorkItem(
string TenantId,
string SourceModule,
string SourceType,
string SourceId,
string ReferenceNo,
string? SourceScreenId,
long? SourceVersion,
string Code,
string Title,
string? Detail,
string Severity,
DateTimeOffset OccurredAt,
DateTimeOffset? DueAt = null,
string? RetryActionKey = null,
bool AllowManualResolution = false,
object? Context = null);
public sealed class OperationsProjectionWriter(NpgsqlDataSource dataSource)
{
public async Task<Guid> UpsertAsync(UpsertWorkItem item, CancellationToken ct)
{
await using var connection = await dataSource.OpenConnectionAsync(ct);
var id = Guid.NewGuid();
const string sql = """
insert into kbx.work_items(
id, tenant_id, source_module, source_type, source_id, reference_no, source_screen_id, source_version,
code, title, detail, severity, status, retry_action_key, allow_manual_resolution,
context, occurred_at, due_at)
values(
@Id, @TenantId, @SourceModule, @SourceType, @SourceId, @ReferenceNo, @SourceScreenId, @SourceVersion,
@Code, @Title, @Detail, @Severity, 'open', @RetryActionKey, @AllowManualResolution,
cast(@Context as jsonb), @OccurredAt, @DueAt)
on conflict(tenant_id, source_module, source_type, source_id, code)
do update set
source_version = excluded.source_version,
reference_no = excluded.reference_no,
source_screen_id = excluded.source_screen_id,
title = excluded.title,
detail = excluded.detail,
severity = excluded.severity,
status = case when kbx.work_items.status = 'resolved' then 'open' else kbx.work_items.status end,
retry_action_key = excluded.retry_action_key,
allow_manual_resolution = excluded.allow_manual_resolution,
context = excluded.context,
occurred_at = excluded.occurred_at,
due_at = excluded.due_at,
resolved_at = null,
resolution_reason = null,
version = kbx.work_items.version + 1,
updated_at = now()
where excluded.source_version is null
or kbx.work_items.source_version is null
or excluded.source_version > kbx.work_items.source_version
returning id;
""";
var args = new {
Id = id,
item.TenantId, item.SourceModule, item.SourceType, item.SourceId, item.ReferenceNo, item.SourceScreenId, item.SourceVersion,
item.Code, item.Title, item.Detail, item.Severity, item.RetryActionKey, item.AllowManualResolution,
Context = JsonSerializer.Serialize(item.Context ?? new { }), item.OccurredAt, item.DueAt
};
var changedId = await connection.ExecuteScalarAsync<Guid?>(new CommandDefinition(sql, args, cancellationToken: ct));
if (changedId is not null) return changedId.Value;
return await connection.ExecuteScalarAsync<Guid>(new CommandDefinition("""
select id from kbx.work_items
where tenant_id=@TenantId and source_module=@SourceModule and source_type=@SourceType
and source_id=@SourceId and code=@Code;
""", args, cancellationToken: ct));
}
public async Task ResolveBySourceAsync(string tenantId, string sourceModule, string sourceType, string sourceId, string code, string reason, long? sourceVersion, CancellationToken ct)
{
await using var connection = await dataSource.OpenConnectionAsync(ct);
await connection.ExecuteAsync(new CommandDefinition("""
update kbx.work_items
set status = 'resolved', resolved_at = now(), resolution_reason = @Reason,
source_version = coalesce(@SourceVersion, source_version),
version = version + 1, updated_at = now()
where tenant_id = @TenantId and source_module = @SourceModule and source_type = @SourceType
and source_id = @SourceId and code = @Code and status <> 'resolved'
and (@SourceVersion is null or source_version is null or @SourceVersion >= source_version);
""", new { TenantId = tenantId, SourceModule = sourceModule, SourceType = sourceType, SourceId = sourceId, Code = code, Reason = reason, SourceVersion = sourceVersion }, cancellationToken: ct));
}
}