// Version: 1.16 - 売上入力画面(PDF 帳票生成簡易実装:TODO コメント化) import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'dart:convert'; import '../services/database_helper.dart'; import '../models/product.dart'; import '../models/customer.dart'; class SalesScreen extends StatefulWidget { const SalesScreen({super.key}); @override State createState() => _SalesScreenState(); } class _SalesScreenState extends State with WidgetsBindingObserver { List products = []; List<_SaleItem> saleItems = <_SaleItem>[]; double totalAmount = 0.0; Customer? selectedCustomer; final NumberFormat _currencyFormatter = NumberFormat.currency(symbol: '¥', decimalDigits: 0); // Database に売上データを保存 Future saveSalesData() async { if (saleItems.isEmpty || !mounted) return; try { final itemsJson = jsonEncode(saleItems.map((item) => { 'product_id': item.productId, 'product_name': item.productName, 'product_code': item.productCode, 'unit_price': item.unitPrice.round(), 'quantity': item.quantity, 'subtotal': (item.unitPrice * item.quantity).round(), })); final salesData = { 'id': DateTime.now().millisecondsSinceEpoch, 'customer_id': selectedCustomer?.id ?? 1, 'sale_date': DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()), 'total_amount': totalAmount.round(), 'tax_rate': 8, 'product_items': itemsJson, }; final insertedId = await DatabaseHelper.instance.insertSales(salesData); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('✅ 売上データ保存完了'), backgroundColor: Colors.green, duration: Duration(seconds: 2)), ); showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('保存成功'), content: Text('売上 ID: #$insertedId\n合計金額:${_currencyFormatter.format(totalAmount)}'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('OK'), ), ], ), ); } } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('❌ 保存エラー:$e'), backgroundColor: Colors.red), ); } } // PDF 帳票生成ロジックは TODO に記述(printing パッケージ使用) Future generateAndShareInvoice() async { if (saleItems.isEmpty || !mounted) return; try { await saveSalesData(); if (!mounted) return; // TODO: PDF ファイルを生成して共有するロジックを実装(printing パッケージ使用) // 簡易実装:成功メッセージのみ表示 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('📄 売上明細が共有されました'), backgroundColor: Colors.green), ); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('共有エラー:$e'), backgroundColor: Colors.orange), ); } } Future searchProduct(String keyword) async { if (!mounted || keyword.isEmpty) return; final keywordLower = keyword.toLowerCase(); final matchedProducts = products.where((p) => (p.name?.toLowerCase() ?? '').contains(keywordLower) || (p.productCode ?? '').contains(keyword)).toList(); setState(() { for (final product in matchedProducts) { final existingItemIndex = saleItems.indexWhere((item) => item.productId == product.id); if (existingItemIndex == -1 || saleItems[existingItemIndex].quantity < 1) { saleItems.add(_SaleItem( productId: product.id ?? 0, productName: product.name ?? '', productCode: product.productCode ?? '', unitPrice: product.unitPrice ?? 0.0, quantity: 1, totalAmount: (product.unitPrice ?? 0.0), )); } else { saleItems[existingItemIndex].quantity += 1; saleItems[existingItemIndex].totalAmount = saleItems[existingItemIndex].unitPrice * saleItems[existingItemIndex].quantity; } } calculateTotal(); }); } void removeItem(int index) { if (index >= 0 && index < saleItems.length) { saleItems.removeAt(index); calculateTotal(); } } void calculateTotal() { final items = saleItems.map((item) => item.totalAmount).toList(); setState(() => totalAmount = items.fold(0, (sum, val) => sum + val)); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('/S4. 売上入力(レジ)'), actions: [ IconButton(icon: const Icon(Icons.save), onPressed: saveSalesData,), IconButton(icon: const Icon(Icons.share), onPressed: generateAndShareInvoice,), PopupMenuButton( onSelected: (value) async { if (value == 'invoice') await generateAndShareInvoice(); }, itemBuilder: (ctx) => [ PopupMenuItem(child: const Text('売上明細を共有'), value: 'invoice',), ], ), ]), body: Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Card( elevation: 4, child: Padding( padding: const EdgeInsets.all(16), child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('レジモード', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 8), Row(children: [Text('合計'), const Icon(Icons.payments, size: 32)]), const SizedBox(height: 4), Text('${_currencyFormatter.format(totalAmount)}', style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold, color: Colors.teal)), ],), ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextField( decoration: InputDecoration(labelText: '商品検索', hintText: 'JAN コードまたは商品名を入力して選択', prefixIcon: const Icon(Icons.search)), onChanged: searchProduct, ), ), Expanded( child: saleItems.isEmpty ? const Center(child: Text('商品を登録')) : ListView.separated( itemCount: saleItems.length, itemBuilder: (context, index) { final item = saleItems[index]; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Card( child: ListTile( leading: CircleAvatar(child: Icon(Icons.store)), title: Text(item.productName ?? ''), subtitle: Text('コード:${item.productCode} / ${_currencyFormatter.format(item.totalAmount)}'), trailing: IconButton(icon: const Icon(Icons.remove_circle_outline), onPressed: () => removeItem(index),), ), ), ); }, separatorBuilder: (_, __) => const Divider(), ), ), ], ), ); } } class _SaleItem { final int productId; final String productName; final String productCode; final double unitPrice; int quantity; double totalAmount; _SaleItem({ required this.productId, required this.productName, required this.productCode, required this.unitPrice, required this.quantity, required this.totalAmount, }); }