feat(approvals): 결재 상세 전표 연동과 스코프 권한 매핑 확장
- 결재 상세 다이얼로그에 전표 요약·라인·고객 섹션을 추가하고 현재 사용자 단계 강조 및 비고 입력 검증을 개선함 - 대시보드·결재 목록에서 전표 리포지토리와 AuthService를 주입해 상세 진입과 결재 관리 이동 버튼을 제공함 - StockTransactionApprovalInput이 template/steps를 config 노드로 직렬화하도록 변경하고 통합 테스트를 갱신함 - scope 권한 문자열을 리소스권으로 변환하는 PermissionScopeMapper와 단위 테스트를 추가하고 AuthPermission을 연동함 - 재고 메뉴 정렬, 상세 컨트롤러 오류 리셋, 요청자 자동완성 상태 동기화 등 주변 UI 버그를 수정하고 테스트를 보강함
This commit is contained in:
@@ -7,8 +7,19 @@ import 'package:lucide_icons_flutter/lucide_icons.dart' as lucide;
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import 'package:superport_v2/core/network/failure.dart';
|
||||
import 'package:superport_v2/core/permissions/permission_manager.dart';
|
||||
import 'package:superport_v2/core/permissions/permission_resources.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
|
||||
import 'package:superport_v2/features/auth/application/auth_service.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/repositories/approval_repository.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/repositories/approval_template_repository.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/usecases/get_approval_draft_use_case.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/usecases/list_approval_drafts_use_case.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/usecases/save_approval_draft_use_case.dart';
|
||||
import 'package:superport_v2/features/approvals/presentation/controllers/approval_controller.dart';
|
||||
import 'package:superport_v2/features/approvals/presentation/dialogs/approval_detail_dialog.dart';
|
||||
import 'package:superport_v2/features/inventory/lookups/domain/repositories/inventory_lookup_repository.dart';
|
||||
import 'package:superport_v2/features/inventory/transactions/domain/repositories/stock_transaction_repository.dart';
|
||||
import 'package:superport_v2/widgets/app_layout.dart';
|
||||
import 'package:superport_v2/widgets/components/empty_state.dart';
|
||||
import 'package:superport_v2/widgets/components/feedback.dart';
|
||||
@@ -446,6 +457,8 @@ class _PendingApprovalCard extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
final repository = GetIt.I<ApprovalRepository>();
|
||||
final parentContext = context;
|
||||
final detailNotifier = ValueNotifier<Approval?>(null);
|
||||
final detailFuture = repository
|
||||
.fetchDetail(approvalId, includeSteps: true, includeHistories: true)
|
||||
.catchError((error) {
|
||||
@@ -462,9 +475,11 @@ class _PendingApprovalCard extends StatelessWidget {
|
||||
debugPrint(
|
||||
'[DashboardPage] 결재 상세 조회 성공: id=${detail.id}, approvalNo=${detail.approvalNo}',
|
||||
);
|
||||
detailNotifier.value = detail;
|
||||
return detail;
|
||||
});
|
||||
if (!context.mounted) {
|
||||
detailNotifier.dispose();
|
||||
return;
|
||||
}
|
||||
await SuperportDialog.show<void>(
|
||||
@@ -474,6 +489,37 @@ class _PendingApprovalCard extends StatelessWidget {
|
||||
description: '결재번호 ${approval.approvalNo}',
|
||||
constraints: const BoxConstraints(maxWidth: 760),
|
||||
actions: [
|
||||
ValueListenableBuilder<Approval?>(
|
||||
valueListenable: detailNotifier,
|
||||
builder: (dialogContext, detail, _) {
|
||||
return ShadButton.outline(
|
||||
onPressed: detail == null
|
||||
? null
|
||||
: () async {
|
||||
final approvalDetailId = detail.id;
|
||||
if (approvalDetailId == null) {
|
||||
SuperportToast.error(
|
||||
parentContext,
|
||||
'결재 ID가 없어 결재 관리 화면을 열 수 없습니다.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
await Navigator.of(
|
||||
dialogContext,
|
||||
rootNavigator: true,
|
||||
).maybePop();
|
||||
if (!parentContext.mounted) {
|
||||
return;
|
||||
}
|
||||
await _openApprovalManagement(
|
||||
parentContext,
|
||||
approvalDetailId,
|
||||
);
|
||||
},
|
||||
child: const Text('결재 관리'),
|
||||
);
|
||||
},
|
||||
),
|
||||
ShadButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context, rootNavigator: true).maybePop(),
|
||||
@@ -509,6 +555,73 @@ class _PendingApprovalCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
detailNotifier.dispose();
|
||||
}
|
||||
|
||||
Future<void> _openApprovalManagement(
|
||||
BuildContext context,
|
||||
int approvalId,
|
||||
) async {
|
||||
final controller = ApprovalController(
|
||||
approvalRepository: GetIt.I<ApprovalRepository>(),
|
||||
templateRepository: GetIt.I<ApprovalTemplateRepository>(),
|
||||
transactionRepository: GetIt.I<StockTransactionRepository>(),
|
||||
lookupRepository: GetIt.I.isRegistered<InventoryLookupRepository>()
|
||||
? GetIt.I<InventoryLookupRepository>()
|
||||
: null,
|
||||
saveDraftUseCase: GetIt.I.isRegistered<SaveApprovalDraftUseCase>()
|
||||
? GetIt.I<SaveApprovalDraftUseCase>()
|
||||
: null,
|
||||
getDraftUseCase: GetIt.I.isRegistered<GetApprovalDraftUseCase>()
|
||||
? GetIt.I<GetApprovalDraftUseCase>()
|
||||
: null,
|
||||
listDraftsUseCase: GetIt.I.isRegistered<ListApprovalDraftsUseCase>()
|
||||
? GetIt.I<ListApprovalDraftsUseCase>()
|
||||
: null,
|
||||
);
|
||||
final dateFormat = intl.DateFormat('yyyy-MM-dd HH:mm');
|
||||
final currentUserId = GetIt.I.isRegistered<AuthService>()
|
||||
? GetIt.I<AuthService>().session?.user.id
|
||||
: null;
|
||||
|
||||
try {
|
||||
await Future.wait([
|
||||
controller.loadActionOptions(),
|
||||
controller.loadTemplates(),
|
||||
controller.loadStatusLookups(),
|
||||
]);
|
||||
await controller.selectApproval(approvalId);
|
||||
if (controller.selected == null) {
|
||||
final error = controller.errorMessage ?? '결재 상세 정보를 불러오지 못했습니다.';
|
||||
if (context.mounted) {
|
||||
SuperportToast.error(context, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
final permissionScope = PermissionScope.of(context);
|
||||
final canPerformStepActions = permissionScope.can(
|
||||
PermissionResources.approvals,
|
||||
PermissionAction.approve,
|
||||
);
|
||||
final canApplyTemplate = permissionScope.can(
|
||||
PermissionResources.approvals,
|
||||
PermissionAction.edit,
|
||||
);
|
||||
|
||||
await showApprovalDetailDialog(
|
||||
context: context,
|
||||
controller: controller,
|
||||
dateFormat: dateFormat,
|
||||
canPerformStepActions: canPerformStepActions,
|
||||
canApplyTemplate: canApplyTemplate,
|
||||
currentUserId: currentUserId,
|
||||
);
|
||||
} finally {
|
||||
controller.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user