feat(approvals): 결재 상세 전표 연동과 스코프 권한 매핑 확장

- 결재 상세 다이얼로그에 전표 요약·라인·고객 섹션을 추가하고 현재 사용자 단계 강조 및 비고 입력 검증을 개선함

- 대시보드·결재 목록에서 전표 리포지토리와 AuthService를 주입해 상세 진입과 결재 관리 이동 버튼을 제공함

- StockTransactionApprovalInput이 template/steps를 config 노드로 직렬화하도록 변경하고 통합 테스트를 갱신함

- scope 권한 문자열을 리소스권으로 변환하는 PermissionScopeMapper와 단위 테스트를 추가하고 AuthPermission을 연동함

- 재고 메뉴 정렬, 상세 컨트롤러 오류 리셋, 요청자 자동완성 상태 동기화 등 주변 UI 버그를 수정하고 테스트를 보강함
This commit is contained in:
JiWoong Sul
2025-11-14 01:57:02 +09:00
parent e3cf068bf8
commit 6d09e72142
12 changed files with 857 additions and 50 deletions

View File

@@ -100,7 +100,7 @@ final List<MenuRouteDefinition> menuRouteDefinitions = [
defaultLabel: '재고 현황',
icon: lucide.LucideIcons.chartNoAxesColumnIncreasing,
builder: (context, state) => InventorySummaryPage(routeUri: state.uri),
defaultOrder: 20,
defaultOrder: 11,
extraRequirements: const [
PermissionRequirement(resource: PermissionResources.inventoryScope),
],
@@ -112,7 +112,7 @@ final List<MenuRouteDefinition> menuRouteDefinitions = [
defaultLabel: '입고',
icon: lucide.LucideIcons.packagePlus,
builder: (context, state) => InboundPage(routeUri: state.uri),
defaultOrder: 21,
defaultOrder: 12,
),
MenuRouteDefinition(
menuCode: 'inventory.issues',
@@ -121,7 +121,7 @@ final List<MenuRouteDefinition> menuRouteDefinitions = [
defaultLabel: '출고',
icon: lucide.LucideIcons.packageMinus,
builder: (context, state) => OutboundPage(routeUri: state.uri),
defaultOrder: 22,
defaultOrder: 13,
),
MenuRouteDefinition(
menuCode: 'inventory.rentals',
@@ -129,7 +129,7 @@ final List<MenuRouteDefinition> menuRouteDefinitions = [
defaultLabel: '대여',
icon: lucide.LucideIcons.handshake,
builder: (context, state) => RentalPage(routeUri: state.uri),
defaultOrder: 23,
defaultOrder: 14,
),
MenuRouteDefinition(
menuCode: 'inventory.vendors',

View File

@@ -0,0 +1,97 @@
import 'permission_manager.dart';
import 'permission_resources.dart';
/// 서버가 내려주는 scope 권한 코드를 실사용 리소스 권한으로 변환한다.
class PermissionScopeMapper {
const PermissionScopeMapper._();
/// scope:<code> 형식의 권한에서 [PermissionManager]가 이해할 수 있는 리소스 맵을 생성한다.
static Map<String, Set<PermissionAction>>? map(String scope) {
final code = _normalize(scope);
if (code.isEmpty) {
return null;
}
final definition = _definitions[code];
if (definition == null || definition.isEmpty) {
return null;
}
final mapped = <String, Set<PermissionAction>>{};
for (final entry in definition.entries) {
mapped[entry.key] = entry.value.toSet();
}
return mapped;
}
static String _normalize(String value) {
final trimmed = value.trim().toLowerCase();
if (trimmed.isEmpty) {
return '';
}
if (trimmed.startsWith('scope:')) {
return trimmed.substring('scope:'.length);
}
return trimmed;
}
static const Map<String, Map<String, Set<PermissionAction>>> _definitions = {
'approval.approve': {
PermissionResources.approvals: {PermissionAction.approve},
},
'approvals': {
PermissionResources.approvals: {PermissionAction.view},
},
'approvals.history': {
PermissionResources.approvalHistories: {PermissionAction.view},
},
'approvals.steps': {
PermissionResources.approvalSteps: {PermissionAction.view},
},
'approvals.templates': {
PermissionResources.approvalTemplates: {PermissionAction.view},
},
'dashboard': {
PermissionResources.dashboard: {PermissionAction.view},
},
'dashboard.view': {
PermissionResources.dashboard: {PermissionAction.view},
},
'approval.view_all': {
PermissionResources.approvals: {PermissionAction.view},
PermissionResources.approvalSteps: {PermissionAction.view},
PermissionResources.approvalHistories: {PermissionAction.view},
},
'approval.manage': {
PermissionResources.approvals: {
PermissionAction.view,
PermissionAction.create,
PermissionAction.edit,
PermissionAction.delete,
},
PermissionResources.approvalSteps: {
PermissionAction.view,
PermissionAction.create,
PermissionAction.edit,
PermissionAction.delete,
},
PermissionResources.approvalHistories: {PermissionAction.view},
PermissionResources.approvalTemplates: {
PermissionAction.view,
PermissionAction.create,
PermissionAction.edit,
PermissionAction.delete,
},
},
'inventory.view': {
PermissionResources.inventorySummary: {PermissionAction.view},
},
'inventory.receipts': {
PermissionResources.stockTransactions: {PermissionAction.view},
},
'inventory.issues': {
PermissionResources.stockTransactions: {PermissionAction.view},
},
'inventory.rentals': {
PermissionResources.stockTransactions: {PermissionAction.view},
},
};
}