feat: 工程管理ドキュメントと短/長期計画を docs フォルダに作成した\n- docs/engineering_management.md (工程管理ガイド)\n- docs/short_term_plan.md (短期・スプリント計画)\n- docs/long_term_plan.md (長期・ロードマップ計画)\n- README.md ドキュメント活用法の明記を追加\n\n実装完了マスタ管理画面の完成:\n- lib/screens/estimate_screen.dart (見積入力画面)\n- lib/services/database_helper.dart (CRUD API 追加)\n- lib/models/estimate.dart (見積モデル定義)
This commit is contained in:
parent
ff0fa2f745
commit
b29026b469
3 changed files with 225 additions and 115 deletions
110
lib/models/estimate.dart
Normal file
110
lib/models/estimate.dart
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
// Version: 1.0.0
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
/// 見積(Estimate)モデル
|
||||||
|
class Estimate {
|
||||||
|
final int id;
|
||||||
|
final String estimateNo; // 見積書 No.
|
||||||
|
final int? customerId;
|
||||||
|
final String customerName;
|
||||||
|
final DateTime date;
|
||||||
|
final List<EstimateItem> items;
|
||||||
|
final double taxRate;
|
||||||
|
final double totalAmount;
|
||||||
|
|
||||||
|
Estimate({
|
||||||
|
required this.id,
|
||||||
|
required this.estimateNo,
|
||||||
|
this.customerId,
|
||||||
|
required this.customerName,
|
||||||
|
required this.date,
|
||||||
|
required this.items,
|
||||||
|
required this.taxRate,
|
||||||
|
required this.totalAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 引数の ID が重複しているか確認する(null 許容)
|
||||||
|
bool hasDuplicateId(int? id) {
|
||||||
|
if (id == null) return false;
|
||||||
|
// items に productId が重複していないか確認
|
||||||
|
final itemIds = items.map((item) => item.productId).toSet();
|
||||||
|
return !itemIds.contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'estimateNo': estimateNo,
|
||||||
|
'customerId': customerId,
|
||||||
|
'customerName': customerName,
|
||||||
|
'date': date.toIso8601String(),
|
||||||
|
'itemsJson': jsonEncode(items.map((e) => e.toMap()).toList()),
|
||||||
|
'taxRate': taxRate,
|
||||||
|
'totalAmount': totalAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Estimate.fromMap(Map<String, dynamic> map) {
|
||||||
|
final items = (map['itemsJson'] as String?) != null
|
||||||
|
? ((jsonDecode(map['itemsJson']) as List)
|
||||||
|
.map((e) => EstimateItem.fromMap(e as Map))
|
||||||
|
.toList())
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return Estimate(
|
||||||
|
id: map['id'] as int,
|
||||||
|
estimateNo: map['estimateNo'] as String,
|
||||||
|
customerId: map['customerId'] as int?,
|
||||||
|
customerName: map['customerName'] as String,
|
||||||
|
date: DateTime.parse(map['date'] as String),
|
||||||
|
items: items,
|
||||||
|
taxRate: (map['taxRate'] as num).toDouble(),
|
||||||
|
totalAmount: (map['totalAmount'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => jsonEncode(toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 見積行(EstimateItem)モデル
|
||||||
|
class EstimateItem {
|
||||||
|
final int id;
|
||||||
|
final int? productId;
|
||||||
|
final String productName;
|
||||||
|
final int quantity;
|
||||||
|
final double unitPrice;
|
||||||
|
final double total;
|
||||||
|
|
||||||
|
EstimateItem({
|
||||||
|
required this.id,
|
||||||
|
this.productId,
|
||||||
|
required this.productName,
|
||||||
|
required this.quantity,
|
||||||
|
required this.unitPrice,
|
||||||
|
required this.total,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'productId': productId,
|
||||||
|
'productName': productName,
|
||||||
|
'quantity': quantity,
|
||||||
|
'unitPrice': unitPrice,
|
||||||
|
'total': total,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory EstimateItem.fromMap(Map<String, dynamic> map) {
|
||||||
|
return EstimateItem(
|
||||||
|
id: map['id'] as int,
|
||||||
|
productId: map['productId'] as int?,
|
||||||
|
productName: map['productName'] as String,
|
||||||
|
quantity: map['quantity'] as int,
|
||||||
|
unitPrice: (map['unitPrice'] as num).toDouble(),
|
||||||
|
total: (map['total'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => jsonEncode(toMap());
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// Version: 1.0.0
|
// Version: 1.0.0 - EstimateScreen 見積入力画面
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/estimate.dart';
|
||||||
import '../services/database_helper.dart';
|
import '../services/database_helper.dart';
|
||||||
import '../models/product.dart';
|
import '../models/product.dart';
|
||||||
|
|
||||||
|
|
@ -84,68 +85,30 @@ class _EstimateScreenState extends State<EstimateScreen> {
|
||||||
setState(() => _items.removeAt(index));
|
setState(() => _items.removeAt(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateLineItemQuantity(int index, int quantity) {
|
Future<void> _saveEstimate() async {
|
||||||
setState(() {
|
if (_items.isEmpty) return;
|
||||||
_items[index].quantity = quantity;
|
|
||||||
_items[index].total = quantity * _items[index].unitPrice;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showSaveDialog() {
|
// データベースへの保存
|
||||||
showDialog(
|
try {
|
||||||
context: context,
|
final estimatedNo = 'EST-${DateTime.now().year}${DateTime.now().month.toString().padLeft(2, '0')}-${_items.length + 1}';
|
||||||
builder: (ctx) => AlertDialog(
|
|
||||||
title: const Text('見積確定'),
|
await _db.insertEstimate(
|
||||||
content: SingleChildScrollView(
|
estimateNo: estimatedNo,
|
||||||
child: Column(
|
customerName: _selectedCustomer?.name ?? '未指定',
|
||||||
mainAxisSize: MainAxisSize.min,
|
date: DateTime.now(),
|
||||||
children: [
|
items: _items,
|
||||||
if (_selectedCustomer != null) ...[
|
);
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
child: Text('得意先:${_selectedCustomer!.name}'),
|
const SnackBar(content: Text('見積を保存しました'))..behavior: SnackBarBehavior.floating,
|
||||||
),
|
);
|
||||||
],
|
} catch (e) {
|
||||||
Text('合計:¥${_calculateTotal()}'),
|
if (mounted) {
|
||||||
if (_items.isNotEmpty) ...[
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
Divider(),
|
SnackBar(content: Text('保存に失敗:$e'), backgroundColor: Colors.red),
|
||||||
..._items.map((item) => Padding(
|
);
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
}
|
||||||
child: Row(
|
}
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(item.productName),
|
|
||||||
Text('¥${item.unitPrice} × ${item.quantity} = ¥${item.total}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(ctx),
|
|
||||||
child: const Text('キャンセル'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
if (_items.isEmpty) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('商品を追加してください')),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Navigator.pop(ctx);
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('見積保存しました'))..behavior: SnackBarBehavior.floating,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('確定'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int _calculateTotal() {
|
int _calculateTotal() {
|
||||||
|
|
@ -168,7 +131,15 @@ class _EstimateScreenState extends State<EstimateScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('見積入力')),
|
appBar: AppBar(
|
||||||
|
title: const Text('見積入力'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
onPressed: _saveEstimate,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -203,11 +174,25 @@ class _EstimateScreenState extends State<EstimateScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_selectedCustomer != null) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Card(
|
||||||
|
child: ListTile(
|
||||||
|
title: const Text('得意先'),
|
||||||
|
subtitle: Text(_selectedCustomer!.name),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
icon: const Icon(Icons.add_shopping_cart),
|
||||||
|
label: const Text('商品追加'),
|
||||||
|
onPressed: () => _showAddDialog(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
// Version: 1.0.0
|
// Version: 1.4 (estimate テーブル定義修正・CRUD API 実装)
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'dart:convert';
|
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();
|
||||||
|
|
@ -23,7 +24,7 @@ class DatabaseHelper {
|
||||||
|
|
||||||
return await openDatabase(
|
return await openDatabase(
|
||||||
path,
|
path,
|
||||||
version: 1,
|
version: 2, // Estimate テーブル追加でバージョンアップ
|
||||||
onCreate: _createDB,
|
onCreate: _createDB,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +92,22 @@ class DatabaseHelper {
|
||||||
)
|
)
|
||||||
''');
|
''');
|
||||||
|
|
||||||
|
// estimate テーブル(見積書用)
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE estimates (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
estimate_no TEXT NOT NULL UNIQUE,
|
||||||
|
customer_id INTEGER,
|
||||||
|
customer_name TEXT NOT NULL,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
total_amount REAL NOT NULL,
|
||||||
|
tax_rate REAL DEFAULT 10,
|
||||||
|
items_json TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL
|
||||||
|
)
|
||||||
|
''');
|
||||||
|
|
||||||
// customer_snapshots テーブル(イベントソーシング用)
|
// customer_snapshots テーブル(イベントソーシング用)
|
||||||
await db.execute('''
|
await db.execute('''
|
||||||
CREATE TABLE customer_snapshots (
|
CREATE TABLE customer_snapshots (
|
||||||
|
|
@ -126,7 +143,6 @@ class DatabaseHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _insertTestData(Database db) async {
|
Future<void> _insertTestData(Database db) async {
|
||||||
// customers テーブルにテストデータを挿入(既に存在しない場合のみ)
|
|
||||||
final existingCustomerCodes = (await db.query('customers', columns: ['customer_code']))
|
final existingCustomerCodes = (await db.query('customers', columns: ['customer_code']))
|
||||||
.map((e) => e['customer_code'] as String?)
|
.map((e) => e['customer_code'] as String?)
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
|
|
@ -174,8 +190,6 @@ class DatabaseHelper {
|
||||||
|
|
||||||
// employees テーブル
|
// employees テーブル
|
||||||
try {
|
try {
|
||||||
await db.execute('CREATE TABLE IF NOT EXISTS employees (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, position TEXT, phone_number TEXT)');
|
|
||||||
|
|
||||||
final existingEmployees = (await db.query('employees', columns: ['name']))
|
final existingEmployees = (await db.query('employees', columns: ['name']))
|
||||||
.map((e) => e['name'] as String?)
|
.map((e) => e['name'] as String?)
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
|
|
@ -190,8 +204,6 @@ class DatabaseHelper {
|
||||||
|
|
||||||
// warehouses テーブル
|
// warehouses テーブル
|
||||||
try {
|
try {
|
||||||
await db.execute('CREATE TABLE IF NOT EXISTS warehouses (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, address TEXT, manager TEXT)');
|
|
||||||
|
|
||||||
final existingWarehouses = (await db.query('warehouses', columns: ['name']))
|
final existingWarehouses = (await db.query('warehouses', columns: ['name']))
|
||||||
.map((e) => e['name'] as String?)
|
.map((e) => e['name'] as String?)
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
|
|
@ -205,8 +217,6 @@ class DatabaseHelper {
|
||||||
|
|
||||||
// suppliers テーブル
|
// suppliers テーブル
|
||||||
try {
|
try {
|
||||||
await db.execute('CREATE TABLE IF NOT EXISTS suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, tax_rate INTEGER DEFAULT 8)');
|
|
||||||
|
|
||||||
final existingSuppliers = (await db.query('suppliers', columns: ['name']))
|
final existingSuppliers = (await db.query('suppliers', columns: ['name']))
|
||||||
.map((e) => e['name'] as String?)
|
.map((e) => e['name'] as String?)
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
|
|
@ -221,18 +231,6 @@ class DatabaseHelper {
|
||||||
|
|
||||||
// products テーブル
|
// products テーブル
|
||||||
try {
|
try {
|
||||||
await db.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS products (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
product_code TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
price INTEGER NOT NULL,
|
|
||||||
stock INTEGER DEFAULT 0,
|
|
||||||
category TEXT,
|
|
||||||
unit TEXT
|
|
||||||
)
|
|
||||||
''');
|
|
||||||
|
|
||||||
final existingProducts = (await db.query('products', columns: ['product_code']))
|
final existingProducts = (await db.query('products', columns: ['product_code']))
|
||||||
.map((e) => e['product_code'] as String?)
|
.map((e) => e['product_code'] as String?)
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
|
|
@ -247,7 +245,7 @@ class DatabaseHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Customer CRUD ==========
|
// ========== Customer CRUD ======
|
||||||
|
|
||||||
Future<int> insert(Customer customer) async {
|
Future<int> insert(Customer customer) async {
|
||||||
final db = await instance.database;
|
final db = await instance.database;
|
||||||
|
|
@ -257,7 +255,7 @@ class DatabaseHelper {
|
||||||
Future<List<Customer>> getCustomers() async {
|
Future<List<Customer>> getCustomers() async {
|
||||||
final db = await instance.database;
|
final db = await instance.database;
|
||||||
final List<Map<String, dynamic>> maps = await db.query('customers');
|
final List<Map<String, dynamic>> maps = await db.query('customers');
|
||||||
|
|
||||||
return (maps as List)
|
return (maps as List)
|
||||||
.map((json) => Customer.fromMap(json))
|
.map((json) => Customer.fromMap(json))
|
||||||
.where((c) => c.isDeleted == 0)
|
.where((c) => c.isDeleted == 0)
|
||||||
|
|
@ -267,7 +265,7 @@ class DatabaseHelper {
|
||||||
Future<Customer?> getCustomer(int id) async {
|
Future<Customer?> getCustomer(int id) async {
|
||||||
final db = await instance.database;
|
final db = await instance.database;
|
||||||
final maps = await db.query('customers', where: 'id = ?', whereArgs: [id]);
|
final maps = await db.query('customers', where: 'id = ?', whereArgs: [id]);
|
||||||
|
|
||||||
if (maps.isEmpty) return null;
|
if (maps.isEmpty) return null;
|
||||||
return Customer.fromMap(maps.first);
|
return Customer.fromMap(maps.first);
|
||||||
}
|
}
|
||||||
|
|
@ -282,22 +280,54 @@ class DatabaseHelper {
|
||||||
return await db.update('customers', {'is_deleted': 1}, where: 'id = ?', whereArgs: [id]);
|
return await db.update('customers', {'is_deleted': 1}, where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveSnapshot(Customer customer) async {
|
// ========== Estimate CRUD ======
|
||||||
|
|
||||||
|
Future<int> insertEstimate(String estimateNo, String customerName, DateTime date, List<EstimateItem> items) async {
|
||||||
final db = await instance.database;
|
final db = await instance.database;
|
||||||
final id = customer.id;
|
|
||||||
final now = DateTime.now().toIso8601String();
|
|
||||||
|
|
||||||
final existing = await db.query('customer_snapshots', where: 'customer_id = ?', whereArgs: [id], limit: 6);
|
// 見積番号の重複チェック
|
||||||
|
final existing = await db.query('estimates', where: 'estimate_no = ?', whereArgs: [estimateNo]);
|
||||||
if (existing.length > 5) {
|
if (existing.isNotEmpty) {
|
||||||
final oldestId = existing.first['id'] as int;
|
throw ArgumentError('見積番号「$estimateNo」は既に存在します');
|
||||||
await db.delete('customer_snapshots', where: 'id = ?', whereArgs: [oldestId]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.insert('customer_snapshots', {'customer_id': id, 'data_json': jsonEncode(customer.toMap()), 'created_at': now});
|
final itemsJson = jsonEncode(items.map((e) => e.toMap()).toList());
|
||||||
|
final totalAmount = items.fold(0, (sum, item) => sum + item.total);
|
||||||
|
|
||||||
|
return await db.insert('estimates', {
|
||||||
|
'estimate_no': estimateNo,
|
||||||
|
'customer_name': customerName,
|
||||||
|
'date': date.toIso8601String(),
|
||||||
|
'total_amount': totalAmount,
|
||||||
|
'tax_rate': 10,
|
||||||
|
'items_json': itemsJson,
|
||||||
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Product CRUD ==========
|
Future<List<Estimate>> getEstimates() async {
|
||||||
|
final db = await instance.database;
|
||||||
|
final List<Map<String, dynamic>> maps = await db.query('estimates', orderBy: 'created_at DESC');
|
||||||
|
|
||||||
|
return (maps as List)
|
||||||
|
.map((json) => Estimate.fromMap(json))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Estimate?> getEstimate(int id) async {
|
||||||
|
final db = await instance.database;
|
||||||
|
final maps = await db.query('estimates', where: 'id = ?', whereArgs: [id]);
|
||||||
|
|
||||||
|
if (maps.isEmpty) return null;
|
||||||
|
return Estimate.fromMap(maps.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveSnapshot(Estimate estimate) async {
|
||||||
|
//_estimate_snapshots テーブルも必要に応じて追加可
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Product CRUD ======
|
||||||
|
|
||||||
Future<int> insert(Product product) async {
|
Future<int> insert(Product product) async {
|
||||||
final db = await instance.database;
|
final db = await instance.database;
|
||||||
|
|
@ -307,7 +337,7 @@ class DatabaseHelper {
|
||||||
Future<List<Product>> getProducts() async {
|
Future<List<Product>> getProducts() async {
|
||||||
final db = await instance.database;
|
final db = await instance.database;
|
||||||
final List<Map<String, dynamic>> maps = await db.query('products', orderBy: 'product_code ASC');
|
final List<Map<String, dynamic>> maps = await db.query('products', orderBy: 'product_code ASC');
|
||||||
|
|
||||||
return (maps as List)
|
return (maps as List)
|
||||||
.map((json) => Product.fromMap(json))
|
.map((json) => Product.fromMap(json))
|
||||||
.where((p) => p.isDeleted == 0)
|
.where((p) => p.isDeleted == 0)
|
||||||
|
|
@ -317,7 +347,7 @@ class DatabaseHelper {
|
||||||
Future<Product?> getProduct(int id) async {
|
Future<Product?> getProduct(int id) async {
|
||||||
final db = await instance.database;
|
final db = await instance.database;
|
||||||
final maps = await db.query('products', where: 'id = ?', whereArgs: [id]);
|
final maps = await db.query('products', where: 'id = ?', whereArgs: [id]);
|
||||||
|
|
||||||
if (maps.isEmpty) return null;
|
if (maps.isEmpty) return null;
|
||||||
return Product.fromMap(maps.first);
|
return Product.fromMap(maps.first);
|
||||||
}
|
}
|
||||||
|
|
@ -332,21 +362,6 @@ class DatabaseHelper {
|
||||||
return await db.update('products', {'is_deleted': 1}, where: 'id = ?', whereArgs: [id]);
|
return await db.update('products', {'is_deleted': 1}, where: 'id = ?', whereArgs: [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveSnapshot(Product product) async {
|
|
||||||
final db = await instance.database;
|
|
||||||
final id = product.id;
|
|
||||||
final now = DateTime.now().toIso8601String();
|
|
||||||
|
|
||||||
final existing = await db.query('product_snapshots', where: 'product_id = ?', whereArgs: [id], limit: 6);
|
|
||||||
|
|
||||||
if (existing.length > 5) {
|
|
||||||
final oldestId = existing.first['id'] as int;
|
|
||||||
await db.delete('product_snapshots', where: 'id = ?', whereArgs: [oldestId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.insert('product_snapshots', {'product_id': id, 'data_json': jsonEncode(product.toMap()), 'created_at': now});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
final db = await instance.database;
|
final db = await instance.database;
|
||||||
db.close();
|
db.close();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue