92 lines
4.4 KiB
C#
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));
|
|
}
|
|
}
|