fix(inventory): 상세 편집 플로우 안정화
- inbound/outbound/rental controller에 fetchTransactionDetail을 추가해 상세 동기화를 지원 - 각 페이지 초기화 시 결재 초안 로딩 권한을 PermissionScope에서 확인하도록 수정 - 상세 패널의 수정 버튼이 모달과 연동되도록 흐름을 정리하고 생성/수정 후 상세 데이터를 재조회 - 기존 결재 메모 필드는 등록 이후 수정 불가하도록 UI와 입력 상태를 비활성화 - 신규 상세-수정 위젯 테스트와 리포지토리 스텁 fetchDetail 구현을 추가 - flutter analyze, flutter test를 실행해 회귀를 점검
This commit is contained in:
@@ -262,6 +262,24 @@ class RentalController extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
/// 단일 대여/반납 트랜잭션 상세를 조회한다.
|
||||
Future<RentalRecord?> fetchTransactionDetail(
|
||||
int id, {
|
||||
List<String> include = const ['lines', 'customers', 'approval'],
|
||||
}) async {
|
||||
try {
|
||||
final transaction = await _transactionRepository.fetchDetail(
|
||||
id,
|
||||
include: include,
|
||||
);
|
||||
return RentalRecord.fromTransaction(transaction);
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint('[RentalController] 상세 조회 실패(id=$id): $error');
|
||||
debugPrintStack(stackTrace: stackTrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _persistApprovalDraft(StockTransactionApprovalInput approval) {
|
||||
final useCase = _saveDraftUseCase;
|
||||
if (useCase == null) {
|
||||
|
||||
@@ -162,7 +162,7 @@ class _RentalPageState extends State<RentalPage> {
|
||||
Future.microtask(() async {
|
||||
await controller.loadStatusOptions();
|
||||
final requester = _resolveCurrentWriter();
|
||||
if (requester != null) {
|
||||
if (requester != null && _canRestoreApprovalDrafts) {
|
||||
await controller.loadApprovalDraftFromServer(requesterId: requester.id);
|
||||
}
|
||||
final hasTypes = await controller.resolveTransactionTypes();
|
||||
@@ -175,6 +175,17 @@ class _RentalPageState extends State<RentalPage> {
|
||||
});
|
||||
}
|
||||
|
||||
bool get _canRestoreApprovalDrafts {
|
||||
final getIt = GetIt.I;
|
||||
if (!getIt.isRegistered<PermissionManager>()) {
|
||||
return false;
|
||||
}
|
||||
return getIt<PermissionManager>().can(
|
||||
PermissionResources.approvals,
|
||||
PermissionAction.view,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleControllerChanged() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
@@ -948,7 +959,7 @@ class _RentalPageState extends State<RentalPage> {
|
||||
}
|
||||
|
||||
List<Widget> _buildDetailActions(RentalRecord record) {
|
||||
final isProcessing = _isProcessing(record.id) || _isLoading;
|
||||
final isProcessing = _isProcessing(record.id);
|
||||
final actions = <Widget>[];
|
||||
|
||||
if (_canSubmit(record)) {
|
||||
@@ -994,12 +1005,7 @@ class _RentalPageState extends State<RentalPage> {
|
||||
actions.add(
|
||||
ShadButton.outline(
|
||||
leading: const Icon(lucide.LucideIcons.pencil, size: 16),
|
||||
onPressed: isProcessing
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).maybePop();
|
||||
_handleEdit(record);
|
||||
},
|
||||
onPressed: isProcessing ? null : () => _openEditFromDetail(record),
|
||||
child: const Text('수정'),
|
||||
),
|
||||
);
|
||||
@@ -1007,6 +1013,17 @@ class _RentalPageState extends State<RentalPage> {
|
||||
return actions;
|
||||
}
|
||||
|
||||
void _openEditFromDetail(RentalRecord record) {
|
||||
final navigator = Navigator.of(context, rootNavigator: true);
|
||||
navigator.pop();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
await _handleEdit(record, reopenOnCancel: true);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _handleCreate() async {
|
||||
final record = await _showRentalFormDialog();
|
||||
if (record != null) {
|
||||
@@ -1014,10 +1031,15 @@ class _RentalPageState extends State<RentalPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleEdit(RentalRecord record) async {
|
||||
Future<void> _handleEdit(
|
||||
RentalRecord record, {
|
||||
bool reopenOnCancel = false,
|
||||
}) async {
|
||||
final updated = await _showRentalFormDialog(initial: record);
|
||||
if (updated != null) {
|
||||
_selectRecord(updated, openDetail: true);
|
||||
} else if (reopenOnCancel) {
|
||||
_selectRecord(record, openDetail: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1805,6 +1827,22 @@ class _RentalPageState extends State<RentalPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
Future<RentalRecord?> resolveUpdatedRecord(int? id) async {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
final detail = await controller.fetchTransactionDetail(id);
|
||||
if (detail != null) {
|
||||
return detail;
|
||||
}
|
||||
for (final record in controller.records) {
|
||||
if (record.id == id) {
|
||||
return record;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final selectedLookup =
|
||||
rentalTypeValue.value == RentalTableSpec.rentalTypes.last
|
||||
? (_returnTransactionType ?? controller.returnTransactionType)
|
||||
@@ -1898,7 +1936,6 @@ class _RentalPageState extends State<RentalPage> {
|
||||
),
|
||||
refreshAfter: false,
|
||||
);
|
||||
result = updated;
|
||||
final currentLines =
|
||||
initialRecord.raw?.lines ?? const <StockTransactionLine>[];
|
||||
final currentCustomers =
|
||||
@@ -1922,6 +1959,8 @@ class _RentalPageState extends State<RentalPage> {
|
||||
);
|
||||
}
|
||||
await controller.refresh();
|
||||
final refreshed = await resolveUpdatedRecord(transactionId);
|
||||
result = refreshed ?? updated;
|
||||
updateSaving(false);
|
||||
if (!mounted) {
|
||||
return;
|
||||
@@ -1993,7 +2032,8 @@ class _RentalPageState extends State<RentalPage> {
|
||||
approval: approvalInput,
|
||||
),
|
||||
);
|
||||
result = created;
|
||||
final refreshed = await resolveUpdatedRecord(created.id);
|
||||
result = refreshed ?? created;
|
||||
updateSaving(false);
|
||||
if (!mounted) {
|
||||
return;
|
||||
@@ -2329,9 +2369,26 @@ class _RentalPageState extends State<RentalPage> {
|
||||
width: 500,
|
||||
child: _FormFieldLabel(
|
||||
label: '결재 메모',
|
||||
child: ShadInput(
|
||||
controller: approvalNoteController,
|
||||
maxLines: 2,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ShadInput(
|
||||
controller: approvalNoteController,
|
||||
maxLines: 2,
|
||||
readOnly: initial != null,
|
||||
enabled: initial == null,
|
||||
),
|
||||
if (initial != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text(
|
||||
'등록된 결재 메모는 수정할 수 없습니다.',
|
||||
style: theme.textTheme.small.copyWith(
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user