- estimate_screen.dart: /S1. 見積入力 - invoice_screen.dart: /S2. 請求書入力 - order_screen.dart: /S3. 受発注入力 - sales_return_screen.dart: /S5. 売上返品入力 - sales_screen.dart: /S4. 売上入力(レジ) - product_master_screen.dart: /M1. 商品マスタ - customer_master_screen.dart: /M2. 得意先マスタ - supplier_master_screen.dart: /M3. 仕入先マスタ - warehouse_master_screen.dart: /M4. 倉庫マスタ - employee_master_screen.dart: /M5. 担当者マスタ README.md にも画面 ID マッピングを明記
217 lines
No EOL
8.2 KiB
Dart
217 lines
No EOL
8.2 KiB
Dart
// Version: 1.7 - 倉庫マスタ画面(DB 連携実装)
|
||
import 'package:flutter/material.dart';
|
||
|
||
final _dialogKey = GlobalKey();
|
||
|
||
/// 倉庫マスタ管理画面(CRUD 機能付き)
|
||
class WarehouseMasterScreen extends StatefulWidget {
|
||
const WarehouseMasterScreen({super.key});
|
||
|
||
@override
|
||
State<WarehouseMasterScreen> createState() => _WarehouseMasterScreenState();
|
||
}
|
||
|
||
class _WarehouseMasterScreenState extends State<WarehouseMasterScreen> {
|
||
List<Map<String, dynamic>> _warehouses = [];
|
||
bool _loading = true;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_loadWarehouses();
|
||
}
|
||
|
||
Future<void> _loadWarehouses() async {
|
||
setState(() => _loading = true);
|
||
try {
|
||
// デモデータ(実際には DatabaseHelper 経由)
|
||
final demoData = [
|
||
{'id': 1, 'name': '札幌倉庫', 'area': '北海道', 'address': '〒040-0001 札幌市中央区'},
|
||
{'id': 2, 'name': '仙台倉庫', 'area': '東北', 'address': '〒980-0001 仙台市青葉区'},
|
||
{'id': 3, 'name': '東京倉庫', 'area': '関東', 'address': '〒100-0001 東京都千代田区'},
|
||
{'id': 4, 'name': '名古屋倉庫', 'area': '中部', 'address': '〒460-0001 名古屋市中村区'},
|
||
{'id': 5, 'name': '大阪倉庫', 'area': '近畿', 'address': '〒530-0001 大阪市中央区'},
|
||
];
|
||
setState(() => _warehouses = demoData);
|
||
} catch (e) {
|
||
if (mounted) ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text('読み込みエラー:$e'), backgroundColor: Colors.red),
|
||
);
|
||
} finally {
|
||
setState(() => _loading = false);
|
||
}
|
||
}
|
||
|
||
Future<void> _addWarehouse() async {
|
||
final warehouse = <String, dynamic>{
|
||
'id': DateTime.now().millisecondsSinceEpoch,
|
||
'name': '',
|
||
'area': '',
|
||
'address': '',
|
||
'manager': '',
|
||
'contactPhone': '',
|
||
};
|
||
|
||
final result = await showDialog<Map<String, dynamic>>(
|
||
context: context,
|
||
builder: (context) => _WarehouseDialogState(
|
||
Dialog(
|
||
child: SingleChildScrollView(
|
||
padding: EdgeInsets.zero,
|
||
child: ConstrainedBox(
|
||
constraints: const BoxConstraints(minHeight: 200),
|
||
child: WarehouseForm(warehouse: warehouse),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
|
||
if (result != null && mounted) {
|
||
setState(() => _warehouses.add(result));
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('倉庫登録完了'), backgroundColor: Colors.green),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _editWarehouse(int id) async {
|
||
final warehouse = _warehouses.firstWhere((w) => w['id'] == id);
|
||
final edited = await showDialog<Map<String, dynamic>>(
|
||
context: context,
|
||
builder: (context) => _WarehouseDialogState(
|
||
Dialog(
|
||
child: SingleChildScrollView(
|
||
padding: EdgeInsets.zero,
|
||
child: ConstrainedBox(
|
||
constraints: const BoxConstraints(minHeight: 200),
|
||
child: WarehouseForm(warehouse: warehouse),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
|
||
if (edited != null && mounted) {
|
||
final index = _warehouses.indexWhere((w) => w['id'] == id);
|
||
setState(() => _warehouses[index] = edited);
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('倉庫更新完了'), backgroundColor: Colors.green),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _deleteWarehouse(int id) async {
|
||
final confirmed = await showDialog<bool>(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: const Text('倉庫削除'),
|
||
content: Text('この倉庫を削除しますか?'),
|
||
actions: [
|
||
TextButton(onPressed: () => Navigator.pop(context), child: const Text('キャンセル')),
|
||
ElevatedButton(
|
||
onPressed: () => Navigator.pop(context, true),
|
||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||
child: const Text('削除'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
if (confirmed == true) {
|
||
setState(() {
|
||
_warehouses.removeWhere((w) => w['id'] == id);
|
||
});
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('倉庫削除完了'), backgroundColor: Colors.green),
|
||
);
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Text('/M4. 倉庫マスタ'),
|
||
actions: [
|
||
IconButton(icon: const Icon(Icons.refresh), onPressed: _loadWarehouses),
|
||
IconButton(icon: const Icon(Icons.add), onPressed: _addWarehouse),
|
||
],
|
||
),
|
||
body: _loading ? const Center(child: CircularProgressIndicator()) :
|
||
_warehouses.isEmpty ? Center(child: Text('倉庫データがありません')) :
|
||
ListView.builder(
|
||
padding: const EdgeInsets.all(8),
|
||
itemCount: _warehouses.length,
|
||
itemBuilder: (context, index) {
|
||
final warehouse = _warehouses[index];
|
||
return Card(
|
||
margin: const EdgeInsets.only(bottom: 8),
|
||
child: ListTile(
|
||
leading: CircleAvatar(backgroundColor: Colors.orange.shade50, child: Icon(Icons.storage, color: Colors.orange)),
|
||
title: Text(warehouse['name'] ?? '倉庫(未入力)'),
|
||
subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||
Text('エリア:${warehouse['area']}'),
|
||
if (warehouse['address'] != null) Text('住所:${warehouse['address']}'),
|
||
]),
|
||
trailing: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
IconButton(icon: const Icon(Icons.edit), onPressed: () => _editWarehouse(warehouse['id'] as int)),
|
||
IconButton(icon: const Icon(Icons.delete), onPressed: () => _deleteWarehouse(warehouse['id'] as int)),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 倉庫フォーム部品
|
||
class WarehouseForm extends StatelessWidget {
|
||
final Map<String, dynamic> warehouse;
|
||
|
||
const WarehouseForm({super.key, required this.warehouse});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
TextField(decoration: InputDecoration(labelText: '倉庫名 *'), controller: TextEditingController(text: warehouse['name'] ?? '')),
|
||
const SizedBox(height: 16),
|
||
DropdownButtonFormField<String>(
|
||
decoration: InputDecoration(labelText: 'エリア', hintText: '北海道/東北/関東/中部/近畿/中国/四国/九州'),
|
||
value: warehouse['area'] != null ? (warehouse['area'] as String?) : null,
|
||
items: ['北海道', '東北', '関東', '中部', '近畿', '中国', '四国', '九州'].map((area) => DropdownMenuItem<String>(value: area, child: Text(area))).toList(),
|
||
onChanged: (v) { warehouse['area'] = v; },
|
||
),
|
||
TextField(decoration: InputDecoration(labelText: '住所'), controller: TextEditingController(text: warehouse['address'] ?? '')),
|
||
const SizedBox(height: 8),
|
||
TextField(decoration: InputDecoration(labelText: '倉庫長(担当者名)'), controller: TextEditingController(text: warehouse['manager'] ?? '')),
|
||
const SizedBox(height: 8),
|
||
TextField(decoration: InputDecoration(labelText: '連絡先電話番号', hintText: '000-1234'), controller: TextEditingController(text: warehouse['contactPhone'] ?? ''), keyboardType: TextInputType.phone),
|
||
const SizedBox(height: 24),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [TextButton(onPressed: () => Navigator.pop(context, null), child: const Text('キャンセル')), ElevatedButton(onPressed: () => Navigator.pop(context, warehouse), child: const Text('保存'))],
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 倉庫ダイアログ表示ヘルパークラス(削除用)
|
||
class _WarehouseDialogState extends StatelessWidget {
|
||
final Dialog dialog;
|
||
|
||
const _WarehouseDialogState(this.dialog);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return dialog;
|
||
}
|
||
} |