Co-authored-by: aider (ollama_chat/7b) <aider@aider.chat>
This commit is contained in:
parent
8ae6d1be62
commit
a8242c2a7e
19 changed files with 774 additions and 0 deletions
25
.github/workflows/flutter.yml
vendored
Normal file
25
.github/workflows/flutter.yml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
name: Flutter CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v1
|
||||
with:
|
||||
flutter-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
- name: Run tests
|
||||
run: flutter test
|
||||
- name: Build the app for release
|
||||
run: flutter build apk --release
|
||||
111
create_invoice_screen.dart
Normal file
111
create_invoice_screen.dart
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'customer_provider.dart';
|
||||
import 'product_provider.dart';
|
||||
import 'invoice_provider.dart';
|
||||
|
||||
class CreateInvoiceScreen extends StatefulWidget {
|
||||
@override
|
||||
_CreateInvoiceScreenState createState() => _CreateInvoiceScreenState();
|
||||
}
|
||||
|
||||
class _CreateInvoiceScreenState extends State<CreateInvoiceScreen> {
|
||||
final TextEditingController _quantityController = TextEditingController();
|
||||
|
||||
int? _selectedCustomerId;
|
||||
int? _selectedProductId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final customerProvider = Provider.of<CustomerProvider>(context);
|
||||
final productProvider = Provider.of<ProductProvider>(context);
|
||||
final invoiceProvider = Provider.of<InvoiceProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('請求書作成'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
DropdownButtonFormField<int>(
|
||||
value: _selectedCustomerId,
|
||||
items: customerProvider.customers.map((customer) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: customer.id,
|
||||
child: Text(customer.name),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_selectedCustomerId = value;
|
||||
});
|
||||
},
|
||||
decoration: InputDecoration(labelText: '顧客選択'),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
DropdownButtonFormField<int>(
|
||||
value: _selectedProductId,
|
||||
items: productProvider.products.map((product) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: product.id,
|
||||
child: Text(product.name),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_selectedProductId = value;
|
||||
});
|
||||
},
|
||||
decoration: InputDecoration(labelText: '商品選択'),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
TextField(
|
||||
controller: _quantityController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(labelText: '数量'),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
if (_selectedCustomerId != null &&
|
||||
_selectedProductId != null &&
|
||||
_quantityController.text.isNotEmpty) {
|
||||
final quantity = int.parse(_quantityController.text);
|
||||
final product = productProvider.products.firstWhere((p) => p.id == _selectedProductId);
|
||||
|
||||
final invoiceItem = InvoiceItem(
|
||||
invoiceId: 0, // 後で生成される ID
|
||||
productId: _selectedProductId!,
|
||||
quantity: quantity,
|
||||
unitPrice: product.unitPrice,
|
||||
discount: product.discount,
|
||||
);
|
||||
|
||||
final total = (product.unitPrice * quantity) - (product.unitPrice * quantity * product.discount);
|
||||
final tax = total * 0.1; // 簡易的な税率
|
||||
final discountTotal = product.unitPrice * quantity * product.discount;
|
||||
|
||||
final invoice = Invoice(
|
||||
customerId: _selectedCustomerId!,
|
||||
date: DateTime.now().toIso8601String(),
|
||||
total: total,
|
||||
tax: tax,
|
||||
discountTotal: discountTotal,
|
||||
);
|
||||
|
||||
await invoiceProvider.addInvoice(invoice);
|
||||
// 追加の処理(PDF生成など)
|
||||
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text('PDF生成'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
35
customer.dart
Normal file
35
customer.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
class Customer {
|
||||
final int? id;
|
||||
final String name;
|
||||
final String phone;
|
||||
final String address;
|
||||
final String email;
|
||||
|
||||
Customer({
|
||||
this.id,
|
||||
required this.name,
|
||||
required this.phone,
|
||||
required this.address,
|
||||
required this.email,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'phone': phone,
|
||||
'address': address,
|
||||
'email': email,
|
||||
};
|
||||
}
|
||||
|
||||
factory Customer.fromMap(Map<String, dynamic> map) {
|
||||
return Customer(
|
||||
id: map['id'] as int?,
|
||||
name: map['name'] as String,
|
||||
phone: map['phone'] as String,
|
||||
address: map['address'] as String,
|
||||
email: map['email'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
35
customer_list_screen.dart
Normal file
35
customer_list_screen.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'customer_provider.dart';
|
||||
|
||||
class CustomerListScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final customerProvider = Provider.of<CustomerProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('顧客一覧'),
|
||||
),
|
||||
body: FutureBuilder<void>(
|
||||
future: customerProvider.fetchCustomers(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return ListView.builder(
|
||||
itemCount: customerProvider.customers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final customer = customerProvider.customers[index];
|
||||
return ListTile(
|
||||
title: Text(customer.name),
|
||||
subtitle: Text(customer.phone),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
27
customer_provider.dart
Normal file
27
customer_provider.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'db_helper.dart';
|
||||
import 'customer.dart';
|
||||
|
||||
class CustomerProvider with ChangeNotifier {
|
||||
List<Customer> _customers = [];
|
||||
|
||||
List<Customer> get customers => _customers;
|
||||
|
||||
Future<void> fetchCustomers() async {
|
||||
final db = await DbHelper().database;
|
||||
final List<Map<String, dynamic>> maps = await db.query('customers');
|
||||
_customers = List.generate(maps.length, (i) {
|
||||
return Customer.fromMap(maps[i]);
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> addCustomer(Customer customer) async {
|
||||
final db = await DbHelper().database;
|
||||
await db.insert('customers', customer.toMap());
|
||||
fetchCustomers();
|
||||
}
|
||||
|
||||
// 追加の CRUD メソッドを実装
|
||||
}
|
||||
41
customer_test.dart
Normal file
41
customer_test.dart
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'db_helper.dart';
|
||||
import 'customer.dart';
|
||||
|
||||
void main() {
|
||||
group('Customer Tests', () {
|
||||
late Database db;
|
||||
|
||||
setUp(() async {
|
||||
final io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = '${documentsDirectory.path}/test.db';
|
||||
db = await openDatabase(path, version: 1, onCreate: DbHelper()._onCreate);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await db.close();
|
||||
final io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = '${documentsDirectory.path}/test.db';
|
||||
await io.File(path).delete();
|
||||
});
|
||||
|
||||
test('Add and fetch customer', () async {
|
||||
final customer = Customer(
|
||||
name: 'Test Customer',
|
||||
phone: '1234567890',
|
||||
address: 'Test Address',
|
||||
email: 'test@example.com',
|
||||
);
|
||||
|
||||
await db.insert('customers', customer.toMap());
|
||||
|
||||
final List<Map<String, dynamic>> maps = await db.query('customers');
|
||||
expect(maps.length, 1);
|
||||
|
||||
final fetchedCustomer = Customer.fromMap(maps.first);
|
||||
expect(fetchedCustomer.name, 'Test Customer');
|
||||
});
|
||||
});
|
||||
}
|
||||
63
db_helper.dart
Normal file
63
db_helper.dart
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:io' as io;
|
||||
|
||||
class DbHelper {
|
||||
static Database? _database;
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database!;
|
||||
_database = await initDb();
|
||||
return _database!;
|
||||
}
|
||||
|
||||
initDb() async {
|
||||
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = '${documentsDirectory.path}/invoice.db';
|
||||
var db = await openDatabase(path, version: 1, onCreate: _onCreate);
|
||||
return db;
|
||||
}
|
||||
|
||||
void _onCreate(Database db, int version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE customers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT,
|
||||
phone TEXT,
|
||||
address TEXT,
|
||||
email TEXT
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE products (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT,
|
||||
unit_price REAL,
|
||||
discount REAL
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE invoices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
customer_id INTEGER,
|
||||
date TEXT,
|
||||
total REAL,
|
||||
tax REAL,
|
||||
discount_total REAL
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE invoice_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
invoice_id INTEGER,
|
||||
product_id INTEGER,
|
||||
quantity INTEGER,
|
||||
unit_price REAL,
|
||||
discount REAL
|
||||
)
|
||||
''');
|
||||
}
|
||||
}
|
||||
39
invoice.dart
Normal file
39
invoice.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
class Invoice {
|
||||
final int? id;
|
||||
final int customerId;
|
||||
final String date;
|
||||
final double total;
|
||||
final double tax;
|
||||
final double discountTotal;
|
||||
|
||||
Invoice({
|
||||
this.id,
|
||||
required this.customerId,
|
||||
required this.date,
|
||||
required this.total,
|
||||
required this.tax,
|
||||
required this.discountTotal,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'customer_id': customerId,
|
||||
'date': date,
|
||||
'total': total,
|
||||
'tax': tax,
|
||||
'discount_total': discountTotal,
|
||||
};
|
||||
}
|
||||
|
||||
factory Invoice.fromMap(Map<String, dynamic> map) {
|
||||
return Invoice(
|
||||
id: map['id'] as int?,
|
||||
customerId: map['customer_id'] as int,
|
||||
date: map['date'] as String,
|
||||
total: map['total'] as double,
|
||||
tax: map['tax'] as double,
|
||||
discountTotal: map['discount_total'] as double,
|
||||
);
|
||||
}
|
||||
}
|
||||
39
invoice_item.dart
Normal file
39
invoice_item.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
class InvoiceItem {
|
||||
final int? id;
|
||||
final int invoiceId;
|
||||
final int productId;
|
||||
final int quantity;
|
||||
final double unitPrice;
|
||||
final double discount;
|
||||
|
||||
InvoiceItem({
|
||||
this.id,
|
||||
required this.invoiceId,
|
||||
required this.productId,
|
||||
required this.quantity,
|
||||
required this.unitPrice,
|
||||
required this.discount,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'invoice_id': invoiceId,
|
||||
'product_id': productId,
|
||||
'quantity': quantity,
|
||||
'unit_price': unitPrice,
|
||||
'discount': discount,
|
||||
};
|
||||
}
|
||||
|
||||
factory InvoiceItem.fromMap(Map<String, dynamic> map) {
|
||||
return InvoiceItem(
|
||||
id: map['id'] as int?,
|
||||
invoiceId: map['invoice_id'] as int,
|
||||
productId: map['product_id'] as int,
|
||||
quantity: map['quantity'] as int,
|
||||
unitPrice: map['unit_price'] as double,
|
||||
discount: map['discount'] as double,
|
||||
);
|
||||
}
|
||||
}
|
||||
27
invoice_provider.dart
Normal file
27
invoice_provider.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'db_helper.dart';
|
||||
import 'invoice.dart';
|
||||
|
||||
class InvoiceProvider with ChangeNotifier {
|
||||
List<Invoice> _invoices = [];
|
||||
|
||||
List<Invoice> get invoices => _invoices;
|
||||
|
||||
Future<void> fetchInvoices() async {
|
||||
final db = await DbHelper().database;
|
||||
final List<Map<String, dynamic>> maps = await db.query('invoices');
|
||||
_invoices = List.generate(maps.length, (i) {
|
||||
return Invoice.fromMap(maps[i]);
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> addInvoice(Invoice invoice) async {
|
||||
final db = await DbHelper().database;
|
||||
await db.insert('invoices', invoice.toMap());
|
||||
fetchInvoices();
|
||||
}
|
||||
|
||||
// 追加の CRUD メソッドを実装
|
||||
}
|
||||
42
invoice_test.dart
Normal file
42
invoice_test.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'db_helper.dart';
|
||||
import 'invoice.dart';
|
||||
|
||||
void main() {
|
||||
group('Invoice Tests', () {
|
||||
late Database db;
|
||||
|
||||
setUp(() async {
|
||||
final io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = '${documentsDirectory.path}/test.db';
|
||||
db = await openDatabase(path, version: 1, onCreate: DbHelper()._onCreate);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await db.close();
|
||||
final io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = '${documentsDirectory.path}/test.db';
|
||||
await io.File(path).delete();
|
||||
});
|
||||
|
||||
test('Add and fetch invoice', () async {
|
||||
final invoice = Invoice(
|
||||
customerId: 1,
|
||||
date: DateTime.now().toIso8601String(),
|
||||
total: 90.0,
|
||||
tax: 9.0,
|
||||
discountTotal: 10.0,
|
||||
);
|
||||
|
||||
await db.insert('invoices', invoice.toMap());
|
||||
|
||||
final List<Map<String, dynamic>> maps = await db.query('invoices');
|
||||
expect(maps.length, 1);
|
||||
|
||||
final fetchedInvoice = Invoice.fromMap(maps.first);
|
||||
expect(fetchedInvoice.customerId, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
66
main.dart
Normal file
66
main.dart
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'customer_provider.dart';
|
||||
import 'product_provider.dart';
|
||||
import 'invoice_provider.dart';
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => CustomerProvider()),
|
||||
ChangeNotifierProvider(create: (_) => ProductProvider()),
|
||||
ChangeNotifierProvider(create: (_) => InvoiceProvider()),
|
||||
],
|
||||
child: MaterialApp(
|
||||
title: 'Invoice App',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: HomeScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Invoice App'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, '/create_invoice');
|
||||
},
|
||||
child: Text('請求書作成'),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, '/customer_list');
|
||||
},
|
||||
child: Text('顧客一覧'),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, '/product_list');
|
||||
},
|
||||
child: Text('商品一覧'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
2
path/to/filename.js
Normal file
2
path/to/filename.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// entire file content ...
|
||||
// ... goes in between
|
||||
67
pdf_generator.dart
Normal file
67
pdf_generator.dart
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:printing/printing.dart';
|
||||
|
||||
class PdfGenerator {
|
||||
static Future<void> generatePdf(Invoice invoice, List<InvoiceItem> items) async {
|
||||
final pdf = pw.Document();
|
||||
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
build: (pw.Context context) => pw.Column(
|
||||
children: [
|
||||
pw.Text('請求書'),
|
||||
pw.SizedBox(height: 20),
|
||||
pw.Text('顧客名: ${invoice.customerId}'), // 後で顧客名に変更
|
||||
pw.Text('日付: ${invoice.date}'),
|
||||
pw.SizedBox(height: 20),
|
||||
pw.Table(
|
||||
border: pw.TableBorder.all(),
|
||||
children: [
|
||||
pw.TableRow(children: [
|
||||
pw.Text('商品名'),
|
||||
pw.Text('数量'),
|
||||
pw.Text('単価'),
|
||||
pw.Text('値引き'),
|
||||
pw.Text('小計'),
|
||||
]),
|
||||
for (var item in items)
|
||||
pw.TableRow(children: [
|
||||
pw.Text(item.productId.toString()), // 後で商品名に変更
|
||||
pw.Text(item.quantity.toString()),
|
||||
pw.Text(item.unitPrice.toStringAsFixed(2)),
|
||||
pw.Text((item.discount * 100).toStringAsFixed(2) + '%'),
|
||||
pw.Text((item.unitPrice * item.quantity - (item.unitPrice * item.quantity * item.discount)).toStringAsFixed(2)),
|
||||
]),
|
||||
],
|
||||
),
|
||||
pw.SizedBox(height: 20),
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Text('合計'),
|
||||
pw.Text(invoice.total.toStringAsFixed(2)),
|
||||
],
|
||||
),
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Text('税額'),
|
||||
pw.Text(invoice.tax.toStringAsFixed(2)),
|
||||
],
|
||||
),
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Text('値引き合計'),
|
||||
pw.Text(invoice.discountTotal.toStringAsFixed(2)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await Printing.sharePdf(bytes: pdf.save(), filename: 'invoice.pdf');
|
||||
}
|
||||
}
|
||||
31
product.dart
Normal file
31
product.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
class Product {
|
||||
final int? id;
|
||||
final String name;
|
||||
final double unitPrice;
|
||||
final double discount;
|
||||
|
||||
Product({
|
||||
this.id,
|
||||
required this.name,
|
||||
required this.unitPrice,
|
||||
required this.discount,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'unit_price': unitPrice,
|
||||
'discount': discount,
|
||||
};
|
||||
}
|
||||
|
||||
factory Product.fromMap(Map<String, dynamic> map) {
|
||||
return Product(
|
||||
id: map['id'] as int?,
|
||||
name: map['name'] as String,
|
||||
unitPrice: map['unit_price'] as double,
|
||||
discount: map['discount'] as double,
|
||||
);
|
||||
}
|
||||
}
|
||||
35
product_list_screen.dart
Normal file
35
product_list_screen.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'product_provider.dart';
|
||||
|
||||
class ProductListScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final productProvider = Provider.of<ProductProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('商品一覧'),
|
||||
),
|
||||
body: FutureBuilder<void>(
|
||||
future: productProvider.fetchProducts(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return ListView.builder(
|
||||
itemCount: productProvider.products.length,
|
||||
itemBuilder: (context, index) {
|
||||
final product = productProvider.products[index];
|
||||
return ListTile(
|
||||
title: Text(product.name),
|
||||
subtitle: Text('単価: ${product.unitPrice}, 値引き: ${product.discount}'),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
27
product_provider.dart
Normal file
27
product_provider.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'db_helper.dart';
|
||||
import 'product.dart';
|
||||
|
||||
class ProductProvider with ChangeNotifier {
|
||||
List<Product> _products = [];
|
||||
|
||||
List<Product> get products => _products;
|
||||
|
||||
Future<void> fetchProducts() async {
|
||||
final db = await DbHelper().database;
|
||||
final List<Map<String, dynamic>> maps = await db.query('products');
|
||||
_products = List.generate(maps.length, (i) {
|
||||
return Product.fromMap(maps[i]);
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> addProduct(Product product) async {
|
||||
final db = await DbHelper().database;
|
||||
await db.insert('products', product.toMap());
|
||||
fetchProducts();
|
||||
}
|
||||
|
||||
// 追加の CRUD メソッドを実装
|
||||
}
|
||||
40
product_test.dart
Normal file
40
product_test.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'db_helper.dart';
|
||||
import 'product.dart';
|
||||
|
||||
void main() {
|
||||
group('Product Tests', () {
|
||||
late Database db;
|
||||
|
||||
setUp(() async {
|
||||
final io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = '${documentsDirectory.path}/test.db';
|
||||
db = await openDatabase(path, version: 1, onCreate: DbHelper()._onCreate);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await db.close();
|
||||
final io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = '${documentsDirectory.path}/test.db';
|
||||
await io.File(path).delete();
|
||||
});
|
||||
|
||||
test('Add and fetch product', () async {
|
||||
final product = Product(
|
||||
name: 'Test Product',
|
||||
unitPrice: 100.0,
|
||||
discount: 0.1,
|
||||
);
|
||||
|
||||
await db.insert('products', product.toMap());
|
||||
|
||||
final List<Map<String, dynamic>> maps = await db.query('products');
|
||||
expect(maps.length, 1);
|
||||
|
||||
final fetchedProduct = Product.fromMap(maps.first);
|
||||
expect(fetchedProduct.name, 'Test Product');
|
||||
});
|
||||
});
|
||||
}
|
||||
22
pubspec.yaml
Normal file
22
pubspec.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name: invoice_app
|
||||
description: A new Flutter project.
|
||||
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
sqflite: ^2.0.0+3
|
||||
path_provider: ^2.0.11
|
||||
pdf: ^3.6.0
|
||||
printing: ^5.9.3
|
||||
provider: ^6.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
Loading…
Reference in a new issue