diff --git a/CLAUDE.md b/CLAUDE.md index e5af0fc..42f52d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1138,6 +1138,111 @@ public async Task OnPostAsync() } ``` +### 10.5 폼 UI/UX - Enter 키 포커스 이동 + +**목표**: 관리 페이지 폼에서 Enter 키를 누르면 다음 필드로 자동 포커스 + +#### 구현 패턴 +```razor + + + +``` + +```csharp +@code { + private MudTextField? fieldB; + private MudTextField? fieldC; + + private async Task HandleEnter(KeyboardEventArgs e, string nextFieldId) + { + if (e.Code == "Enter" || e.Key == "Enter") + { + e.PreventDefault(); + await FocusNextField(nextFieldId); + } + } + + private async Task FocusNextField(string fieldId) + { + // 다음 필드로 포커스 이동 + if (fieldId == "fieldB") + await fieldB?.FocusAsync()!; + else if (fieldId == "fieldC") + await fieldC?.FocusAsync()!; + } +} +``` + +**규칙**: +- 모든 관리 페이지 폼에 Enter 키 지원 필수 +- Tab 키와 동일하게 작동하되, 명시적 입력 의도 반영 +- 마지막 필드에서 Enter = 폼 제출 (자동 검증) + +--- + +### 10.6 Dorsum ERP 통합 가이드 + +**목표**: TaxBaik은 더존 세무회계의 상위 CRM/고객 관리 전략 시스템 + +#### 역할 정의 +| 시스템 | 담당 | 기능 | 통합 지점 | +|--------|------|------|---------| +| **더존** (Dorsum) | 세무 처리 | 신고, 장부관리, 결산 | 데이터 동기화 | +| **TaxBaik** | 고객 관리 | CRM, 계약, 수익 추적 | 고객 메타 정보 | + +#### 중복 제거 원칙 +- ❌ 세무 장부 데이터는 Dorsum에만 관리 (중복 금지) +- ❌ 신고 자동화는 Dorsum API 활용 (TaxBaik은 상태만 추적) +- ✅ 고객사 정보 (회사명, 담당자, 연락처) = TaxBaik 관리 +- ✅ 고객 계약 이력, CRM 활동 = TaxBaik 관리 +- ✅ 수익 추적, 인보이스 관리 = TaxBaik 관리 + +#### Dorsum과의 차별화 기능 +``` +Dorsum의 강점 TaxBaik의 고유 기능 +┌─────────────────────┐ ┌──────────────────────┐ +│ 신고 장부 자동화 │ │ 고객 수명주기 관리 │ +│ 세금 계산기 │ │ 계약/수익 추적 │ +│ 결산 보고서 │ │ 상담 활동 기록 │ +│ 세율/세법 업데이트 │ │ 다중 회사 관리 │ +└─────────────────────┘ │ 마케팅 자동화 │ + │ 모바일 앱 │ + └──────────────────────┘ +``` + +#### API 동기화 (향후) +``` +Dorsum API + ↓ +[고객별 신고 상태 조회] + ↓ +TaxBaik [상태 추적] → [CRM 분석] + ↓ +[수익 인식 자동화] +``` + +#### 데이터 주인 원칙 +``` +고객사 정보 +├─ Dorsum 소유: 사업자등록번호, 기업명, 업종 +├─ TaxBaik 소유: 컨택트 정보, 계약 내용, 상담 기록 +└─ 동기화 필요: 회사 마스터ID + +신고 일정 +├─ Dorsum 소유: 신고 유형, 세법 기한 +├─ TaxBaik 소유: 담당자 배정, 상담 노트, 상태 +└─ 참고만: TaxBaik은 Dorsum의 신고 기한을 읽기만 함 +``` + +**구현 팁**: +- Dorsum 엔터프라이즈 API 활용 가능 시: webhook로 신고 완료 알림 수신 +- 불가능하면: 주기적 배치로 Dorsum 상태 폴링 (일 1회) +- TaxBaik에서 생성한 데이터는 절대 Dorsum에 역동기화 금지 + --- ## 11. 배포 검증 diff --git a/TaxBaik.Web/Services/TelegramNotificationService.cs b/TaxBaik.Web/Services/TelegramNotificationService.cs index 762c367..14d1f69 100644 --- a/TaxBaik.Web/Services/TelegramNotificationService.cs +++ b/TaxBaik.Web/Services/TelegramNotificationService.cs @@ -11,6 +11,8 @@ public interface ITelegramNotificationService Task SendMessageAsync(string message, CancellationToken ct = default); Task SendErrorAsync(string title, string details, CancellationToken ct = default); Task SendInfoAsync(string title, string message, CancellationToken ct = default); + Task SendInquiryNotificationAsync(string message, CancellationToken ct = default); + Task SendSystemNotificationAsync(string message, CancellationToken ct = default); } public class TelegramNotificationService : ITelegramNotificationService @@ -18,7 +20,9 @@ public class TelegramNotificationService : ITelegramNotificationService private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly string _botToken; - private readonly string _chatId; + private readonly string _defaultChatId; + private readonly string _inquiryChatId; + private readonly string _systemChatId; private const string TelegramApiUrl = "https://api.telegram.org"; public TelegramNotificationService( @@ -29,23 +33,42 @@ public class TelegramNotificationService : ITelegramNotificationService _httpClient = httpClient; _logger = logger; _botToken = config["Telegram:BotToken"] ?? ""; - _chatId = config["Telegram:ChatId"] ?? ""; + _defaultChatId = config["Telegram:ChatId"] ?? ""; + _inquiryChatId = config["Telegram:InquiryChatId"] ?? "-5434691215"; + _systemChatId = config["Telegram:SystemChatId"] ?? "-5585148480"; } public async Task SendMessageAsync(string message, CancellationToken ct = default) { - if (string.IsNullOrEmpty(_botToken) || string.IsNullOrEmpty(_chatId)) + if (string.IsNullOrEmpty(_botToken) || string.IsNullOrEmpty(_defaultChatId)) { _logger.LogWarning("Telegram credentials not configured"); return; } + await SendToChat(_defaultChatId, message, ct); + } + + public async Task SendInquiryNotificationAsync(string message, CancellationToken ct = default) => + await SendToChat(_inquiryChatId, $"📋 문의 사항\n\n{message}", ct); + + public async Task SendSystemNotificationAsync(string message, CancellationToken ct = default) => + await SendToChat(_systemChatId, $"🔧 시스템 알림\n\n{message}", ct); + + private async Task SendToChat(string chatId, string message, CancellationToken ct) + { + if (string.IsNullOrEmpty(_botToken) || string.IsNullOrEmpty(chatId)) + { + _logger.LogWarning("Telegram credentials not configured for chatId {ChatId}", chatId); + return; + } + try { var url = $"{TelegramApiUrl}/bot{_botToken}/sendMessage"; var payload = new { - chat_id = _chatId, + chat_id = chatId, text = message, parse_mode = "HTML" }; @@ -53,12 +76,12 @@ public class TelegramNotificationService : ITelegramNotificationService var response = await _httpClient.PostAsJsonAsync(url, payload, cancellationToken: ct); if (!response.IsSuccessStatusCode) { - _logger.LogError("Failed to send Telegram message: {StatusCode}", response.StatusCode); + _logger.LogError("Failed to send Telegram message to {ChatId}: {StatusCode}", chatId, response.StatusCode); } } catch (Exception ex) { - _logger.LogError(ex, "Error sending Telegram message"); + _logger.LogError(ex, "Error sending Telegram message to {ChatId}", chatId); } } diff --git a/TaxBaik.Web/appsettings.json b/TaxBaik.Web/appsettings.json index df573f3..4a87fa2 100644 --- a/TaxBaik.Web/appsettings.json +++ b/TaxBaik.Web/appsettings.json @@ -19,7 +19,9 @@ }, "Telegram": { "BotToken": "8679990909:AAGLLRUIAuEbYAZVGOYDu-UuTu4ihroEiX0", - "ChatId": "-5585148480" + "ChatId": "-5585148480", + "InquiryChatId": "-5434691215", + "SystemChatId": "-5585148480" }, "Admin": { "PasswordResetToken": "dev-reset-token-12345"