feat(frontend): 승인 템플릿 API 통합 및 디버그 로그인 확장
- docs 폴더 문서를 최신 API 계약으로 갱신하고 가이드를 다듬었다\n- approvals data/presentation 레이어를 API v4 스펙에 맞춰 리팩터링했다\n- approver 자동완성 위젯을 신규 공유 레포지토리에 맞춰 교체하고 UX를 보강했다\n- inventory/rental 페이지 테이블 초기화 시 승인 기준 연동을 정비했다\n- 로그인 페이지 디버그 버튼을 tera/exa 계정으로 분리해 QA 로그인을 단순화했다\n- get_it 등록과 테스트 케이스를 신규 공유 리포지토리에 맞춰 업데이트했다
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart' as lucide;
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import '../../../../../core/config/environment.dart';
|
||||
@@ -15,6 +16,7 @@ import '../../../domain/entities/approval_template.dart';
|
||||
import '../../../domain/repositories/approval_template_repository.dart';
|
||||
import '../../../domain/usecases/apply_approval_template_use_case.dart';
|
||||
import '../../../domain/usecases/save_approval_template_use_case.dart';
|
||||
import '../../../../auth/application/auth_service.dart';
|
||||
import '../controllers/approval_template_controller.dart';
|
||||
|
||||
/// 결재 템플릿 관리 페이지. 기능 플래그에 따라 준비중 화면을 노출한다.
|
||||
@@ -69,7 +71,7 @@ class _ApprovalTemplateEnabledPageState
|
||||
late final ApprovalTemplateController _controller;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final FocusNode _searchFocus = FocusNode();
|
||||
final DateFormat _dateFormat = DateFormat('yyyy-MM-dd HH:mm');
|
||||
final intl.DateFormat _dateFormat = intl.DateFormat('yyyy-MM-dd HH:mm');
|
||||
String? _lastError;
|
||||
static const _pageSizeOptions = [10, 20, 50];
|
||||
|
||||
@@ -354,6 +356,50 @@ class _ApprovalTemplateEnabledPageState
|
||||
_searchFocus.requestFocus();
|
||||
}
|
||||
|
||||
String _generateTemplateCode() {
|
||||
final authService = GetIt.I<AuthService>();
|
||||
final session = authService.session;
|
||||
String normalizedEmployee = '';
|
||||
|
||||
final candidateValues = <String?>[
|
||||
session?.user.employeeNo,
|
||||
session?.user.email,
|
||||
session?.user.name,
|
||||
];
|
||||
for (final candidate in candidateValues) {
|
||||
if (candidate == null) {
|
||||
continue;
|
||||
}
|
||||
var source = candidate.trim();
|
||||
final atIndex = source.indexOf('@');
|
||||
if (atIndex > 0) {
|
||||
source = source.substring(0, atIndex);
|
||||
}
|
||||
final normalized = source.toUpperCase().replaceAll(
|
||||
RegExp(r'[^A-Z0-9]'),
|
||||
'',
|
||||
);
|
||||
if (normalized.isNotEmpty) {
|
||||
normalizedEmployee = normalized;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (normalizedEmployee.isEmpty && session?.user.id != null) {
|
||||
normalizedEmployee = session!.user.id.toString();
|
||||
}
|
||||
|
||||
final suffixSource = normalizedEmployee.isEmpty
|
||||
? '0000'
|
||||
: normalizedEmployee;
|
||||
final suffix = suffixSource.length >= 4
|
||||
? suffixSource.substring(suffixSource.length - 4)
|
||||
: suffixSource.padLeft(4, '0');
|
||||
final timestamp = intl.DateFormat(
|
||||
'yyMMddHHmmssSSS',
|
||||
).format(DateTime.now().toUtc());
|
||||
return 'AP_TEMP_${suffix}_$timestamp';
|
||||
}
|
||||
|
||||
Future<void> _openTemplatePreview(int templateId) async {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
@@ -530,7 +576,9 @@ class _ApprovalTemplateEnabledPageState
|
||||
Future<bool?> _openTemplateForm({ApprovalTemplate? template}) async {
|
||||
final isEdit = template != null;
|
||||
final existingTemplate = template;
|
||||
final codeController = TextEditingController(text: template?.code ?? '');
|
||||
final codeController = TextEditingController(
|
||||
text: isEdit ? existingTemplate!.code : _generateTemplateCode(),
|
||||
);
|
||||
final nameController = TextEditingController(text: template?.name ?? '');
|
||||
final descriptionController = TextEditingController(
|
||||
text: template?.description ?? '',
|
||||
@@ -573,7 +621,7 @@ class _ApprovalTemplateEnabledPageState
|
||||
)
|
||||
.toList();
|
||||
final input = ApprovalTemplateInput(
|
||||
code: isEdit ? existingTemplate?.code : codeValue,
|
||||
code: isEdit ? existingTemplate!.code : codeValue,
|
||||
name: nameValue,
|
||||
description: descriptionController.text.trim().isEmpty
|
||||
? null
|
||||
@@ -583,16 +631,11 @@ class _ApprovalTemplateEnabledPageState
|
||||
: noteController.text.trim(),
|
||||
isActive: statusNotifier.value,
|
||||
);
|
||||
if (isEdit && existingTemplate == null) {
|
||||
modalSetState?.call(() => errorText = '템플릿 정보를 불러오지 못했습니다.');
|
||||
modalSetState?.call(() => isSaving = false);
|
||||
return;
|
||||
}
|
||||
|
||||
modalSetState?.call(() => isSaving = true);
|
||||
|
||||
final success = isEdit && existingTemplate != null
|
||||
? await _controller.update(existingTemplate.id, input, stepInputs)
|
||||
final success = isEdit
|
||||
? await _controller.update(existingTemplate!.id, input, stepInputs)
|
||||
: await _controller.create(input, stepInputs);
|
||||
if (success != null && mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pop(true);
|
||||
@@ -622,6 +665,8 @@ class _ApprovalTemplateEnabledPageState
|
||||
label: '템플릿 코드',
|
||||
child: ShadInput(
|
||||
controller: codeController,
|
||||
readOnly: true,
|
||||
enabled: false,
|
||||
placeholder: const Text('예: AP_INBOUND'),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user