Migrate remaining legacy operator workflows

This commit is contained in:
2026-07-12 05:39:27 +09:00
parent ffb8f43c19
commit a01836a2d7
132 changed files with 20566 additions and 720 deletions

View File

@@ -17,6 +17,7 @@ public sealed partial class MainWindow
new(StringComparer.Ordinal);
private IThemeCatalogPersistenceService? _themeCatalogPersistenceService;
private IExpertCatalogPersistenceService? _expertCatalogPersistenceService;
private IStockMasterIdentityValidationService? _stockMasterIdentityValidationService;
private IOperatorCatalogSchemaValidationService? _operatorCatalogSchemaValidationService;
private CancellationTokenSource? _operatorCatalogWriteCancellation;
private string? _operatorCatalogInitializationError;
@@ -56,6 +57,8 @@ public sealed partial class MainWindow
_expertCatalogPersistenceService = new LegacyExpertCatalogPersistenceService(
runtime.Executor,
mutationExecutor);
_stockMasterIdentityValidationService =
new LegacyStockMasterIdentityValidationService(runtime.Executor);
_operatorCatalogSchemaValidationService =
new LegacyOperatorCatalogSchemaValidationService(runtime.Executor);
}
@@ -63,6 +66,7 @@ public sealed partial class MainWindow
{
_themeCatalogPersistenceService = null;
_expertCatalogPersistenceService = null;
_stockMasterIdentityValidationService = null;
_operatorCatalogSchemaValidationService = null;
_operatorCatalogInitializationError =
"The theme/expert database boundary could not be initialized.";
@@ -376,12 +380,19 @@ public sealed partial class MainWindow
InitializeOperatorCatalogRuntime();
var service = _themeCatalogPersistenceService;
var identityValidation = _stockMasterIdentityValidationService;
await ExecuteOperatorCatalogWriteAsync(
requestId,
"theme",
"create",
definition.ThemeCode,
service?.CanMutate == true,
service?.CanMutate == true && identityValidation is not null,
async cancellationToken =>
{
_ = await identityValidation!.ValidateThemeItemsAsync(
definition.Items,
cancellationToken);
},
cancellationToken => service!.CreateAsync(definition, cancellationToken));
}
@@ -403,12 +414,19 @@ public sealed partial class MainWindow
InitializeOperatorCatalogRuntime();
var service = _themeCatalogPersistenceService;
var identityValidation = _stockMasterIdentityValidationService;
await ExecuteOperatorCatalogWriteAsync(
requestId,
"theme",
"replace",
expectedIdentity.ThemeCode,
service?.CanMutate == true,
service?.CanMutate == true && identityValidation is not null,
async cancellationToken =>
{
_ = await identityValidation!.ValidateThemeItemsAsync(
items,
cancellationToken);
},
cancellationToken => service!.ReplaceAsync(
expectedIdentity,
newTitle,
@@ -438,6 +456,7 @@ public sealed partial class MainWindow
"delete",
identity.ThemeCode,
service?.CanMutate == true,
validationBody: null,
cancellationToken => service!.DeleteAsync(identity, cancellationToken));
}
@@ -457,12 +476,19 @@ public sealed partial class MainWindow
InitializeOperatorCatalogRuntime();
var service = _expertCatalogPersistenceService;
var identityValidation = _stockMasterIdentityValidationService;
await ExecuteOperatorCatalogWriteAsync(
requestId,
"expert",
"create",
definition.Identity.ExpertCode,
service?.CanMutate == true,
service?.CanMutate == true && identityValidation is not null,
async cancellationToken =>
{
_ = await identityValidation!.ValidateExpertRecommendationsAsync(
definition.Recommendations,
cancellationToken);
},
cancellationToken => service!.CreateAsync(definition, cancellationToken));
}
@@ -484,12 +510,19 @@ public sealed partial class MainWindow
InitializeOperatorCatalogRuntime();
var service = _expertCatalogPersistenceService;
var identityValidation = _stockMasterIdentityValidationService;
await ExecuteOperatorCatalogWriteAsync(
requestId,
"expert",
"replace",
expectedIdentity.ExpertCode,
service?.CanMutate == true,
service?.CanMutate == true && identityValidation is not null,
async cancellationToken =>
{
_ = await identityValidation!.ValidateExpertRecommendationsAsync(
recommendations,
cancellationToken);
},
cancellationToken => service!.ReplaceAsync(
expectedIdentity,
newName,
@@ -519,6 +552,7 @@ public sealed partial class MainWindow
"delete",
identity.ExpertCode,
service?.CanMutate == true,
validationBody: null,
cancellationToken => service!.DeleteAsync(identity, cancellationToken));
}
@@ -672,6 +706,7 @@ public sealed partial class MainWindow
string operation,
string identityCode,
bool canMutate,
Func<CancellationToken, Task>? validationBody,
Func<CancellationToken, Task<OperatorCatalogMutationReceipt>> operationBody)
{
if (!canMutate)
@@ -764,6 +799,30 @@ public sealed partial class MainWindow
return;
}
if (validationBody is not null)
{
await validationBody(requestToken);
requestToken.ThrowIfCancellationRequested();
if (!CanStartOperatorCatalogWrite())
{
PostOperatorCatalogPlayoutActive(
requestId,
entity,
operation,
identityCode);
return;
}
if (browserGeneration != Volatile.Read(ref _operatorCatalogBrowserGeneration) ||
!ReferenceEquals(
Volatile.Read(ref _operatorCatalogWriteCancellation),
requestCancellation))
{
requestCancellation.Cancel();
return;
}
}
mutationDispatched = true;
Interlocked.Exchange(ref _operatorCatalogWriteInFlight, 1);
var receipt = await operationBody(requestToken);
@@ -806,6 +865,31 @@ public sealed partial class MainWindow
outcomeUnknown: false,
requestCancellation);
}
catch (StockMasterIdentityDataException) when (!mutationDispatched)
{
PostOperatorCatalogWriteErrorIfCurrent(
requestId,
entity,
operation,
identityCode,
"INVALID_DATA",
"Every catalog stock must be selected from one current, unambiguous stock-master identity.",
outcomeUnknown: false,
requestCancellation);
}
catch (DatabaseOperationException) when (!mutationDispatched)
{
PostOperatorCatalogWriteErrorIfCurrent(
requestId,
entity,
operation,
identityCode,
"DATABASE_ERROR",
"The stock-master identity verification failed before the database transaction began.",
outcomeUnknown: false,
requestCancellation);
_ = PostDatabaseStatusAsync(_lifetimeCancellation.Token);
}
catch (OperatorCatalogMutationException exception)
{
if (exception.OutcomeUnknown)
@@ -1152,10 +1236,12 @@ public sealed partial class MainWindow
private bool CanMutateThemeCatalog() =>
_themeCatalogPersistenceService?.CanMutate == true &&
_stockMasterIdentityValidationService is not null &&
!IsOperatorCatalogWriteQuarantined();
private bool CanMutateExpertCatalog() =>
_expertCatalogPersistenceService?.CanMutate == true &&
_stockMasterIdentityValidationService is not null &&
!IsOperatorCatalogWriteQuarantined();
private bool IsOperatorCatalogWriteQuarantined() =>
@@ -1224,6 +1310,7 @@ public sealed partial class MainWindow
CancelRequest(Interlocked.Exchange(ref _operatorCatalogWriteCancellation, null));
_themeCatalogPersistenceService = null;
_expertCatalogPersistenceService = null;
_stockMasterIdentityValidationService = null;
_operatorCatalogSchemaValidationService = null;
}
@@ -1378,7 +1465,7 @@ public sealed partial class MainWindow
market == ThemeMarket.Krx && session != ThemeSession.NotApplicable ||
market == ThemeMarket.Nxt &&
session is not (ThemeSession.PreMarket or ThemeSession.AfterMarket) ||
!TryGetOperatorCatalogCode(element, "themeCode", 8, out var code) ||
!TryGetExistingThemeCatalogCode(element, "themeCode", out var code) ||
!TryGetOperatorCatalogText(element, "themeTitle", 128, out var title) ||
market == ThemeMarket.Nxt && title.Contains("(NXT)", StringComparison.OrdinalIgnoreCase))
{
@@ -1612,6 +1699,15 @@ public sealed partial class MainWindow
return value.Length == length && value.All(char.IsAsciiDigit);
}
private static bool TryGetExistingThemeCatalogCode(
JsonElement payload,
string propertyName,
out string value)
{
value = GetString(payload, propertyName) ?? string.Empty;
return value.Length is >= 3 and <= 8 && value.All(char.IsAsciiDigit);
}
private static bool TryGetOperatorCatalogText(
JsonElement payload,
string propertyName,