見積書画面簡素化\n\n- database_helper.dart の重複 API 削除\n- estimate_screen.dart の Estimate モデル依存排除(Map データ保存)\n- showModal → showDialog 修正\n- Duration(inDays:...) → Duration(days:) 修正\n- TextButton の child パラメータ追加\n- ビルド成功 (49.4MB APK)
This commit is contained in:
parent
4c5ea99947
commit
4679ad30ae
2 changed files with 419 additions and 129 deletions
|
|
@ -1,6 +1,9 @@
|
||||||
// Version: 1.5 - 見積書画面(簡易実装)
|
// Version: 1.9 - 見積書画面(簡素版)
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import '../models/customer.dart';
|
import '../models/customer.dart';
|
||||||
|
import '../models/product.dart';
|
||||||
|
import '../services/database_helper.dart';
|
||||||
|
|
||||||
/// 見積書作成画面
|
/// 見積書作成画面
|
||||||
class EstimateScreen extends StatefulWidget {
|
class EstimateScreen extends StatefulWidget {
|
||||||
|
|
@ -10,78 +13,411 @@ class EstimateScreen extends StatefulWidget {
|
||||||
State<EstimateScreen> createState() => _EstimateScreenState();
|
State<EstimateScreen> createState() => _EstimateScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EstimateScreenState extends State<EstimateScreen> {
|
class _EstimateScreenState extends State<EstimateScreen> with SingleTickerProviderStateMixin {
|
||||||
Customer? _selectedCustomer;
|
Customer? _selectedCustomer;
|
||||||
List<Customer> _customers = [];
|
List<Customer> _customers = [];
|
||||||
DateTime? _expiryDate;
|
DateTime? _expiryDate;
|
||||||
|
|
||||||
|
// 商品リスト状態
|
||||||
|
List<Product> _products = [];
|
||||||
|
List<_EstimateItem> _estimateItems = <_EstimateItem>[];
|
||||||
|
double _totalAmount = 0.0;
|
||||||
|
String _estimateNumber = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadCustomers();
|
_loadCustomers();
|
||||||
|
_generateEstimateNumber();
|
||||||
|
_loadProducts();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadCustomers() async {
|
Future<void> _loadCustomers() async {
|
||||||
// TODO: DatabaseHelper.instance.getCustomers() を使用
|
try {
|
||||||
setState(() => _customers = []);
|
final customers = await DatabaseHelper.instance.getCustomers();
|
||||||
|
if (mounted) setState(() => _customers = customers ?? const <Customer>[]);
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 見積有効期限を設定(簡易)
|
/// 見積書番号を自動生成(YMM-0001 形式)
|
||||||
void setExpiryDate(DateTime date) {
|
void _generateEstimateNumber() {
|
||||||
setState(() => _expiryDate = date);
|
final now = DateTime.now();
|
||||||
|
final yearMonth = '${now.year}${now.month.toString().padLeft(2, '0')}';
|
||||||
|
if (mounted) setState(() => _estimateNumber = '$yearMonth-0001');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadProducts() async {
|
||||||
|
try {
|
||||||
|
final ps = await DatabaseHelper.instance.getProducts();
|
||||||
|
if (mounted) setState(() => _products = ps ?? const <Product>[]);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 商品を検索して見積項目に追加
|
||||||
|
Future<void> searchProduct(String keyword) async {
|
||||||
|
if (!mounted || keyword.isEmpty || keyword.contains(' ')) return;
|
||||||
|
|
||||||
|
final keywordLower = keyword.toLowerCase();
|
||||||
|
final matchedProducts = _products.where((p) =>
|
||||||
|
(p.name?.toLowerCase() ?? '').contains(keywordLower) ||
|
||||||
|
(p.productCode ?? '').contains(keyword)).toList();
|
||||||
|
|
||||||
|
if (matchedProducts.isEmpty) return;
|
||||||
|
|
||||||
|
// 最初の一致する商品を追加
|
||||||
|
final product = matchedProducts.first;
|
||||||
|
final existingItemIndex = _estimateItems.indexWhere((item) => item.productId == product.id);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (existingItemIndex == -1 || _estimateItems[existingItemIndex].quantity < 50) {
|
||||||
|
_estimateItems.add(_EstimateItem(
|
||||||
|
productId: product.id ?? 0,
|
||||||
|
productName: product.name ?? '',
|
||||||
|
productCode: product.productCode ?? '',
|
||||||
|
unitPrice: product.unitPrice ?? 0.0,
|
||||||
|
quantity: 1,
|
||||||
|
totalAmount: (product.unitPrice ?? 0.0),
|
||||||
|
));
|
||||||
|
} else if (existingItemIndex != -1) {
|
||||||
|
_estimateItems[existingItemIndex].quantity += 1;
|
||||||
|
_estimateItems[existingItemIndex].totalAmount =
|
||||||
|
_estimateItems[existingItemIndex].unitPrice * _estimateItems[existingItemIndex].quantity;
|
||||||
|
}
|
||||||
|
calculateTotal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeItem(int index) {
|
||||||
|
if (index >= 0 && index < _estimateItems.length) {
|
||||||
|
_estimateItems.removeAt(index);
|
||||||
|
calculateTotal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void increaseQuantity(int index) {
|
||||||
|
if (index >= 0 && index < _estimateItems.length) {
|
||||||
|
final item = _estimateItems[index];
|
||||||
|
if (item.quantity < 50) { // 1 セルで最大 50 件
|
||||||
|
item.quantity += 1;
|
||||||
|
item.totalAmount = item.unitPrice * item.quantity;
|
||||||
|
calculateTotal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decreaseQuantity(int index) {
|
||||||
|
if (index >= 0 && index < _estimateItems.length && _estimateItems[index].quantity > 1) {
|
||||||
|
_estimateItems[index].quantity -= 1;
|
||||||
|
_estimateItems[index].totalAmount = _estimateItems[index].unitPrice * _estimateItems[index].quantity;
|
||||||
|
calculateTotal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void calculateTotal() {
|
||||||
|
final items = _estimateItems.map((item) => item.totalAmount).toList();
|
||||||
|
if (mounted) setState(() => _totalAmount = items.fold(0.0, (sum, val) => sum + val));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveEstimate() async {
|
||||||
|
if (_estimateItems.isEmpty || !_selectedCustomer!.customerCode.isNotEmpty) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Map にデータ構築
|
||||||
|
final estimateData = <String, dynamic>{
|
||||||
|
'customer_code': _selectedCustomer!.customerCode,
|
||||||
|
'estimate_number': _estimateNumber,
|
||||||
|
'expiry_date': _expiryDate != null ? DateFormat('yyyy-MM-dd').format(_expiryDate!) : null,
|
||||||
|
'total_amount': _totalAmount.round(),
|
||||||
|
'tax_rate': _selectedCustomer!.taxRate ?? 8,
|
||||||
|
'product_items': _estimateItems.map((item) {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'productId': item.productId,
|
||||||
|
'productName': item.productName,
|
||||||
|
'unitPrice': item.unitPrice.round(),
|
||||||
|
'quantity': item.quantity,
|
||||||
|
};
|
||||||
|
}).toList(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await DatabaseHelper.instance.insertEstimate(estimateData);
|
||||||
|
|
||||||
|
if (mounted) ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('見積書保存完了'), duration: Duration(seconds: 2)),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('保存エラー:$e'), backgroundColor: Colors.red),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('見積書')),
|
appBar: AppBar(
|
||||||
body: _selectedCustomer == null
|
title: const Text('見積書'),
|
||||||
? const Center(child: Text('得意先を選択してください'))
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
onPressed: _selectedCustomer != null ? saveEstimate : null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: _selectedCustomer == null || _estimateItems.isEmpty
|
||||||
|
? Center(child: Text('得意先を選択し、商品を検索して見積書を作成'))
|
||||||
: SingleChildScrollView(
|
: SingleChildScrollView(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(16),
|
||||||
padding: const EdgeInsets.all(16),
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
children: [
|
// 見積書番号表示
|
||||||
// 見積有効期限設定エリア(簡易)
|
ListTile(
|
||||||
ListTile(
|
contentPadding: EdgeInsets.zero,
|
||||||
|
title: const Text('見積書番号'),
|
||||||
|
subtitle: Text(_estimateNumber),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(height: 24),
|
||||||
|
|
||||||
|
// 得意先情報表示
|
||||||
|
Card(
|
||||||
|
child: ListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('見積有効期限'),
|
title: const Text('得意先'),
|
||||||
subtitle: _expiryDate != null ? Text('${_expiryDate!.day}/${_expiryDate!.month}') : const Text('-'),
|
subtitle: Text(_selectedCustomer!.name),
|
||||||
trailing: IconButton(icon: const Icon(Icons.calendar_today), onPressed: () {
|
trailing: IconButton(icon: const Icon(Icons.person), onPressed: () => _showCustomerSelector()),
|
||||||
// TODO: デイティピッカーの実装
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const Divider(),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 合計金額表示(簡易)
|
// 有効期限設定
|
||||||
Card(
|
Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('見積書合計'),
|
title: Text(_expiryDate != null ? '見積有効期限' : '見積有効期限(未設定)'),
|
||||||
subtitle: const Text('¥0.00'),
|
subtitle: _expiryDate != null
|
||||||
trailing: IconButton(icon: const Icon(Icons.edit), onPressed: () {}),
|
? Text(DateFormat('yyyy/MM/dd').format(_expiryDate!))
|
||||||
|
: const Text('-'),
|
||||||
|
trailing: IconButton(icon: const Icon(Icons.calendar_today), onPressed: () => _showDatePicker()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// 商品検索エリア
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: '商品検索',
|
||||||
|
hintText: '商品名または JAN コードを入力',
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
suffixIcon: IconButton(icon: const Icon(Icons.clear), onPressed: () => searchProduct('')),
|
||||||
),
|
),
|
||||||
|
onChanged: searchProduct,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
// 見積項目一覧
|
||||||
|
Card(
|
||||||
|
child: _estimateItems.isEmpty
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Center(child: Text('商品を登録して見積書を作成')),
|
||||||
|
)
|
||||||
|
: ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: _estimateItems.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = _estimateItems[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(item.productName),
|
||||||
|
subtitle: Text('コード:${item.productCode} / ¥${item.totalAmount.toStringAsFixed(2)} × ${item.quantity}'),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(icon: const Icon(Icons.remove_circle), onPressed: () => decreaseQuantity(index),),
|
||||||
|
IconButton(icon: const Icon(Icons.add_circle), onPressed: () => increaseQuantity(index),),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, __) => const Divider(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// PDF 帳票出力ボタン(簡易)
|
const SizedBox(height: 24),
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
// 合計金額表示
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
Card(
|
||||||
const SnackBar(content: Text('PDF 帳票生成中...')),
|
color: Colors.blue.shade50,
|
||||||
);
|
child: ListTile(
|
||||||
},
|
contentPadding: EdgeInsets.zero,
|
||||||
icon: const Icon(Icons.download),
|
title: const Text('見積書合計'),
|
||||||
label: const Text('PDF をダウンロード'),
|
subtitle: Text('¥${_totalAmount.toStringAsFixed(2)}', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 保存ボタン
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: _selectedCustomer != null ? saveEstimate : null,
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
label: const Text('見積書を保存'),
|
||||||
|
style: ElevatedButton.styleFrom(padding: const EdgeInsets.all(16)),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// 詳細表示ボタン(簡易版)
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: _estimateItems.isNotEmpty ? () => _showSummary() : null,
|
||||||
|
icon: const Icon(Icons.info),
|
||||||
|
label: const Text('見積内容を確認'),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showCustomerSelector() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => StatefulBuilder(
|
||||||
|
builder: (context, setStateDialog) => AlertDialog(
|
||||||
|
title: const Text('得意先を選択'),
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: _customers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final customer = _customers[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(customer.name),
|
||||||
|
subtitle: Text('${customer.customerCode} / TEL:${customer.phoneNumber}'),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selectedCustomer = customer;
|
||||||
|
if (_expiryDate != null) {
|
||||||
|
final yearMonth = '${_expiryDate!.year}${_expiryDate!.month.toString().padLeft(2, '0')}';
|
||||||
|
_estimateNumber = '$yearMonth-0001';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('キャンセル'))],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showDatePicker() {
|
||||||
|
showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DatePickerDialog(initialDate: _expiryDate ?? DateTime.now().add(const Duration(days: 30))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSummary() {
|
||||||
|
if (_estimateItems.isEmpty) return;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('見積書概要'),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('見積書番号:$_estimateNumber'),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text('得意先:${_selectedCustomer?.name ?? '未指定'}'),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text('合計金額:¥${_totalAmount.toStringAsFixed(2)}'),
|
||||||
|
if (_expiryDate != null) Text('有効期限:${DateFormat('yyyy/MM/dd').format(_expiryDate!)}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('閉じる'))],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EstimateItem {
|
||||||
|
final int productId;
|
||||||
|
final String productName;
|
||||||
|
final String productCode;
|
||||||
|
double unitPrice;
|
||||||
|
int quantity;
|
||||||
|
double totalAmount;
|
||||||
|
|
||||||
|
_EstimateItem({
|
||||||
|
required this.productId,
|
||||||
|
required this.productName,
|
||||||
|
required this.productCode,
|
||||||
|
required this.unitPrice,
|
||||||
|
required this.quantity,
|
||||||
|
required this.totalAmount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// デイティピッカーダイアログ(簡易)
|
||||||
|
class DatePickerDialog extends StatefulWidget {
|
||||||
|
final DateTime initialDate;
|
||||||
|
const DatePickerDialog({super.key, required this.initialDate});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DatePickerDialog> createState() => _DatePickerDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatePickerDialogState extends State<DatePickerDialog> {
|
||||||
|
DateTime _selectedDate = DateTime.now();
|
||||||
|
|
||||||
|
void _selectDate(DateTime date) {
|
||||||
|
setState(() => _selectedDate = date);
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('見積有効期限を選択'),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.calendar_today),
|
||||||
|
title: const Text('今日から 30 日後'),
|
||||||
|
onTap: () => _selectDate(DateTime.now().add(const Duration(days: 30))),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.access_time),
|
||||||
|
title: const Text('1 ヶ月後(約 30 日)'),
|
||||||
|
onTap: () => _selectDate(DateTime.now().add(const Duration(days: 30))),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.info_outline),
|
||||||
|
title: const Text('カスタム日付(簡易:未実装)'),
|
||||||
|
subtitle: const Text('デフォルト:30 日後'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('キャンセル')),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _selectDate(DateTime.now().add(const Duration(days: 30))),
|
||||||
|
child: const Text('標準(30 日後)'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
import 'dart:convert';
|
||||||
import '../models/customer.dart';
|
import '../models/customer.dart';
|
||||||
import '../models/product.dart';
|
import '../models/product.dart';
|
||||||
|
import '../models/estimate.dart';
|
||||||
|
|
||||||
class DatabaseHelper {
|
class DatabaseHelper {
|
||||||
static final DatabaseHelper instance = DatabaseHelper._init();
|
static final DatabaseHelper instance = DatabaseHelper._init();
|
||||||
|
|
@ -32,11 +34,13 @@ class DatabaseHelper {
|
||||||
await db.execute('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, address TEXT, phone_number TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
await db.execute('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, address TEXT, phone_number TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
||||||
await db.execute('CREATE TABLE products (id INTEGER PRIMARY KEY AUTOINCREMENT, product_code TEXT NOT NULL, name TEXT NOT NULL, unit_price INTEGER NOT NULL, quantity INTEGER DEFAULT 0, stock INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
await db.execute('CREATE TABLE products (id INTEGER PRIMARY KEY AUTOINCREMENT, product_code TEXT NOT NULL, name TEXT NOT NULL, unit_price INTEGER NOT NULL, quantity INTEGER DEFAULT 0, stock INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
||||||
await db.execute('CREATE TABLE sales (id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER NOT NULL, sale_date TEXT NOT NULL, total_amount INTEGER NOT NULL, tax_rate INTEGER DEFAULT 8, product_items TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
await db.execute('CREATE TABLE sales (id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER NOT NULL, sale_date TEXT NOT NULL, total_amount INTEGER NOT NULL, tax_rate INTEGER DEFAULT 8, product_items TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
||||||
await db.execute('CREATE TABLE estimates (id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER NOT NULL, estimate_number TEXT NOT NULL, product_items TEXT, total_amount INTEGER NOT NULL, tax_rate INTEGER DEFAULT 8, status TEXT DEFAULT "open", expiry_date TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
await db.execute('CREATE TABLE estimates (id INTEGER PRIMARY KEY AUTOINCREMENT, customer_code TEXT NOT NULL, estimate_number TEXT NOT NULL, product_items TEXT, total_amount INTEGER NOT NULL, tax_rate INTEGER DEFAULT 8, status TEXT DEFAULT "open", expiry_date TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
||||||
await db.execute('CREATE TABLE inventory (id INTEGER PRIMARY KEY AUTOINCREMENT, product_code TEXT UNIQUE NOT NULL, name TEXT NOT NULL, unit_price INTEGER NOT NULL, stock INTEGER DEFAULT 0, min_stock INTEGER DEFAULT 0, max_stock INTEGER DEFAULT 1000, supplier_name TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
await db.execute('CREATE TABLE inventory (id INTEGER PRIMARY KEY AUTOINCREMENT, product_code TEXT UNIQUE NOT NULL, name TEXT NOT NULL, unit_price INTEGER NOT NULL, stock INTEGER DEFAULT 0, min_stock INTEGER DEFAULT 0, max_stock INTEGER DEFAULT 1000, supplier_name TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
||||||
|
await db.execute('CREATE TABLE invoices (id INTEGER PRIMARY KEY AUTOINCREMENT, customer_code TEXT NOT NULL, invoice_number TEXT NOT NULL, sale_date TEXT NOT NULL, total_amount INTEGER NOT NULL, tax_rate INTEGER DEFAULT 8, status TEXT DEFAULT "paid", product_items TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)');
|
||||||
print('Database created with version: 1');
|
print('Database created with version: 1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Customer API
|
||||||
Future<int> insertCustomer(Customer customer) async {
|
Future<int> insertCustomer(Customer customer) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.insert('customers', customer.toMap());
|
return await db.insert('customers', customer.toMap());
|
||||||
|
|
@ -57,12 +61,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
Future<int> updateCustomer(Customer customer) async {
|
Future<int> updateCustomer(Customer customer) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.update(
|
return await db.update('customers', customer.toMap(), where: 'id = ?', whereArgs: [customer.id]);
|
||||||
'customers',
|
|
||||||
customer.toMap(),
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [customer.id],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteCustomer(int id) async {
|
Future<int> deleteCustomer(int id) async {
|
||||||
|
|
@ -70,6 +69,7 @@ class DatabaseHelper {
|
||||||
return await db.delete('customers', where: 'id = ?', whereArgs: [id]);
|
return await db.delete('customers', where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Product API
|
||||||
Future<int> insertProduct(Product product) async {
|
Future<int> insertProduct(Product product) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.insert('products', product.toMap());
|
return await db.insert('products', product.toMap());
|
||||||
|
|
@ -90,12 +90,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
Future<int> updateProduct(Product product) async {
|
Future<int> updateProduct(Product product) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.update(
|
return await db.update('products', product.toMap(), where: 'id = ?', whereArgs: [product.id]);
|
||||||
'products',
|
|
||||||
product.toMap(),
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [product.id],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteProduct(int id) async {
|
Future<int> deleteProduct(int id) async {
|
||||||
|
|
@ -103,37 +98,9 @@ class DatabaseHelper {
|
||||||
return await db.delete('products', where: 'id = ?', whereArgs: [id]);
|
return await db.delete('products', where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
String encodeToJson(Object? data) {
|
// Sales API
|
||||||
try {
|
|
||||||
if (data == null) return '';
|
|
||||||
if (data is String) return data;
|
|
||||||
final json = StringBuffer('{');
|
|
||||||
var first = true;
|
|
||||||
if (data is Map) {
|
|
||||||
for (var key in data.keys) {
|
|
||||||
if (!first) json.write(',');
|
|
||||||
first = false;
|
|
||||||
json.write('"${key}":"${data[key]}"');
|
|
||||||
}
|
|
||||||
} else if (data is List) {
|
|
||||||
for (var item in data) {
|
|
||||||
if (!first) json.write(',');
|
|
||||||
first = false;
|
|
||||||
json.write('{"val":"$item"}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json.write('}');
|
|
||||||
return json.toString();
|
|
||||||
} catch (e) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> insertSales(Map<String, dynamic> salesData) async {
|
Future<int> insertSales(Map<String, dynamic> salesData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
if (salesData['product_items'] != null && salesData['product_items'] is List) {
|
|
||||||
salesData['product_items'] = encodeToJson(salesData['product_items']);
|
|
||||||
}
|
|
||||||
return await db.insert('sales', salesData);
|
return await db.insert('sales', salesData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,15 +111,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
Future<int> updateSales(Map<String, dynamic> salesData) async {
|
Future<int> updateSales(Map<String, dynamic> salesData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
if (salesData['product_items'] != null && salesData['product_items'] is List) {
|
return await db.update('sales', salesData, where: 'id = ?', whereArgs: [salesData['id'] as int]);
|
||||||
salesData['product_items'] = encodeToJson(salesData['product_items']);
|
|
||||||
}
|
|
||||||
return await db.update(
|
|
||||||
'sales',
|
|
||||||
salesData,
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [salesData['id'] as int],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteSales(int id) async {
|
Future<int> deleteSales(int id) async {
|
||||||
|
|
@ -160,11 +119,9 @@ class DatabaseHelper {
|
||||||
return await db.delete('sales', where: 'id = ?', whereArgs: [id]);
|
return await db.delete('sales', where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Estimate API(単純化)
|
||||||
Future<int> insertEstimate(Map<String, dynamic> estimateData) async {
|
Future<int> insertEstimate(Map<String, dynamic> estimateData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
if (estimateData['product_items'] != null && estimateData['product_items'] is List) {
|
|
||||||
estimateData['product_items'] = encodeToJson(estimateData['product_items']);
|
|
||||||
}
|
|
||||||
return await db.insert('estimates', estimateData);
|
return await db.insert('estimates', estimateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,15 +132,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
Future<int> updateEstimate(Map<String, dynamic> estimateData) async {
|
Future<int> updateEstimate(Map<String, dynamic> estimateData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
if (estimateData['product_items'] != null && estimateData['product_items'] is List) {
|
return await db.update('estimates', estimateData, where: 'id = ?', whereArgs: [estimateData['id'] as int]);
|
||||||
estimateData['product_items'] = encodeToJson(estimateData['product_items']);
|
|
||||||
}
|
|
||||||
return await db.update(
|
|
||||||
'estimates',
|
|
||||||
estimateData,
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [estimateData['id'] as int],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteEstimate(int id) async {
|
Future<int> deleteEstimate(int id) async {
|
||||||
|
|
@ -191,6 +140,28 @@ class DatabaseHelper {
|
||||||
return await db.delete('estimates', where: 'id = ?', whereArgs: [id]);
|
return await db.delete('estimates', where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoice API
|
||||||
|
Future<int> insertInvoice(Map<String, dynamic> invoiceData) async {
|
||||||
|
final db = await database;
|
||||||
|
return await db.insert('invoices', invoiceData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getInvoices() async {
|
||||||
|
final db = await database;
|
||||||
|
return await db.query('invoices');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> updateInvoice(Map<String, dynamic> invoiceData) async {
|
||||||
|
final db = await database;
|
||||||
|
return await db.update('invoices', invoiceData, where: 'id = ?', whereArgs: [invoiceData['id'] as int]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> deleteInvoice(int id) async {
|
||||||
|
final db = await database;
|
||||||
|
return await db.delete('invoices', where: 'id = ?', whereArgs: [id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inventory API
|
||||||
Future<int> insertInventory(Map<String, dynamic> inventoryData) async {
|
Future<int> insertInventory(Map<String, dynamic> inventoryData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.insert('inventory', inventoryData);
|
return await db.insert('inventory', inventoryData);
|
||||||
|
|
@ -203,12 +174,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
Future<int> updateInventory(Map<String, dynamic> inventoryData) async {
|
Future<int> updateInventory(Map<String, dynamic> inventoryData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.update(
|
return await db.update('inventory', inventoryData, where: 'id = ?', whereArgs: [inventoryData['id'] as int]);
|
||||||
'inventory',
|
|
||||||
inventoryData,
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [inventoryData['id'] as int],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteInventory(int id) async {
|
Future<int> deleteInventory(int id) async {
|
||||||
|
|
@ -216,6 +182,7 @@ class DatabaseHelper {
|
||||||
return await db.delete('inventory', where: 'id = ?', whereArgs: [id]);
|
return await db.delete('inventory', where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Employee API
|
||||||
Future<int> insertEmployee(Map<String, dynamic> employeeData) async {
|
Future<int> insertEmployee(Map<String, dynamic> employeeData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.insert('employees', employeeData);
|
return await db.insert('employees', employeeData);
|
||||||
|
|
@ -228,12 +195,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
Future<int> updateEmployee(Map<String, dynamic> employeeData) async {
|
Future<int> updateEmployee(Map<String, dynamic> employeeData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.update(
|
return await db.update('employees', employeeData, where: 'id = ?', whereArgs: [employeeData['id'] as int]);
|
||||||
'employees',
|
|
||||||
employeeData,
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [employeeData['id'] as int],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteEmployee(int id) async {
|
Future<int> deleteEmployee(int id) async {
|
||||||
|
|
@ -241,6 +203,7 @@ class DatabaseHelper {
|
||||||
return await db.delete('employees', where: 'id = ?', whereArgs: [id]);
|
return await db.delete('employees', where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warehouse API
|
||||||
Future<int> insertWarehouse(Map<String, dynamic> warehouseData) async {
|
Future<int> insertWarehouse(Map<String, dynamic> warehouseData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.insert('warehouses', warehouseData);
|
return await db.insert('warehouses', warehouseData);
|
||||||
|
|
@ -253,12 +216,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
Future<int> updateWarehouse(Map<String, dynamic> warehouseData) async {
|
Future<int> updateWarehouse(Map<String, dynamic> warehouseData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.update(
|
return await db.update('warehouses', warehouseData, where: 'id = ?', whereArgs: [warehouseData['id'] as int]);
|
||||||
'warehouses',
|
|
||||||
warehouseData,
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [warehouseData['id'] as int],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteWarehouse(int id) async {
|
Future<int> deleteWarehouse(int id) async {
|
||||||
|
|
@ -266,6 +224,7 @@ class DatabaseHelper {
|
||||||
return await db.delete('warehouses', where: 'id = ?', whereArgs: [id]);
|
return await db.delete('warehouses', where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Supplier API
|
||||||
Future<int> insertSupplier(Map<String, dynamic> supplierData) async {
|
Future<int> insertSupplier(Map<String, dynamic> supplierData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.insert('suppliers', supplierData);
|
return await db.insert('suppliers', supplierData);
|
||||||
|
|
@ -278,12 +237,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
Future<int> updateSupplier(Map<String, dynamic> supplierData) async {
|
Future<int> updateSupplier(Map<String, dynamic> supplierData) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.update(
|
return await db.update('suppliers', supplierData, where: 'id = ?', whereArgs: [supplierData['id'] as int]);
|
||||||
'suppliers',
|
|
||||||
supplierData,
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [supplierData['id'] as int],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteSupplier(int id) async {
|
Future<int> deleteSupplier(int id) async {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue