This commit is contained in:
@@ -8,8 +8,8 @@
|
||||
Blazor → Service (서버) → DB
|
||||
|
||||
✅ 현재: API-First (클라이언트-서버 분리)
|
||||
Blazor (UI만) ← API (모든 로직) ← DB
|
||||
SignalR (변경 알림만)
|
||||
Blazor (UI만, 사용자 액션 후 API 재조회) ← API (모든 로직) ← DB
|
||||
Blazor 데이터 변경 자동 push/broadcast 금지
|
||||
```
|
||||
|
||||
### SOLID 기반 순차 마이그레이션 전략
|
||||
@@ -61,10 +61,10 @@ _refreshTokenExpirationMinutes = 10080;
|
||||
|
||||
**완료**: 2026-06-28 / 토큰 갱신 자동화 + 이중 토큰 패턴
|
||||
|
||||
#### Phase 6: SignalR 통합
|
||||
- [ ] NotificationHub (변경 알림만)
|
||||
- [ ] Blazor에서 구독
|
||||
- [ ] 알림 후 API로 데이터 검증
|
||||
#### Phase 6: Blazor 데이터 변경 SignalR 갱신 제거
|
||||
- [x] NotificationHub 제거
|
||||
- [x] 데이터 변경용 INotificationService 제거
|
||||
- [x] Program.cs의 별도 AddSignalR/MapHub 등록 제거
|
||||
|
||||
#### Phase 7: 순차적 마이그레이션 ✅
|
||||
- [x] Blog 페이지 → API 클라이언트
|
||||
@@ -136,11 +136,11 @@ _refreshTokenExpirationMinutes = 10080;
|
||||
- Status Color Chips (Error/Warning/Success)
|
||||
- Client 링크 (상세 페이지 연동)
|
||||
|
||||
### **Phase 6: SignalR 통합** ✅
|
||||
- NotificationHub (브로드캐스트만, 상태 관리 없음)
|
||||
- INotificationService (이벤트 기반)
|
||||
- 5개 알림 유형 (Inquiry, Client, Announcement, Filing, Status)
|
||||
- Program.cs SignalR 등록
|
||||
### **Phase 6: Lite Blazor 운영 원칙** ✅
|
||||
- Blazor에서 데이터 변경 시 SignalR publish/subscribe로 목록을 자동 갱신하지 않는다.
|
||||
- NotificationHub와 데이터 변경용 INotificationService는 제거된 상태를 유지한다.
|
||||
- Blazor Server의 기본 interactive 연결은 UI 구동에만 사용한다.
|
||||
- 공지사항, 문의, 고객, 신고 등 도메인 CRUD 기능은 그대로 유지하고, 변경 전파 방식만 API 재조회로 제한한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -160,11 +160,11 @@ Repositories (데이터 계층)
|
||||
PostgreSQL Database
|
||||
```
|
||||
|
||||
**Blazor Server SignalR**:
|
||||
- 자동 연결 (내장 Hub connection)
|
||||
- NotificationHub 클라이언트 그룹 (admins)
|
||||
- 이벤트 기반 메시지 (상태 관리 없음)
|
||||
- 클라이언트는 알림 후 API로 데이터 검증
|
||||
**Lite Blazor 데이터 갱신**:
|
||||
- Blazor Server 자동 연결은 컴포넌트 상호작용용 기본 회선으로만 사용한다.
|
||||
- 데이터 변경 알림용 별도 Hub, 그룹, broadcast, client subscription을 추가하지 않는다.
|
||||
- 저장/삭제/완료 같은 사용자 액션 이후 필요한 목록만 API로 다시 조회한다.
|
||||
- 공지사항, 문의, 고객, 신고 등 도메인 CRUD 기능은 그대로 유지한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -182,10 +182,10 @@ PostgreSQL Database
|
||||
- [x] Phase 7-4: CRM & 세무관리 (5개 API, 5개 Blazor) - **2026-06-28 완료**
|
||||
- [x] SOLID 원칙 전체 적용 (Single Responsibility, Dependency Inversion)
|
||||
|
||||
**실시간 알림 (Phase 6)**:
|
||||
- [x] NotificationHub 구현
|
||||
- [x] Event-driven 알림 시스템
|
||||
- [x] Scoped DI 등록
|
||||
**Lite Blazor / 데이터 갱신 (Phase 6)**:
|
||||
- [x] Blazor 데이터 변경 SignalR 자동 갱신 제거
|
||||
- [x] NotificationHub 제거
|
||||
- [x] 데이터 변경용 INotificationService 제거
|
||||
|
||||
**Blazor 페이지 & UI 고도화 (Phase 7-4)**:
|
||||
- [x] 5개 CRM/세무관리 Blazor 페이지
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<MudSelect T="int" @bind-Value="activityForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="true" Class="mb-4">
|
||||
@foreach (var client in clients)
|
||||
{
|
||||
<MudSelectItem Value="@client.Id">@client.CompanyName</MudSelectItem>
|
||||
<MudSelectItem Value="@client.Id">@GetClientDisplayName(client)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="string" @bind-Value="activityForm.ActivityType" Label="활동 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true">
|
||||
@@ -155,9 +155,9 @@
|
||||
try
|
||||
{
|
||||
activities = await ActivityClient.GetAllAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000);
|
||||
clients = clientItems.ToList();
|
||||
clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? "");
|
||||
clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -273,6 +273,12 @@
|
||||
activityForm = new();
|
||||
}
|
||||
|
||||
private static string GetClientDisplayName(Client client)
|
||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||
? client.CompanyName
|
||||
: !string.IsNullOrWhiteSpace(client.Name)
|
||||
? client.Name
|
||||
: $"Client #{client.Id}";
|
||||
private class ConsultingActivityForm
|
||||
{
|
||||
public int ClientId { get; set; }
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<MudSelect T="int?" @bind-Value="contractForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" RequiredError="고객을 선택하세요.">
|
||||
@foreach (var client in clients)
|
||||
{
|
||||
<MudSelectItem Value="@((int?)client.Id)">@(string.IsNullOrEmpty(client.CompanyName) ? client.Name : client.CompanyName)</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTextField T="string" @bind-Value="contractForm.ContractNumber" Label="계약번호" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
|
||||
@@ -165,9 +165,9 @@
|
||||
try
|
||||
{
|
||||
contracts = await ContractClient.GetAllAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000);
|
||||
clients = clientItems.ToList();
|
||||
clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? "");
|
||||
clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName);
|
||||
mrr = await ContractClient.GetMonthlyRecurringRevenueAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -253,6 +253,12 @@
|
||||
contractForm = new();
|
||||
}
|
||||
|
||||
private static string GetClientDisplayName(Client client)
|
||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||
? client.CompanyName
|
||||
: !string.IsNullOrWhiteSpace(client.Name)
|
||||
? client.Name
|
||||
: $"Client #{client.Id}";
|
||||
private class ContractForm
|
||||
{
|
||||
public int? ClientId { get; set; }
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<MudSelect T="int" @bind-Value="revenueForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="true" Class="mb-4">
|
||||
@foreach (var client in clients)
|
||||
{
|
||||
<MudSelectItem Value="@client.Id">@client.CompanyName</MudSelectItem>
|
||||
<MudSelectItem Value="@client.Id">@GetClientDisplayName(client)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTextField T="string" @bind-Value="revenueForm.InvoiceNumber" Label="청구번호" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true" />
|
||||
@@ -150,9 +150,9 @@
|
||||
try
|
||||
{
|
||||
revenues = await RevenueClient.GetAllAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000);
|
||||
clients = clientItems.ToList();
|
||||
clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? "");
|
||||
clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -252,6 +252,12 @@
|
||||
revenueForm = new();
|
||||
}
|
||||
|
||||
private static string GetClientDisplayName(Client client)
|
||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||
? client.CompanyName
|
||||
: !string.IsNullOrWhiteSpace(client.Name)
|
||||
? client.Name
|
||||
: $"Client #{client.Id}";
|
||||
private class RevenueForm
|
||||
{
|
||||
public int ClientId { get; set; }
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
RequiredError="고객을 선택하세요.">
|
||||
@foreach (var client in clients)
|
||||
{
|
||||
<MudSelectItem Value="@((int?)client.Id)">@(string.IsNullOrEmpty(client.CompanyName) ? client.Name : client.CompanyName)</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="string" @bind-Value="scheduleForm.FilingType" Label="신고 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true">
|
||||
@@ -182,9 +182,9 @@
|
||||
try
|
||||
{
|
||||
schedules = await TaxFilingClient.GetAllAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000);
|
||||
clients = clientItems.ToList();
|
||||
clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? "");
|
||||
clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -286,9 +286,15 @@
|
||||
scheduleForm = new();
|
||||
}
|
||||
|
||||
private static string GetClientDisplayName(Client client)
|
||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||
? client.CompanyName
|
||||
: !string.IsNullOrWhiteSpace(client.Name)
|
||||
? client.Name
|
||||
: $"Client #{client.Id}";
|
||||
private class TaxFilingScheduleForm
|
||||
{
|
||||
public int ClientId { get; set; }
|
||||
public int? ClientId { get; set; }
|
||||
public string FilingType { get; set; } = "";
|
||||
public DateTime? DueDate { get; set; }
|
||||
public int FilingYear { get; set; } = DateTime.Now.Year;
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var (items, _) = await ClientClient.GetPagedAsync(1, 20, search: value);
|
||||
var (items, _) = await ClientClient.GetPagedAsync(1, 100, search: value);
|
||||
return items;
|
||||
}
|
||||
catch
|
||||
@@ -110,6 +110,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetClientDisplayName(Client client)
|
||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||
? client.CompanyName
|
||||
: !string.IsNullOrWhiteSpace(client.Name)
|
||||
? client.Name
|
||||
: $"Client #{client.Id}";
|
||||
private async Task AddFiling()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -87,7 +87,7 @@ else
|
||||
<MudSelect T="int?" @bind-Value="profileForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" RequiredError="고객을 선택하세요.">
|
||||
@foreach (var client in clients)
|
||||
{
|
||||
<MudSelectItem Value="@((int?)client.Id)">@(string.IsNullOrEmpty(client.CompanyName) ? client.Name : client.CompanyName)</MudSelectItem>
|
||||
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="string" @bind-Value="profileForm.BusinessType" Label="사업 유형" Variant="Variant.Outlined" FullWidth="true" Class="mb-4" Required="true">
|
||||
@@ -150,9 +150,9 @@ else
|
||||
try
|
||||
{
|
||||
profiles = await TaxProfileClient.GetAllAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync();
|
||||
var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000);
|
||||
clients = clientItems.ToList();
|
||||
clientMap = clients.ToDictionary(c => c.Id, c => c.CompanyName ?? "");
|
||||
clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -195,7 +195,7 @@ else
|
||||
await form.Validate();
|
||||
if (!form.IsValid)
|
||||
{
|
||||
Snackbar.Add("필수 항목을 입력해주세요.", Severity.Warning);
|
||||
Snackbar.Add("고객을 선택하세요.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -210,8 +210,13 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!profileForm.ClientId.HasValue)
|
||||
{
|
||||
Snackbar.Add("고객을 선택하세요.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
var newId = await TaxProfileClient.CreateAsync(
|
||||
profileForm.ClientId,
|
||||
profileForm.ClientId.Value,
|
||||
profileForm.BusinessType);
|
||||
if (newId > 0)
|
||||
{
|
||||
@@ -274,9 +279,15 @@ else
|
||||
_ => Color.Default
|
||||
};
|
||||
|
||||
private static string GetClientDisplayName(Client client)
|
||||
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
||||
? client.CompanyName
|
||||
: !string.IsNullOrWhiteSpace(client.Name)
|
||||
? client.Name
|
||||
: $"Client #{client.Id}";
|
||||
private class TaxProfileForm
|
||||
{
|
||||
public int ClientId { get; set; }
|
||||
public int? ClientId { get; set; }
|
||||
public string BusinessType { get; set; } = "";
|
||||
public string TaxRiskLevel { get; set; } = "normal";
|
||||
public DateTime? NextFilingDueDate { get; set; }
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace TaxBaik.Web.Hubs;
|
||||
|
||||
/// <summary>
|
||||
/// Real-time notification hub for admin dashboard
|
||||
/// SOLID: Single Responsibility - Only broadcasts change notifications
|
||||
/// No state management - stateless broadcast pattern
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class NotificationHub : Hub
|
||||
{
|
||||
private const string AdminGroup = "admins";
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, AdminGroup);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast inquiry status changed to all connected admins
|
||||
/// Clients should re-fetch from API to verify
|
||||
/// </summary>
|
||||
public async Task NotifyInquiryStatusChanged(int inquiryId, string newStatus)
|
||||
{
|
||||
await Clients.Group(AdminGroup).SendAsync("InquiryStatusChanged", new
|
||||
{
|
||||
InquiryId = inquiryId,
|
||||
Status = newStatus,
|
||||
ChangedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast inquiry submitted (new inquiry created)
|
||||
/// </summary>
|
||||
public async Task NotifyInquiryCreated(int inquiryId, string name)
|
||||
{
|
||||
await Clients.Group(AdminGroup).SendAsync("InquiryCreated", new
|
||||
{
|
||||
InquiryId = inquiryId,
|
||||
Name = name,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast client created
|
||||
/// </summary>
|
||||
public async Task NotifyClientCreated(int clientId, string name)
|
||||
{
|
||||
await Clients.Group(AdminGroup).SendAsync("ClientCreated", new
|
||||
{
|
||||
ClientId = clientId,
|
||||
Name = name,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast announcement published
|
||||
/// </summary>
|
||||
public async Task NotifyAnnouncementPublished(int announcementId, string title)
|
||||
{
|
||||
await Clients.Group(AdminGroup).SendAsync("AnnouncementPublished", new
|
||||
{
|
||||
AnnouncementId = announcementId,
|
||||
Title = title,
|
||||
PublishedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast tax filing completed
|
||||
/// </summary>
|
||||
public async Task NotifyFilingCompleted(int filingId, string filingType)
|
||||
{
|
||||
await Clients.Group(AdminGroup).SendAsync("FilingCompleted", new
|
||||
{
|
||||
FilingId = filingId,
|
||||
FilingType = filingType,
|
||||
CompletedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -52,9 +52,6 @@ builder.Services.AddControllers();
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
// SignalR (Notifications only, no state management)
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
// Razor Pages + Blazor Server 통합
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||
@@ -197,9 +194,6 @@ builder.Services.AddCascadingAuthenticationState();
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddAuthorizationCore();
|
||||
|
||||
// Notifications (SignalR)
|
||||
builder.Services.AddScoped<INotificationService, NotificationService>();
|
||||
|
||||
// Telegram Notification
|
||||
builder.Services.AddHttpClient<ITelegramNotificationService, TelegramNotificationService>();
|
||||
|
||||
@@ -350,8 +344,6 @@ app.MapControllers();
|
||||
app.MapHealthChecks("/healthz");
|
||||
app.MapRazorPages();
|
||||
|
||||
// SignalR Hub
|
||||
app.MapHub<TaxBaik.Web.Hubs.NotificationHub>("/taxbaik/notifications");
|
||||
// AllowAnonymous: JWT 미들웨어가 Blazor 셸 요청을 401로 차단하지 않도록 한다.
|
||||
// 인증은 Blazor AuthorizeRouteView → RedirectToLogin 에서 처리한다.
|
||||
app.MapRazorComponents<TaxBaik.Web.Components.Admin.App>()
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
namespace TaxBaik.Web.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Notification service for real-time admin updates
|
||||
/// SOLID: Single Responsibility - Event notification only
|
||||
/// Uses Blazor Server's built-in SignalR for real-time communication
|
||||
/// </summary>
|
||||
public interface INotificationService
|
||||
{
|
||||
event Func<int, string, Task>? OnInquiryStatusChanged;
|
||||
event Func<int, string, Task>? OnInquiryCreated;
|
||||
event Func<int, string, Task>? OnClientCreated;
|
||||
event Func<int, string, Task>? OnAnnouncementPublished;
|
||||
event Func<int, string, Task>? OnFilingCompleted;
|
||||
|
||||
Task TriggerInquiryStatusChanged(int inquiryId, string status);
|
||||
Task TriggerInquiryCreated(int inquiryId, string name);
|
||||
Task TriggerClientCreated(int clientId, string name);
|
||||
Task TriggerAnnouncementPublished(int announcementId, string title);
|
||||
Task TriggerFilingCompleted(int filingId, string filingType);
|
||||
}
|
||||
|
||||
public class NotificationService : INotificationService
|
||||
{
|
||||
private readonly ILogger<NotificationService> _logger;
|
||||
|
||||
public NotificationService(ILogger<NotificationService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public event Func<int, string, Task>? OnInquiryStatusChanged;
|
||||
public event Func<int, string, Task>? OnInquiryCreated;
|
||||
public event Func<int, string, Task>? OnClientCreated;
|
||||
public event Func<int, string, Task>? OnAnnouncementPublished;
|
||||
public event Func<int, string, Task>? OnFilingCompleted;
|
||||
|
||||
public async Task TriggerInquiryStatusChanged(int inquiryId, string status)
|
||||
{
|
||||
_logger.LogInformation($"Inquiry {inquiryId} status changed to {status}");
|
||||
if (OnInquiryStatusChanged != null)
|
||||
await OnInquiryStatusChanged(inquiryId, status);
|
||||
}
|
||||
|
||||
public async Task TriggerInquiryCreated(int inquiryId, string name)
|
||||
{
|
||||
_logger.LogInformation($"New inquiry {inquiryId} from {name}");
|
||||
if (OnInquiryCreated != null)
|
||||
await OnInquiryCreated(inquiryId, name);
|
||||
}
|
||||
|
||||
public async Task TriggerClientCreated(int clientId, string name)
|
||||
{
|
||||
_logger.LogInformation($"New client {clientId}: {name}");
|
||||
if (OnClientCreated != null)
|
||||
await OnClientCreated(clientId, name);
|
||||
}
|
||||
|
||||
public async Task TriggerAnnouncementPublished(int announcementId, string title)
|
||||
{
|
||||
_logger.LogInformation($"Announcement {announcementId} published: {title}");
|
||||
if (OnAnnouncementPublished != null)
|
||||
await OnAnnouncementPublished(announcementId, title);
|
||||
}
|
||||
|
||||
public async Task TriggerFilingCompleted(int filingId, string filingType)
|
||||
{
|
||||
_logger.LogInformation($"Filing {filingId} ({filingType}) completed");
|
||||
if (OnFilingCompleted != null)
|
||||
await OnFilingCompleted(filingId, filingType);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user