// Version: 1.7 - 在庫管理画面 import 'package:flutter/material.dart'; import '../../models/product.dart'; import '../../services/database_helper.dart'; /// 在庫管理画面(新規登録・一覧表示) class InventoryMasterScreen extends StatefulWidget { const InventoryMasterScreen({super.key}); @override State createState() => _InventoryMasterScreenState(); } class _InventoryMasterScreenState extends State { List _products = []; Map? _newInventory; // 新規登録用データ @override void initState() { super.initState(); _loadProducts(); } Future _loadProducts() async { try { final products = await DatabaseHelper.instance.getProducts(); if (mounted) setState(() => _products = products ?? const []); } catch (e) {} } void _submitNewInventory() async { if (_newInventory == null || !(_newInventory!['product_code'] as String).isNotEmpty) return; try { final db = await DatabaseHelper.instance.database; // product_code の一意性チェック final existing = await db.query('inventory', where: "product_code = ?", whereArgs: [(_newInventory!['product_code'] as String)]); if (existing.isNotEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('同じ JAN コードの在庫は既に存在します'), backgroundColor: Colors.orange), ); return; } final inventoryData = { 'product_code': _newInventory!['product_code'] as String, 'name': _newInventory!['name'] as String? ?? '', 'unit_price': (_newInventory!['unit_price'] as num?)?.toInt() ?? 0, 'stock': (_newInventory!['stock'] as num?)?.toInt() ?? 0, 'min_stock': (_newInventory!['min_stock'] as num?)?.toInt() ?? 10, 'max_stock': (_newInventory!['max_stock'] as num?)?.toInt() ?? 1000, 'supplier_name': _newInventory!['supplier_name'] as String? ?? '', }; await DatabaseHelper.instance.insertInventory(inventoryData); if (mounted) ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('在庫登録完了'), backgroundColor: Colors.green), ); setState(() => _newInventory = null); _loadProducts(); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('保存エラー:$e'), backgroundColor: Colors.red), ); } } void _showAddDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('新規在庫登録'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( decoration: const InputDecoration(labelText: 'JAN コード', hintText: '例:4901234567890'), onChanged: (v) => _newInventory?['product_code'] = v, ), TextField( decoration: const InputDecoration(labelText: '商品名', hintText: '商品名を入力'), onChanged: (v) => _newInventory?['name'] = v, ), TextField( decoration: const InputDecoration(labelText: '単価(円)', hintText: '0'), keyboardType: TextInputType.number, onChanged: (v) => _newInventory?['unit_price'] = int.tryParse(v), ), TextField( decoration: const InputDecoration(labelText: '在庫数', hintText: '0'), keyboardType: TextInputType.number, onChanged: (v) => _newInventory?['stock'] = int.tryParse(v), ), TextField( decoration: const InputDecoration(labelText: '最低在庫', hintText: '10'), keyboardType: TextInputType.number, onChanged: (v) => _newInventory?['min_stock'] = int.tryParse(v), ), TextField( decoration: const InputDecoration(labelText: '最大在庫', hintText: '1000'), keyboardType: TextInputType.number, onChanged: (v) => _newInventory?['max_stock'] = int.tryParse(v), ), TextField( decoration: const InputDecoration(labelText: '仕入先', hintText: '仕入先会社名'), onChanged: (v) => _newInventory?['supplier_name'] = v, ), ], ), ), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('キャンセル')), ElevatedButton( onPressed: _newInventory != null ? _submitNewInventory : null, child: const Text('登録'), ), ], ), ); } void _editInventory(int id) async { final product = await DatabaseHelper.instance.getProduct(id); if (product == null || mounted) return; showDialog( context: context, builder: (context) => AlertDialog( title: const Text('在庫編集'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( decoration: InputDecoration(labelText: 'JAN コード', hintText: product.productCode), readOnly: true, ), TextField( decoration: const InputDecoration(labelText: '商品名'), controller: TextEditingController(text: product.name), onChanged: (v) { _updateInventory(id, {'name': v}); }, ), TextField( decoration: const InputDecoration(labelText: '単価(円)'), keyboardType: TextInputType.number, onChanged: (v) => _updateInventory(id, {'unit_price': int.tryParse(v)}), ), TextField( decoration: const InputDecoration(labelText: '在庫数'), keyboardType: TextInputType.number, onChanged: (v) => _updateInventory(id, {'stock': int.tryParse(v)}), ), TextField( decoration: const InputDecoration(labelText: '最低在庫'), keyboardType: TextInputType.number, onChanged: (v) => _updateInventory(id, {'min_stock': int.tryParse(v)}), ), TextField( decoration: const InputDecoration(labelText: '最大在庫'), keyboardType: TextInputType.number, onChanged: (v) => _updateInventory(id, {'max_stock': int.tryParse(v)}), ), TextField( decoration: const InputDecoration(labelText: '仕入先'), onChanged: (v) => _updateInventory(id, {'supplier_name': v}), ), ], ), ), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('キャンセル')), ElevatedButton( onPressed: () => Navigator.pop(context), child: const Text('保存'), ), ], ), ); } void _updateInventory(int id, Map data) async { final inventoryData = { 'product_code': data['product_code'] as String?, 'name': data['name'] as String?, 'unit_price': data['unit_price'] as int?, 'stock': data['stock'] as int?, 'min_stock': data['min_stock'] as int?, 'max_stock': data['max_stock'] as int?, 'supplier_name': data['supplier_name'] as String?, }; try { await DatabaseHelper.instance.updateInventory(inventoryData); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('在庫更新完了'), backgroundColor: Colors.green), ); _loadProducts(); } catch (e) {} } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('在庫管理'), actions: [ IconButton(icon: const Icon(Icons.refresh), onPressed: _loadProducts,), ], ), body: _products.isEmpty ? Center(child: const Text('在庫データがありません')) : SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Card( child: ListTile( title: const Text('新規登録'), subtitle: const Text('商品への在庫を登録'), trailing: const Icon(Icons.add_circle), onTap: _showAddDialog, ), ), const SizedBox(height: 16), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _products.length, itemBuilder: (context, index) { final product = _products[index]; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( title: Text(product.name), subtitle: Text('JAN: ${product.productCode} / ¥${product.unitPrice} × ${product.stock ?? 0}'), trailing: IconButton(icon: const Icon(Icons.edit), onPressed: () => _editInventory(product.id ?? 0),), ), ); }, ), ], ), ), ); } }