import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../screens/equipment/controllers/equipment_history_controller.dart'; import '../../data/models/equipment_history_dto.dart'; class StockOutForm extends StatefulWidget { const StockOutForm({super.key}); @override State createState() => _StockOutFormState(); } class _StockOutFormState extends State { final _formKey = GlobalKey(); int? _selectedEquipmentId; int? _selectedWarehouseId; int _quantity = 1; DateTime _transactionDate = DateTime.now(); String? _notes; String? _destination; String? _recipientName; String? _recipientContact; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { // 재고 현황 로드 context.read().loadInventoryStatus(); }); } Future _handleSubmit() async { if (_formKey.currentState?.saveAndValidate() ?? false) { final controller = context.read(); // 재고 부족 체크 final currentStock = await controller.getAvailableStock( _selectedEquipmentId!, warehouseId: _selectedWarehouseId!, ); if (currentStock < _quantity) { ShadToaster.of(context).show( ShadToast.destructive( title: const Text('재고 부족'), description: Text('현재 재고: $currentStock개, 요청 수량: $_quantity개'), ), ); return; } // 백엔드 스키마에 맞는 요청 데이터 생성 final request = EquipmentHistoryRequestDto( equipmentsId: _selectedEquipmentId!, warehousesId: _selectedWarehouseId!, transactionType: 'O', // 출고 quantity: _quantity, transactedAt: _transactionDate, remark: _notes, ); await controller.createHistory(request); if (controller.error == null && mounted) { ShadToaster.of(context).show( const ShadToast( title: Text('출고 처리 완료'), description: Text('장비가 성공적으로 출고되었습니다'), ), ); Navigator.pop(context, true); } else if (controller.error != null && mounted) { ShadToaster.of(context).show( ShadToast.destructive( title: const Text('출고 처리 실패'), description: Text(controller.error!), ), ); } } } @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return Scaffold( backgroundColor: theme.colorScheme.background, appBar: AppBar( backgroundColor: theme.colorScheme.card, title: const Text('장비 출고 처리'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), ), body: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Center( child: Container( constraints: const BoxConstraints(maxWidth: 600), child: ShadCard( child: Padding( padding: const EdgeInsets.all(24), child: ShadForm( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '출고 정보 입력', style: theme.textTheme.h3, ), const SizedBox(height: 8), Text( '출고할 장비와 수령 정보를 입력하세요', style: theme.textTheme.muted, ), const SizedBox(height: 24), // 창고 선택 Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('출고 창고', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), const SizedBox(height: 8), ShadSelect( placeholder: const Text('창고 선택'), options: [ const ShadOption(value: 1, child: Text('본사 창고')), const ShadOption(value: 2, child: Text('지사 창고')), const ShadOption(value: 3, child: Text('외부 창고')), ], selectedOptionBuilder: (context, value) { switch (value) { case 1: return const Text('본사 창고'); case 2: return const Text('지사 창고'); case 3: return const Text('외부 창고'); default: return const Text(''); } }, onChanged: (value) { setState(() { _selectedWarehouseId = value; _selectedEquipmentId = null; // 창고 변경 시 장비 선택 초기화 }); }, ), ], ), const SizedBox(height: 16), // 장비 선택 (창고별 재고가 있는 장비만 표시) if (_selectedWarehouseId != null) FutureBuilder>( future: context.read().getAvailableEquipments(warehouseId: _selectedWarehouseId), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (!snapshot.hasData || snapshot.data!.isEmpty) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('장비', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: const Text('해당 창고에 재고가 있는 장비가 없습니다'), ), ], ); } final availableEquipmentIds = snapshot.data!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('장비', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), ShadSelect( placeholder: const Text('장비 선택'), options: availableEquipmentIds.map((equipmentId) { return ShadOption( value: equipmentId, child: FutureBuilder( future: context.read().getAvailableStock( equipmentId, warehouseId: _selectedWarehouseId, ), builder: (context, stockSnapshot) { final stock = stockSnapshot.data ?? 0; return Text('장비 ID: $equipmentId (재고: $stock개)'); }, ), ); }).toList(), selectedOptionBuilder: (context, value) { return FutureBuilder( future: context.read().getAvailableStock( value, warehouseId: _selectedWarehouseId, ), builder: (context, stockSnapshot) { final stock = stockSnapshot.data ?? 0; return Text('장비 ID: $value (재고: $stock개)'); }, ); }, onChanged: (value) { setState(() { _selectedEquipmentId = value; }); }, ), if (_selectedEquipmentId != null) Padding( padding: const EdgeInsets.only(top: 8), child: FutureBuilder( future: context.read().getAvailableStock( _selectedEquipmentId!, warehouseId: _selectedWarehouseId, ), builder: (context, stockSnapshot) { final stock = stockSnapshot.data ?? 0; return Text( '현재 재고: $stock개', style: theme.textTheme.small.copyWith( color: theme.colorScheme.primary, ), ); }, ), ), ], ); }, ), const SizedBox(height: 16), // 수량 ShadInputFormField( label: const Text('출고 수량'), keyboardType: TextInputType.number, controller: TextEditingController(text: '1'), validator: (v) { if (_quantity <= 0) { return '수량은 1 이상이어야 합니다'; } // 재고 체크는 submit 시에만 async로 처리 return null; }, onChanged: (value) { setState(() { _quantity = int.tryParse(value) ?? 1; }); }, ), const SizedBox(height: 16), // 출고일 GestureDetector( onTap: () async { final picked = await showDatePicker( context: context, initialDate: _transactionDate, firstDate: DateTime(2020), lastDate: DateTime.now(), ); if (picked != null) { setState(() { _transactionDate = picked; }); } }, child: AbsorbPointer( child: ShadInputFormField( label: const Text('출고일'), controller: TextEditingController( text: '${_transactionDate.year}-${_transactionDate.month.toString().padLeft(2, '0')}-${_transactionDate.day.toString().padLeft(2, '0')}', ), readOnly: true, ), ), ), const SizedBox(height: 16), // 출고 목적지 ShadInputFormField( label: const Text('출고 목적지'), placeholder: const Text('예: 고객사명, 부서명'), validator: (v) { if (_destination == null || _destination!.isEmpty) { return '출고 목적지를 입력하세요'; } return null; }, onChanged: (value) { _destination = value; }, ), const SizedBox(height: 16), // 수령인 정보 ShadInputFormField( label: const Text('수령인'), placeholder: const Text('수령인 이름'), onChanged: (value) { _recipientName = value.isEmpty ? null : value; }, ), const SizedBox(height: 16), ShadInputFormField( label: const Text('수령인 연락처'), placeholder: const Text('010-0000-0000'), onChanged: (value) { _recipientContact = value.isEmpty ? null : value; }, ), const SizedBox(height: 16), // 비고 ShadInputFormField( label: const Text('비고'), maxLines: 3, placeholder: const Text('출고 사유 및 추가 정보'), onChanged: (value) { String fullNotes = ''; if (_destination != null) { fullNotes += '목적지: $_destination'; } if (_recipientName != null) { fullNotes += '\n수령인: $_recipientName'; } if (_recipientContact != null) { fullNotes += '\n연락처: $_recipientContact'; } if (value.isNotEmpty) { fullNotes += '\n비고: $value'; } _notes = fullNotes.isEmpty ? null : fullNotes; }, ), const SizedBox(height: 32), // 버튼 Row( mainAxisAlignment: MainAxisAlignment.end, children: [ ShadButton.outline( child: const Text('취소'), onPressed: () => Navigator.pop(context), ), const SizedBox(width: 8), Consumer( builder: (context, controller, _) { return ShadButton( enabled: !controller.isLoading, onPressed: _handleSubmit, child: controller.isLoading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, ), ) : const Text('출고 처리'), ); }, ), ], ), ], ), ), ), ), ), ), ), ); } }