feat: advance legacy UI behavior parity
This commit is contained in:
@@ -66,7 +66,10 @@ public sealed record LegacyCutViewRow(
|
||||
bool IsSelected,
|
||||
bool IsFocused = false);
|
||||
|
||||
public sealed record LegacyDialogState(string Message);
|
||||
public sealed record LegacyDialogState(
|
||||
string Message,
|
||||
string Caption = "MmoneyCoder",
|
||||
bool IsConfirmation = false);
|
||||
|
||||
public sealed record LegacyOperatorCommandResult(
|
||||
long Sequence,
|
||||
@@ -196,6 +199,8 @@ public sealed class LegacyOperatorController
|
||||
private string _statusMessage = string.Empty;
|
||||
private LegacyOperatorStatusKind _statusKind = LegacyOperatorStatusKind.Neutral;
|
||||
private LegacyDialogState? _dialog;
|
||||
private PendingNativeConfirmation? _pendingNativeConfirmation;
|
||||
private bool _closeOperatorCatalogAfterDialog;
|
||||
private LegacyOperatorTabId _activeTab = LegacyOperatorTabId.Overseas;
|
||||
private LegacyMovingAverageSelection _movingAverages = new(true, true);
|
||||
private LegacyThemeWorkflowSnapshot? _themeSnapshot;
|
||||
@@ -231,7 +236,7 @@ public sealed class LegacyOperatorController
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_industryWorkflow = industryWorkflow;
|
||||
_themeWorkflow = themeWorkflow;
|
||||
_themeSnapshot = themeWorkflow?.CreateInitial();
|
||||
_themeSnapshot = themeWorkflow?.CreateInitial(CurrentThemeNxtSession());
|
||||
_expertWorkflow = expertWorkflow;
|
||||
_tradingHaltWorkflow = tradingHaltWorkflow;
|
||||
_comparisonWorkflow = comparisonWorkflow;
|
||||
@@ -640,7 +645,7 @@ public sealed class LegacyOperatorController
|
||||
var loaded = await _themeWorkflow.SearchAsync(
|
||||
_themeSnapshot,
|
||||
string.Empty,
|
||||
MMoneyCoderSharp.Data.ThemeSession.PreMarket,
|
||||
CurrentThemeNxtSession(),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
if (!IsCurrentTabActivation(tabId, activationRequest))
|
||||
{
|
||||
@@ -791,12 +796,6 @@ public sealed class LegacyOperatorController
|
||||
await _operatorCatalogWorkflow.OpenAsync(
|
||||
LegacyOperatorCatalogEntity.Theme,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
if (_operatorCatalogWorkflow.Current.CanBeginCreate)
|
||||
{
|
||||
await _operatorCatalogWorkflow.BeginCreateThemeAsync(
|
||||
MMoneyCoderSharp.Data.ThemeMarket.Krx,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
@@ -824,18 +823,32 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> DeleteSelectedThemeCatalogAsync(
|
||||
public Task<LegacyOperatorSnapshot> DeleteSelectedThemeCatalogAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await EditSelectedThemeCatalogAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (_operatorCatalogWorkflow?.Current.CanDelete == true)
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
return Task.FromResult(ReportError("ThemeA 관리 기능이 구성되지 않았습니다."));
|
||||
}
|
||||
|
||||
return CreateSnapshot();
|
||||
var identity = _themeSnapshot?.SelectedIdentity;
|
||||
if (identity is null)
|
||||
{
|
||||
return Task.FromResult(ReportError("삭제할 테마를 먼저 선택하세요."));
|
||||
}
|
||||
|
||||
if (_dialog is not null)
|
||||
{
|
||||
return Task.FromResult(CreateSnapshot());
|
||||
}
|
||||
|
||||
_pendingNativeConfirmation = PendingNativeConfirmation.ForTheme(identity);
|
||||
_dialog = new LegacyDialogState(
|
||||
"선택한 테마를 삭제하겠습니까?",
|
||||
IsConfirmation: true);
|
||||
Touch();
|
||||
return Task.FromResult(CreateSnapshot());
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> BeginExpertCatalogFromUc6Async(
|
||||
@@ -884,18 +897,35 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> DeleteSelectedExpertCatalogAsync(
|
||||
public Task<LegacyOperatorSnapshot> DeleteSelectedExpertCatalogAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await EditSelectedExpertCatalogAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (_operatorCatalogWorkflow?.Current.CanDelete == true)
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
return Task.FromResult(ReportError("EList 관리 기능이 구성되지 않았습니다."));
|
||||
}
|
||||
|
||||
return CreateSnapshot();
|
||||
var selected = _expertWorkflow?.Current.SelectedExpert;
|
||||
if (selected is null)
|
||||
{
|
||||
return Task.FromResult(ReportError("삭제할 전문가를 먼저 선택하세요."));
|
||||
}
|
||||
|
||||
if (_dialog is not null)
|
||||
{
|
||||
return Task.FromResult(CreateSnapshot());
|
||||
}
|
||||
|
||||
_pendingNativeConfirmation = PendingNativeConfirmation.ForExpert(
|
||||
new MMoneyCoderSharp.Data.ExpertSelectionIdentity(
|
||||
selected.ExpertCode,
|
||||
selected.ExpertName));
|
||||
_dialog = new LegacyDialogState(
|
||||
"선택한 전문가를 삭제하겠습니까?",
|
||||
IsConfirmation: true);
|
||||
Touch();
|
||||
return Task.FromResult(CreateSnapshot());
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot CloseOperatorCatalog()
|
||||
@@ -910,6 +940,43 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> CloseOperatorCatalogAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
var closing = _operatorCatalogWorkflow.Current;
|
||||
_operatorCatalogWorkflow.Close();
|
||||
_statusMessage = string.Empty;
|
||||
_statusKind = LegacyOperatorStatusKind.Neutral;
|
||||
if (closing.IsOpen &&
|
||||
closing.Entity == LegacyOperatorCatalogEntity.Expert &&
|
||||
closing.Mode == LegacyOperatorCatalogMode.Create &&
|
||||
_expertWorkflow is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _expertWorkflow.SearchAsync(string.Empty, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_statusMessage = "전문가 목록을 새로 읽지 못했습니다. 다시 조회하세요.";
|
||||
_statusKind = LegacyOperatorStatusKind.Warning;
|
||||
}
|
||||
}
|
||||
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> SearchOperatorCatalogAsync(
|
||||
string query,
|
||||
MMoneyCoderSharp.Data.ThemeSession nxtSession,
|
||||
@@ -1058,7 +1125,7 @@ public sealed class LegacyOperatorController
|
||||
|
||||
public LegacyOperatorSnapshot SetOperatorCatalogDraftBuyAmount(
|
||||
string rowId,
|
||||
decimal buyAmount)
|
||||
decimal? buyAmount)
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
@@ -1107,6 +1174,61 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot SelectOperatorCatalogDraftRow(string rowId)
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
|
||||
}
|
||||
|
||||
_operatorCatalogWorkflow.SelectDraftRow(rowId);
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot RemoveActiveOperatorCatalogDraftRow()
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
|
||||
}
|
||||
|
||||
_operatorCatalogWorkflow.RemoveActiveDraftRow();
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot ClearOperatorCatalogDraftRows()
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
return ReportError("ThemeA 관리 기능이 구성되지 않았습니다.");
|
||||
}
|
||||
|
||||
var catalog = _operatorCatalogWorkflow.Current;
|
||||
if (!catalog.IsOpen || catalog.Entity != LegacyOperatorCatalogEntity.Theme ||
|
||||
catalog.Mode is not (LegacyOperatorCatalogMode.Create or
|
||||
LegacyOperatorCatalogMode.Edit))
|
||||
{
|
||||
return ReportError("ThemeA 편집 목록이 열려 있지 않습니다.");
|
||||
}
|
||||
|
||||
if (catalog.DraftRows.Count == 0 || _dialog is not null)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
_pendingNativeConfirmation = PendingNativeConfirmation.ForThemeDraftClear(
|
||||
catalog.Revision);
|
||||
_dialog = new LegacyDialogState(
|
||||
"모두 삭제하겠습니까?",
|
||||
IsConfirmation: true);
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> CheckOperatorCatalogThemeNameAsync(
|
||||
string editorName,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -1120,6 +1242,12 @@ public sealed class LegacyOperatorController
|
||||
editorName,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
ApplyOperatorCatalogStatus();
|
||||
var availability = _operatorCatalogWorkflow.Current.ThemeNameAvailability;
|
||||
if (availability is LegacyThemeNameAvailability.Available or
|
||||
LegacyThemeNameAvailability.Duplicate)
|
||||
{
|
||||
_dialog = new LegacyDialogState(_operatorCatalogWorkflow.Current.StatusMessage);
|
||||
}
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
@@ -1133,23 +1261,95 @@ public sealed class LegacyOperatorController
|
||||
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
|
||||
}
|
||||
|
||||
var before = _operatorCatalogWorkflow.Current;
|
||||
var selectedExpertId = before.Entity == LegacyOperatorCatalogEntity.Expert &&
|
||||
before.Mode == LegacyOperatorCatalogMode.Edit
|
||||
? _expertWorkflow?.Current.SelectedExpert?.SelectionId
|
||||
: null;
|
||||
if (before.Entity == LegacyOperatorCatalogEntity.Expert &&
|
||||
before.Mode == LegacyOperatorCatalogMode.Create &&
|
||||
string.IsNullOrWhiteSpace(editorName))
|
||||
{
|
||||
_dialog = new LegacyDialogState("이름을 입력하세요");
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
await _operatorCatalogWorkflow.SaveAsync(editorName, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
var after = _operatorCatalogWorkflow.Current;
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> DeleteOperatorCatalogAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
if (before.Entity == LegacyOperatorCatalogEntity.Theme)
|
||||
{
|
||||
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
|
||||
if (after.Mode == LegacyOperatorCatalogMode.List)
|
||||
{
|
||||
_operatorCatalogWorkflow.Close();
|
||||
_statusMessage = string.Empty;
|
||||
_statusKind = LegacyOperatorStatusKind.Neutral;
|
||||
}
|
||||
else if (string.Equals(
|
||||
after.StatusMessage,
|
||||
LegacyOperatorCatalogWorkflowController.ThemeNameDuplicateMessage,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
_dialog = new LegacyDialogState(
|
||||
LegacyOperatorCatalogWorkflowController.ThemeNameDuplicateMessage);
|
||||
}
|
||||
}
|
||||
else if (after.Mode == LegacyOperatorCatalogMode.List)
|
||||
{
|
||||
var refreshFailed = false;
|
||||
if (before.Mode == LegacyOperatorCatalogMode.Create)
|
||||
{
|
||||
_operatorCatalogWorkflow.Close();
|
||||
try
|
||||
{
|
||||
if (_expertWorkflow is not null)
|
||||
{
|
||||
await _expertWorkflow.SearchAsync(string.Empty, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The mutation receipt is already known-committed. A UC6 list refresh
|
||||
// failure must not be reclassified as an unknown DB-write outcome.
|
||||
_statusMessage = "전문가 목록을 새로 읽지 못했습니다. 다시 조회하세요.";
|
||||
_statusKind = LegacyOperatorStatusKind.Warning;
|
||||
refreshFailed = true;
|
||||
}
|
||||
}
|
||||
else if (before.Mode == LegacyOperatorCatalogMode.Edit)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_expertWorkflow is not null && selectedExpertId is not null)
|
||||
{
|
||||
await _expertWorkflow.SelectExpertAsync(
|
||||
selectedExpertId,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The expert transaction is known-committed. Preview refresh failure
|
||||
// is a read failure and must never turn into an unknown write outcome.
|
||||
refreshFailed = true;
|
||||
_statusMessage = "저장된 추천 종목을 다시 읽지 못했습니다. 전문가를 다시 선택하세요.";
|
||||
_statusKind = LegacyOperatorStatusKind.Warning;
|
||||
}
|
||||
_dialog = new LegacyDialogState("저장되었습니다");
|
||||
_closeOperatorCatalogAfterDialog = true;
|
||||
}
|
||||
|
||||
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
ApplyOperatorCatalogStatus();
|
||||
if (!refreshFailed)
|
||||
{
|
||||
_statusMessage = string.Empty;
|
||||
_statusKind = LegacyOperatorStatusKind.Neutral;
|
||||
}
|
||||
}
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
@@ -2085,6 +2285,32 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot SetIndustryComparisonText(
|
||||
LegacyIndustryComparisonSlot slot,
|
||||
string text)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(text);
|
||||
if (!IsActiveIndustryReady())
|
||||
{
|
||||
return ReportError("업종 목록을 먼저 불러오세요.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_industryWorkflow!.SetComparisonText(slot, text);
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return ReportError("등록되지 않은 업종 비교 입력칸입니다.");
|
||||
}
|
||||
catch (LegacyIndustryWorkflowException exception)
|
||||
{
|
||||
return ReportError(exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot ActivateIndustryAction(string actionId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(actionId);
|
||||
@@ -2117,46 +2343,13 @@ public sealed class LegacyOperatorController
|
||||
return ReportError("업종 목록을 먼저 불러오세요.");
|
||||
}
|
||||
|
||||
var snapshot = _industryWorkflow!.Current;
|
||||
var sections = snapshot.Actions
|
||||
.Select(static action => action.Section)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length)
|
||||
{
|
||||
return ReportError("등록되지 않은 업종 컷 섹션입니다.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var section = sections[sectionIndex];
|
||||
// Materialize every direct child before mutating the playlist. This keeps
|
||||
// the original parent double-click all-or-nothing when one child lacks a
|
||||
// required industry selection.
|
||||
var drafts = snapshot.Actions
|
||||
.Where(action => string.Equals(
|
||||
action.Section,
|
||||
section,
|
||||
StringComparison.Ordinal))
|
||||
.Select(action => _industryWorkflow.MaterializePlaylistDraft(action.ActionId))
|
||||
.ToArray();
|
||||
foreach (var draft in drafts)
|
||||
{
|
||||
AppendIndustryRow(draft, snapshot.Industries.Count);
|
||||
}
|
||||
|
||||
_statusMessage = $"{section} 항목 {drafts.Length}개를 추가했습니다.";
|
||||
_statusKind = LegacyOperatorStatusKind.Information;
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
catch (LegacyIndustryWorkflowException exception)
|
||||
{
|
||||
_dialog = new LegacyDialogState(
|
||||
$"섹션의 모든 항목을 검증하지 못해 아무것도 추가하지 않았습니다. {exception.Message}");
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
// MainForm constructs both industry UC1 instances with UC3 == null. The
|
||||
// original root double-click path dereferences UC3 before its 22-child loop,
|
||||
// then its outer catch silently consumes the exception. Preserve that
|
||||
// observable no-op; only child double-clicks add industry playlist rows.
|
||||
return sectionIndex == 0
|
||||
? CreateSnapshot()
|
||||
: ReportError("등록되지 않은 업종 컷 섹션입니다.");
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot SelectOverseasFixedIndex(string targetId)
|
||||
@@ -2484,8 +2677,17 @@ public sealed class LegacyOperatorController
|
||||
|
||||
if (all)
|
||||
{
|
||||
await _comparisonWorkflow.DeleteAllPairsAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (_dialog is not null)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
_pendingNativeConfirmation = PendingNativeConfirmation.ForComparisonDeleteAll(
|
||||
_comparisonWorkflow.Current.Revision);
|
||||
_dialog = new LegacyDialogState(
|
||||
"모두 삭제하겠습니까?",
|
||||
"MmoneyCoder",
|
||||
IsConfirmation: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2598,10 +2800,23 @@ public sealed class LegacyOperatorController
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> SearchThemesAsync(
|
||||
public Task<LegacyOperatorSnapshot> SearchThemesAsync(
|
||||
string query,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
SearchThemesCoreAsync(query, CurrentThemeNxtSession(), cancellationToken);
|
||||
|
||||
// Compatibility overload for native callers. UC4 does not allow a WebView
|
||||
// payload to select the NXT session; TimeProvider remains authoritative.
|
||||
public Task<LegacyOperatorSnapshot> SearchThemesAsync(
|
||||
string query,
|
||||
MMoneyCoderSharp.Data.ThemeSession ignoredNxtSession,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
SearchThemesAsync(query, cancellationToken);
|
||||
|
||||
private async Task<LegacyOperatorSnapshot> SearchThemesCoreAsync(
|
||||
string query,
|
||||
MMoneyCoderSharp.Data.ThemeSession nxtSession,
|
||||
CancellationToken cancellationToken = default)
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
if (!IsThemeActive() || _themeWorkflow is null || _themeSnapshot is null)
|
||||
@@ -3422,11 +3637,63 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> ConfirmDialogAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var pending = _pendingNativeConfirmation;
|
||||
if (_dialog?.IsConfirmation != true || pending is null)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
// Consume the confirmation before crossing a database-write boundary. A
|
||||
// timeout or unknown outcome must never leave a replayable Yes action.
|
||||
_pendingNativeConfirmation = null;
|
||||
_dialog = null;
|
||||
|
||||
return pending.Kind switch
|
||||
{
|
||||
PendingNativeConfirmationKind.DeleteTheme =>
|
||||
await ExecuteConfirmedThemeDeleteAsync(
|
||||
pending.ThemeIdentity!,
|
||||
cancellationToken).ConfigureAwait(false),
|
||||
PendingNativeConfirmationKind.DeleteExpert =>
|
||||
await ExecuteConfirmedExpertDeleteAsync(
|
||||
pending.ExpertIdentity!,
|
||||
cancellationToken).ConfigureAwait(false),
|
||||
PendingNativeConfirmationKind.ClearThemeDraft =>
|
||||
ExecuteConfirmedThemeDraftClear(pending.CatalogRevision),
|
||||
PendingNativeConfirmationKind.DeleteAllComparisonPairs =>
|
||||
await ExecuteConfirmedComparisonDeleteAllAsync(
|
||||
pending.ComparisonRevision,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false),
|
||||
_ => throw new InvalidOperationException("Unknown native confirmation kind.")
|
||||
};
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot CancelDialog()
|
||||
{
|
||||
if (_dialog?.IsConfirmation == true)
|
||||
{
|
||||
_pendingNativeConfirmation = null;
|
||||
_dialog = null;
|
||||
Touch();
|
||||
}
|
||||
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot DismissDialog()
|
||||
{
|
||||
if (_dialog is not null)
|
||||
if (_dialog is not null && !_dialog.IsConfirmation)
|
||||
{
|
||||
_dialog = null;
|
||||
if (_closeOperatorCatalogAfterDialog)
|
||||
{
|
||||
_operatorCatalogWorkflow?.Close();
|
||||
_closeOperatorCatalogAfterDialog = false;
|
||||
}
|
||||
ContinuePendingDrop();
|
||||
Touch();
|
||||
}
|
||||
@@ -3434,6 +3701,121 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
private async Task<LegacyOperatorSnapshot> ExecuteConfirmedThemeDeleteAsync(
|
||||
MMoneyCoderSharp.Data.ThemeSelectionIdentity identity,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null || _themeWorkflow is null ||
|
||||
_themeSnapshot?.SelectedIdentity != identity)
|
||||
{
|
||||
return ReportError("삭제 대상 테마가 변경되어 DB 삭제를 시작하지 않았습니다.");
|
||||
}
|
||||
|
||||
await _operatorCatalogWorkflow.OpenThemeForEditAsync(identity, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (!_operatorCatalogWorkflow.Current.CanDelete)
|
||||
{
|
||||
ApplyOperatorCatalogStatus();
|
||||
_operatorCatalogWorkflow.Close();
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (_operatorCatalogWorkflow.Current.Mode == LegacyOperatorCatalogMode.List)
|
||||
{
|
||||
_themeSnapshot = _themeWorkflow.RemoveSelectedResult(_themeSnapshot);
|
||||
_operatorCatalogWorkflow.Close();
|
||||
_statusMessage = string.Empty;
|
||||
_statusKind = LegacyOperatorStatusKind.Neutral;
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyOperatorCatalogStatus();
|
||||
}
|
||||
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
private async Task<LegacyOperatorSnapshot> ExecuteConfirmedExpertDeleteAsync(
|
||||
MMoneyCoderSharp.Data.ExpertSelectionIdentity identity,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var selected = _expertWorkflow?.Current.SelectedExpert;
|
||||
if (_operatorCatalogWorkflow is null || selected is null ||
|
||||
!string.Equals(selected.ExpertCode, identity.ExpertCode, StringComparison.Ordinal) ||
|
||||
!string.Equals(selected.ExpertName, identity.ExpertName, StringComparison.Ordinal))
|
||||
{
|
||||
return ReportError("삭제 대상 전문가가 변경되어 DB 삭제를 시작하지 않았습니다.");
|
||||
}
|
||||
|
||||
await _operatorCatalogWorkflow.OpenExpertForEditAsync(identity, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (!_operatorCatalogWorkflow.Current.CanDelete)
|
||||
{
|
||||
ApplyOperatorCatalogStatus();
|
||||
_operatorCatalogWorkflow.Close();
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (_operatorCatalogWorkflow.Current.Mode == LegacyOperatorCatalogMode.List)
|
||||
{
|
||||
_expertWorkflow?.RemoveSelectedResultPreservingPreview();
|
||||
_operatorCatalogWorkflow.Close();
|
||||
_statusMessage = string.Empty;
|
||||
_statusKind = LegacyOperatorStatusKind.Neutral;
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyOperatorCatalogStatus();
|
||||
}
|
||||
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
private LegacyOperatorSnapshot ExecuteConfirmedThemeDraftClear(long catalogRevision)
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
return ReportError("ThemeA 관리 기능이 구성되지 않았습니다.");
|
||||
}
|
||||
|
||||
var catalog = _operatorCatalogWorkflow.Current;
|
||||
if (catalog.Revision != catalogRevision || !catalog.IsOpen ||
|
||||
catalog.Entity != LegacyOperatorCatalogEntity.Theme ||
|
||||
catalog.Mode is not (LegacyOperatorCatalogMode.Create or
|
||||
LegacyOperatorCatalogMode.Edit))
|
||||
{
|
||||
return ReportError("ThemeA 편집 목록이 변경되어 전체 삭제를 실행하지 않았습니다.");
|
||||
}
|
||||
|
||||
_operatorCatalogWorkflow.ClearThemeDraftRows();
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
private async Task<LegacyOperatorSnapshot> ExecuteConfirmedComparisonDeleteAllAsync(
|
||||
long comparisonRevision,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!IsComparisonActive() || _comparisonWorkflow is null ||
|
||||
_comparisonWorkflow.Current.Revision != comparisonRevision)
|
||||
{
|
||||
return ReportError(
|
||||
"삭제 대상 종목 비교 목록이 변경되어 전체 삭제를 실행하지 않았습니다.");
|
||||
}
|
||||
|
||||
await _comparisonWorkflow.DeleteAllPairsAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot ReportError(string message)
|
||||
{
|
||||
_statusMessage = message ?? string.Empty;
|
||||
@@ -4055,6 +4437,11 @@ public sealed class LegacyOperatorController
|
||||
|
||||
private bool IsThemeActive() => _activeTab == LegacyOperatorTabId.Theme;
|
||||
|
||||
private MMoneyCoderSharp.Data.ThemeSession CurrentThemeNxtSession() =>
|
||||
_timeProvider.GetLocalNow().Hour <= 12
|
||||
? MMoneyCoderSharp.Data.ThemeSession.PreMarket
|
||||
: MMoneyCoderSharp.Data.ThemeSession.AfterMarket;
|
||||
|
||||
private void BeginTabActivation(LegacyOperatorTabId tabId)
|
||||
{
|
||||
if (tabId == LegacyOperatorTabId.KospiIndustry)
|
||||
@@ -4077,6 +4464,12 @@ public sealed class LegacyOperatorController
|
||||
else if (tabId == LegacyOperatorTabId.Theme)
|
||||
{
|
||||
_themeActivationListLoaded = false;
|
||||
if (_themeWorkflow is not null && _themeSnapshot is not null)
|
||||
{
|
||||
_themeSnapshot = _themeWorkflow.ResetForTabActivation(
|
||||
_themeSnapshot,
|
||||
CurrentThemeNxtSession());
|
||||
}
|
||||
}
|
||||
else if (tabId == LegacyOperatorTabId.OverseasStocks)
|
||||
{
|
||||
@@ -4382,15 +4775,14 @@ public sealed class LegacyOperatorController
|
||||
|
||||
private void AppendOverseasRow(LegacyOverseasPlaylistDraft draft)
|
||||
{
|
||||
var isCandle = string.Equals(draft.BuilderKey, "s8010", StringComparison.Ordinal);
|
||||
AppendPlaylistRow(new LegacyOperatorPlaylistRow(
|
||||
$"legacy-overseas-{++_playlistSequence:D8}",
|
||||
draft.IsEnabled,
|
||||
draft.Selection.GroupCode,
|
||||
draft.Title,
|
||||
isCandle ? "캔들그래프" : draft.Selection.GraphicType,
|
||||
draft.Selection.Subtype,
|
||||
draft.InitialPageText,
|
||||
draft.VisibleFields.Market,
|
||||
draft.VisibleFields.StockName,
|
||||
draft.VisibleFields.GraphicType,
|
||||
draft.VisibleFields.Subtype,
|
||||
draft.VisibleFields.PageText,
|
||||
draft.Selection.DataCode,
|
||||
draft.Detail,
|
||||
draft.FadeDuration,
|
||||
@@ -4398,23 +4790,20 @@ public sealed class LegacyOperatorController
|
||||
PlayoutSelection: new MBN_STOCK_WEBVIEW.Core.Playout.Scenes.LegacySceneSelection(
|
||||
draft.Selection.GroupCode,
|
||||
draft.Selection.Subject,
|
||||
isCandle ? string.Empty : draft.Selection.GraphicType,
|
||||
draft.Selection.GraphicType,
|
||||
draft.Selection.Subtype,
|
||||
draft.Selection.DataCode)));
|
||||
}
|
||||
|
||||
private void AppendThemeRow(LegacyThemePlaylistDraft draft)
|
||||
{
|
||||
var separator = draft.Detail.IndexOf(" · ", StringComparison.Ordinal);
|
||||
var graphicType = separator > 0 ? draft.Detail[..separator] : draft.Detail;
|
||||
var subtype = separator > 0 ? draft.Detail[(separator + 3)..] : string.Empty;
|
||||
AppendPlaylistRow(new LegacyOperatorPlaylistRow(
|
||||
$"legacy-theme-{++_playlistSequence:D8}",
|
||||
true,
|
||||
draft.Selection.GroupCode == "PAGED_THEME" ? "테마" : "테마_NXT",
|
||||
draft.Title,
|
||||
graphicType,
|
||||
subtype,
|
||||
draft.PlaylistGraphicType,
|
||||
draft.PlaylistSubtype,
|
||||
draft.PagePlan.InitialPageText,
|
||||
draft.Selection.DataCode,
|
||||
draft.Detail,
|
||||
@@ -4881,5 +5270,55 @@ public sealed class LegacyOperatorController
|
||||
}
|
||||
}
|
||||
|
||||
private enum PendingNativeConfirmationKind
|
||||
{
|
||||
DeleteTheme,
|
||||
DeleteExpert,
|
||||
ClearThemeDraft,
|
||||
DeleteAllComparisonPairs
|
||||
}
|
||||
|
||||
private sealed record PendingNativeConfirmation(
|
||||
PendingNativeConfirmationKind Kind,
|
||||
MMoneyCoderSharp.Data.ThemeSelectionIdentity? ThemeIdentity,
|
||||
MMoneyCoderSharp.Data.ExpertSelectionIdentity? ExpertIdentity,
|
||||
long CatalogRevision,
|
||||
long ComparisonRevision)
|
||||
{
|
||||
internal static PendingNativeConfirmation ForTheme(
|
||||
MMoneyCoderSharp.Data.ThemeSelectionIdentity identity) =>
|
||||
new(
|
||||
PendingNativeConfirmationKind.DeleteTheme,
|
||||
identity,
|
||||
null,
|
||||
0,
|
||||
0);
|
||||
|
||||
internal static PendingNativeConfirmation ForExpert(
|
||||
MMoneyCoderSharp.Data.ExpertSelectionIdentity identity) =>
|
||||
new(
|
||||
PendingNativeConfirmationKind.DeleteExpert,
|
||||
null,
|
||||
identity,
|
||||
0,
|
||||
0);
|
||||
|
||||
internal static PendingNativeConfirmation ForThemeDraftClear(long revision) =>
|
||||
new(
|
||||
PendingNativeConfirmationKind.ClearThemeDraft,
|
||||
null,
|
||||
null,
|
||||
revision,
|
||||
0);
|
||||
|
||||
internal static PendingNativeConfirmation ForComparisonDeleteAll(long revision) =>
|
||||
new(
|
||||
PendingNativeConfirmationKind.DeleteAllComparisonPairs,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
revision);
|
||||
}
|
||||
|
||||
private void Touch() => _revision++;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user