Compare commits

..

36 commits

Author SHA1 Message Date
Frank Denzer
fdabfcf741 Merge branch 'backend'
# Conflicts:
#	.gitignore
#	db_scripts/create.sql
2023-07-16 19:32:47 +02:00
Frank Denzer
b3274f1a2a amend gitignore for server 2023-07-16 15:19:20 +02:00
Frank Denzer
8f6c4ac81a start server project 2023-07-16 15:15:47 +02:00
Frank Denzer
70818931aa fix readme 2023-07-15 16:18:38 +02:00
Frank Denzer
3bfab0bcae fix readme 2023-07-15 16:17:58 +02:00
Frank Denzer
f3aac41876 add readme content: branches 2023-07-15 16:09:26 +02:00
Frank Denzer
220025e109 add readme content: branches 2023-07-15 16:09:03 +02:00
7388527c43 merged a simple logic for the basket 2023-07-04 22:07:00 +02:00
f0415a22da added default locale 2023-06-27 00:48:14 +02:00
496a1f2857 changed Readme 2023-06-26 18:05:46 +02:00
e0b571a808 added settings 2023-06-26 17:42:09 +02:00
e0aa77fb96 added settings.dart 2023-06-26 17:43:33 +02:00
2f84e61ba1 edited Readme 2023-06-26 17:08:32 +02:00
df255d71b8 product categories and other improvements 2023-06-25 21:11:10 +02:00
000f141b5a prototype for shopping tab 2023-06-25 19:10:29 +02:00
4da346f6d7 added prototype for editing baskets 2023-06-25 18:11:26 +02:00
0ee26fedb0 added a prototype for a basket 2023-06-25 15:11:23 +02:00
7c9d449c63 added Pfand to sample data 2023-06-25 14:01:19 +02:00
4bbd70464f restructured sample data again 2023-06-25 12:04:17 +02:00
Frank Denzer
597db5d51f mv logo to shared since no flutter_svg 2023-06-25 10:43:50 +02:00
Frank Denzer
05b12991c2 add svg version of logo 2023-06-25 10:33:18 +02:00
26d0735bcf Trash-Filter aktualisiert 2023-06-23 14:43:54 +02:00
d9bf3c4fea restructured sample data 2023-06-23 13:03:17 +02:00
d97f1fbb1c added price for basket 2023-06-23 12:12:46 +02:00
6c19b77768 fix two kinds of typos
bezeichung was written without n before ung
insert or ignore into consistently, but was `insert`, or `insert or into` in previous drafts
2023-06-23 11:14:57 +02:00
cf21ead3e2 fix inconsistency 2023-06-23 11:14:57 +02:00
ee6bb34405 Umstellung auf Sqlite
* Anpassung des Erstellungsscripts
* Beschreibung des Zugriffs und Einlesen der Datei (momentan noch nicht funktional)
2023-06-23 11:14:57 +02:00
ebd05ca96c Aufsetzen von MariaDB 2023-06-23 11:14:57 +02:00
45f76eab17 adding a structure for future basket 2023-06-23 10:54:25 +02:00
3ab6cd5690 design improvements 2023-06-19 20:29:17 +02:00
f083ab8cf0 changed design of the finance tab 2023-06-19 19:37:06 +02:00
893b421fe3 extremely simplyfied code 2023-06-19 16:27:07 +02:00
f343214df0 extremely simplyfied code 2023-06-19 16:25:10 +02:00
99282eb9da bugfix for finance cards 2023-06-19 12:12:18 +02:00
df0fabce0f kleine Änderungen am Design 2023-06-19 11:21:54 +02:00
5ad2ae0867 remake finance, included assets 2023-06-16 15:36:13 +02:00
20 changed files with 1021 additions and 125 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
.idea .idea
db/* db/*
backend/target/*

View file

@ -14,3 +14,21 @@ A few resources to get you started if this is your first Flutter project:
For help getting started with Flutter development, view the For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials, [online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference. samples, guidance on mobile development, and a full API reference.
## toDo
- Einstellungen
- darkmode
- Secure Storage: https://pub.dev/packages/flutter_secure_storage
- Einkauf
- Finance
- Einkaufskorb editieren
- nur 1 Monat lang möglich
- Speichern des veränderten Warenkorbs
- redesign (statefull)
- farbliche Hervorhebungen
- ausstehende Beträge für monatliche Aufladungen
- eventuell muss sample data angepasst werden
- pre-Release:
- semanticLabels, Screenreader-Support
- Error-Management (throws)

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 KiB

View file

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'sample_data.dart';
class ShowBasket extends StatelessWidget {
final Basket basket;
final bool editable;
const ShowBasket(this.basket, {this.editable = false, super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: getBasket(basket),
),
const Divider(),
ListTile(
leading: const Icon(
Icons.euro,
),
title: const Text(
'Kosten:',
),
subtitle: Text(
'${basket.price}',
),
trailing: editable
? Row(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
backgroundColor: Colors.redAccent.shade100,
child: const Icon(Icons.remove_shopping_cart),
onPressed: () {}),
const SizedBox(
width: 10,
),
FloatingActionButton(
child: const Icon(Icons.shopping_cart_checkout),
onPressed: () {}),
],
)
: null),
],
);
}
ListView getBasket(Basket basket) {
return ListView.builder(
itemCount: basket.purchases.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
leading: Text(
basket.purchases.keys.elementAt(index).category.icon,
style: const TextStyle(fontSize: 24),
),
title: Text(basket.purchases.keys.elementAt(index).name),
subtitle: Text(
'${basket.purchases.values.elementAt(index)} ${basket.purchases.keys.elementAt(index).unit}'),
trailing: editable
? SizedBox(
width: 100,
child: TextField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
]),
)
: null),
);
},
);
}
}

View file

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
//this was used in a earlier version of the app and will possibly be removed in future versions.
class Expand extends StatefulWidget {
final Widget child;
final bool expand;
const Expand({super.key, this.expand = true, required this.child});
@override
State<Expand> createState() => _ExpandState();
}
class _ExpandState extends State<Expand> with SingleTickerProviderStateMixin {
late AnimationController expandController;
late Animation<double> animation;
@override
void initState() {
super.initState();
prepareAnimations();
_runExpandCheck();
}
///Setting up the animation
void prepareAnimations() {
expandController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
animation = CurvedAnimation(
parent: expandController,
curve: Curves.fastOutSlowIn,
);
}
void _runExpandCheck() {
if (widget.expand) {
expandController.forward();
} else {
expandController.reverse();
}
}
@override
void didUpdateWidget(Expand oldWidget) {
super.didUpdateWidget(oldWidget);
_runExpandCheck();
}
@override
void dispose() {
expandController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizeTransition(
axisAlignment: 1.0, sizeFactor: animation, child: widget.child);
}
}

View file

@ -1,103 +1,111 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
import 'basket.dart';
/* import 'sample_data.dart';
todo:
- Design
- Details zum Warenkorb und Beschreibung (Popup?)
- farbliche Hervorhebungen
*/
enum Art { monatlBeitrag, aufladung, einkauf, korrektur }
final now = DateTime.now();
class Transaction {
int betrag;
Art art;
DateTime datum;
String beschreibung;
Transaction(this.art, this.betrag, this.datum, this.beschreibung);
}
class Finance extends StatelessWidget { class Finance extends StatelessWidget {
Finance({super.key}); const Finance({super.key});
final List<Transaction> transactions = [
Transaction(Art.monatlBeitrag, 0, now, ''),
Transaction(Art.aufladung, 2042, now, ''),
Transaction(Art.einkauf, -2442, now.subtract(const Duration(days: 2)), ''),
Transaction(Art.korrektur, 2332, now.subtract(const Duration(hours: 5)),
'Korrektur des Warenkorbs')
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
initializeDateFormatting('de_DE'); initializeDateFormatting('de_DE');
Intl.defaultLocale = 'de_DE';
return ListView.builder( return Scaffold(
itemCount: null, appBar: AppBar(
itemBuilder: (context, index) { toolbarHeight: 75,
if (index == 0) { title: const Card(
return const Card( child: ListTile(
child: Padding( leading: Icon(
padding: EdgeInsets.all(8.0), Icons.euro,
child: Row( ),
mainAxisAlignment: MainAxisAlignment.spaceBetween, title: Text(
children: [ 'Aktuelles Guthaben:',
Icon( ),
Icons.euro, subtitle: Text(
//color: Colors.black, '-00,34€',
//semanticLabel: 'Text for screenreader', )),
), ),
Column( ),
children: [ body: ListView.builder(
Text( itemCount: SampleData().transactions.length,
'Aktuelles Guthaben:', itemBuilder: (context, index) {
style: TextStyle(fontWeight: FontWeight.bold), return Card(
), child: ListTile(
Text( leading: getIcon(SampleData().transactions[index].type),
'-00,34€', title:
style: TextStyle(fontWeight: FontWeight.bold), Text(gettitle(SampleData().transactions[index].type)),
) subtitle: getSubtitle(SampleData().transactions[index]),
], trailing: getTrailing(context, index),
) onTap: (SampleData().transactions[index].basket != null)
], ? () {
), showBottomSheet(
), context: context,
); builder: (BuildContext context) {
} else if (index <= transactions.length) { return ShowBasket(SampleData()
return Card( .transactions[index]
child: Padding( .basket!);
padding: const EdgeInsets.all(8.0), });
child: Row( }
mainAxisAlignment: MainAxisAlignment.spaceEvenly, : null));
children: [ }));
Icon(transactions[index - 1].art == Art.korrektur }
? Icons.error
: Icons.money), String gettitle(TransaktionArt art) {
Column( switch (art) {
children: [ case TransaktionArt.aufladung:
Row( return 'Aufladung';
children: [ case TransaktionArt.einkauf:
Text(DateFormat("EEEE, dd. MMMM yyyy HH:mm", 'de_DE') return 'Einkauf';
.format(now)), case TransaktionArt.korrektur:
Text( return 'Korrektur';
'${transactions[index - 1].art}: ${transactions[index - 1].betrag / 100}'), case TransaktionArt.monatlBeitrag:
], return 'Monatlicher Beitrag';
), default:
Text(transactions[index - 1].beschreibung) return 'Ein Error ist aufgetreten';
], }
), }
],
), Text getSubtitle(Transaction transaction) {
), String text = '${transaction.amount / 100}';
); text += '';
} else { text += DateFormat("EEEE, dd. MMMM yyyy HH:mm").format(transaction.date);
return null; if (transaction.description != null) {
} (text += '\n${transaction.description}');
}, }
);
return Text(text);
}
Icon getIcon(TransaktionArt art) {
switch (art) {
case TransaktionArt.aufladung:
return const Icon(Icons.savings);
case TransaktionArt.einkauf:
return const Icon(Icons.shopping_basket);
case TransaktionArt.monatlBeitrag:
return const Icon(Icons.payment);
default:
return const Icon(Icons.priority_high);
}
}
getTrailing(BuildContext context, int index) {
if (SampleData().transactions[index].type == TransaktionArt.einkauf) {
return IconButton(
icon: const Icon(Icons.manage_history),
onPressed: () {
showBottomSheet(
context: context,
builder: (BuildContext context) {
return ShowBasket(SampleData().transactions[index].basket!,
editable: true);
});
},
);
} else {
return null;
}
} }
} }

View file

@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mitgliederladen/shopping.dart';
import 'package:mitgliederladen/settings.dart';
import 'finance.dart'; import 'finance.dart';
void main() { void main() {
@ -14,10 +16,21 @@ class MyApp extends StatelessWidget {
title: 'SoNaKo Demo App', title: 'SoNaKo Demo App',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
//darkgreen:5f7c61, mediumgreen: 66906a, lightgreen: 9cbe96, yellow: f5de64
useMaterial3: true, useMaterial3: true,
brightness: Brightness.light,
textTheme: Typography.englishLike2021,
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(
seedColor: Colors.green)), //9bbee6 //79,128,104 brightness: Brightness.light,
darkTheme: ThemeData.dark(), seedColor: const Color(0xff5f7c61))),
darkTheme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
textTheme: Typography.englishLike2021,
colorScheme: ColorScheme.fromSeed(
brightness: Brightness.dark,
seedColor: const Color(0xff5f7c61),
)),
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
home: const MyHomePage(), home: const MyHomePage(),
); );
@ -39,6 +52,8 @@ class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading:
const Image(image: AssetImage('assets/logo_sonako_4c_optimal.png')),
title: const Text('SoNaKo Demo App'), title: const Text('SoNaKo Demo App'),
), ),
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
@ -65,14 +80,9 @@ class _MyHomePageState extends State<MyHomePage> {
], ],
), ),
body: <Widget>[ body: <Widget>[
Container( const Shopping(),
color: Colors.red, const Finance(),
alignment: Alignment.center, const Settings()
child: Text('Page $test'),
),
Finance(),
const Text(
'Hier könnten Einstellungen zu Darkmode mit shared_preferences und riverpod sein')
][currentPageIndex], ][currentPageIndex],
); );
} }

View file

@ -0,0 +1,223 @@
import 'package:uuid/uuid.dart';
final DateTime now = DateTime.now();
enum TransaktionArt { monatlBeitrag, aufladung, einkauf, korrektur }
enum Unit { stueck, menge }
class Category {
final String name;
final String icon;
const Category({required this.name, required this.icon});
}
class Transaction {
int id = 0;
int amount;
TransaktionArt type;
DateTime date;
Basket? basket;
String? description;
Transaction(
{required this.type,
required this.amount,
required this.date,
this.description,
this.basket});
}
class Product {
final int id = 0;
final String name;
final Unit unit; //pro Kg oder Stück
final double price;
final double vat;
final Category category;
const Product(this.name, this.unit, this.price, this.vat, this.category);
}
class Basket {
Map<Product, int> purchases;
double price;
String guid;
Basket(this.purchases, this.price) : guid = const Uuid().v4();
void addItem(Product product, int quantity) {
if (purchases.containsKey(product)) {
purchases.update(
product, (existingQuantity) => existingQuantity + quantity);
} else {
purchases[product] = quantity;
}
}
void removeItem(Product product) {
purchases.remove(product);
}
}
class SampleData {
static const List<Category> categories = [
Category(name: 'Obst und Gemüse', icon: '🍒'),
Category(name: 'Kochen und Backen', icon: '🍝'),
Category(name: 'Brot, Cerealien & Aufstriche', icon: '🍞'),
Category(name: 'Getränke und Pfand', icon: '🫖'),
Category(name: 'Drogerie und Haushalt', icon: '🧼'),
Category(name: 'Öl, Soßen und Gewürze', icon: '🫚'),
Category(name: 'Süßes und Knabbereien', icon: '🍪')
];
static List<Product> products = [
Product('Apfel', Unit.stueck, 0.23, 7, categories[0]),
Product('Mehl', Unit.menge, 0.003, 19, categories[1]),
Product('Brot', Unit.stueck, 1.23, 7, categories[2]),
Product('Milch', Unit.stueck, 2.23, 3, categories[3]),
Product('Zahnpasta', Unit.stueck, 0.23, 7, categories[4]),
Product('Pfeffer', Unit.stueck, 0.23, 7, categories[5]),
Product('Schokolade', Unit.menge, 0.23, 7, categories[6]),
Product('Flaschenpfand', Unit.stueck, -0.15, 0, categories[3])
];
//such a basket can not exist later. It is for testing purposes
// when scrolling through a long basket
static Basket basket = Basket({
products[0]: 20,
products[1]: 2200,
products[2]: 2,
products[3]: 1,
products[4]: 1,
products[5]: 2,
products[6]: 222,
products[0]: 20,
products[1]: 2200,
products[2]: 2,
products[3]: 1,
products[4]: 1,
products[5]: 2,
products[6]: 222
}, 27.9);
static Basket basket2 = Basket({
products[0]: 22,
products[1]: 2241,
products[3]: 2,
products[4]: 4,
products[6]: 2,
products[7]: 5,
}, 34);
static Basket basket3 = Basket({
products[0]: 2,
products[1]: 21,
products[3]: 4,
products[4]: 1,
products[6]: 5,
}, -1);
List<Transaction> transactions = [
Transaction(
type: TransaktionArt.monatlBeitrag,
amount: 0,
date: now,
),
Transaction(
type: TransaktionArt.aufladung,
amount: 2042,
date: now,
),
Transaction(
type: TransaktionArt.einkauf,
amount: -2442,
date: now.subtract(const Duration(days: 2)),
basket: basket),
Transaction(
type: TransaktionArt.korrektur,
amount: 2332,
date: now.subtract(const Duration(hours: 5)),
description: 'Korrektur des Warenkorbs',
basket: basket3,
),
Transaction(
type: TransaktionArt.monatlBeitrag,
amount: 0,
date: now,
),
Transaction(
type: TransaktionArt.aufladung,
amount: 2042,
date: now,
),
Transaction(
type: TransaktionArt.einkauf,
amount: -2442,
date: now.subtract(const Duration(days: 2)),
basket: basket2),
Transaction(
type: TransaktionArt.korrektur,
amount: 2332,
date: now.subtract(const Duration(hours: 5)),
description: 'Korrektur des FinanzAK'),
Transaction(
type: TransaktionArt.monatlBeitrag,
amount: 0,
date: now,
),
Transaction(
type: TransaktionArt.aufladung,
amount: 2042,
date: now,
),
Transaction(
type: TransaktionArt.einkauf,
amount: -2442,
date: now.subtract(const Duration(days: 2)),
basket: basket,
),
Transaction(
type: TransaktionArt.korrektur,
amount: 2332,
date: now.subtract(const Duration(hours: 5)),
description: 'Korrektur des Warenkorbs'),
Transaction(
type: TransaktionArt.monatlBeitrag,
amount: 0,
date: now,
),
Transaction(
type: TransaktionArt.aufladung,
amount: 2042,
date: now,
),
Transaction(
type: TransaktionArt.einkauf,
amount: -2442,
date: now.subtract(const Duration(days: 2)),
basket: basket),
Transaction(
type: TransaktionArt.korrektur,
amount: 2332,
date: now.subtract(const Duration(hours: 5)),
description: 'Korrektur des Warenkorbs'),
Transaction(
type: TransaktionArt.monatlBeitrag,
amount: 0,
date: now,
),
Transaction(
type: TransaktionArt.aufladung,
amount: 2042,
date: now,
),
Transaction(
type: TransaktionArt.einkauf,
amount: -2442,
date: now.subtract(const Duration(days: 2)),
basket: basket2),
Transaction(
type: TransaktionArt.korrektur,
amount: 2332,
date: now.subtract(const Duration(hours: 5)),
description: 'Korrektur des Warenkorbs'),
];
}

View file

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class Settings extends StatelessWidget {
const Settings({super.key});
@override
Widget build(BuildContext context) {
//Hier in dem Return kann der Inhalt des Setting Tabs angepasst werden
return ListView(children: const <Widget>[
ListTile(
leading: Icon(Icons.dark_mode),
title: Text(
'Hier könnten Einstellungen zu Darkmode sein',
maxLines: 2,
),
)
]);
}
}

View file

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:mitgliederladen/sample_data.dart';
class Shopping extends StatelessWidget {
const Shopping({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.shopping_cart),
onPressed: () {},
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2),
itemCount: SampleData.categories.length,
itemBuilder: ((context, index) {
return Card(
child: Column(children: [
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
SampleData.categories[index].icon,
style: const TextStyle(fontSize: 400),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
SampleData.categories[index].name,
style: Theme.of(context).textTheme.labelLarge,
),
),
]),
);
})));
}
}

View file

@ -41,6 +41,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.17.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -184,6 +192,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.5.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
url: "https://pub.dev"
source: hosted
version: "3.0.7"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:

View file

@ -36,6 +36,7 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
intl: ^0.18.1 intl: ^0.18.1
uuid: ^3.0.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -60,9 +61,8 @@ flutter:
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: assets:
# - images/a_dot_burr.jpeg - assets/
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware

View file

@ -1,2 +1,36 @@
# Mitgliederladen # Mitgliederladen
## Development branches
backend
: Server component, using Rust/SQL, offering API (via HTTP/2?)
db_scripts
: SQL scripts
main
: frontend client as flutter app
## Tech used
### Backend
- Database connection using Rust-lang and SQLx
- RESTful (Level 2, JSON over RPC) API using Rust-lang (and probably (actix web framework)[https://actix.rs] plus (utoipa\[sic! 🍺 beer-branded punny name\])[https://github.com/juhaku/utoipa]
## Frontend
- Google Flutter using Dart.
Target platforms?
- probably Android[^1], iOS[^2].
- Debug target could be desktop for fastest deployment. Production targets: Perhaps desktop and/or web, if wanted.
[1]: via APK? Via Play Store or F-Droid? Then Registration as Play store developer (for charity or one time purchase with credit card?) needed.
[2]: Registration as Apple developer for charity needed.

View file

@ -13,9 +13,9 @@ CREATE TABLE IF NOT EXISTS transaktion_art (
art VARCHAR(50) NOT NULL art VARCHAR(50) NOT NULL
); );
INSERT INTO transaktion_art(art_id, art) VALUES (1, 'monatlicher_beitrag'); INSERT OR IGNORE INTO transaktion_art(art_id, art) VALUES (1, 'monatlicher_beitrag');
INSERT INTO transaktion_art(art_id, art) VALUES (2, 'aufladung'); INSERT OR IGNORE INTO transaktion_art(art_id, art) VALUES (2, 'aufladung');
INSERT INTO transaktion_art(art_id, art) VALUES (3, 'einkauf'); INSERT OR IGNORE INTO transaktion_art(art_id, art) VALUES (3, 'einkauf');
CREATE TABLE IF NOT EXISTS transaktion ( CREATE TABLE IF NOT EXISTS transaktion (
transaktion_id INT NOT NULL PRIMARY KEY, transaktion_id INT NOT NULL PRIMARY KEY,
@ -41,33 +41,22 @@ CREATE TABLE IF NOT EXISTS mwst (
prozent INT NOT NULL prozent INT NOT NULL
); );
INSERT INTO mwst(mwst_id, prozent) VALUES (1, 7); INSERT OR IGNORE INTO mwst(mwst_id, prozent) VALUES (1, 7);
INSERT INTO mwst(mwst_id, prozent) VALUES (2, 19); INSERT OR IGNORE INTO mwst(mwst_id, prozent) VALUES (2, 19);
CREATE TABLE IF NOT EXISTS einheit( CREATE TABLE IF NOT EXISTS einheit(
einheit_id INT NOT NULL PRIMARY KEY, einheit_id INT NOT NULL PRIMARY KEY,
bezeichnung VARCHAR(50) NOT NULL bezeichnung VARCHAR(50) NOT NULL
); );
INSERT INTO einheit(einheit_id, bezeichnung) VALUES (1, 'stueck'); INSERT OR IGNORE INTO einheit(einheit_id, bezeichnung) VALUES (1, 'stueck');
INSERT INTO einheit(einheit_id, bezeichnung) VALUES (2, 'menge'); INSERT OR IGNORE INTO einheit(einheit_id, bezeichnung) VALUES (2, 'menge');
CREATE TABLE IF NOT EXISTS kategorie (
kategorie_id INT NOT NULL PRIMARY KEY,
parent_id INT,
name VARCHAR(100) NOT NULL,
icon_unicode VARCHAR(100) NOT NULL,
CONSTRAINT fk_kategorie_tree FOREIGN KEY (parent_id) REFERENCES kategorie(kategorie_id)
);
CREATE TABLE IF NOT EXISTS artikel ( CREATE TABLE IF NOT EXISTS artikel (
artikel_id INT NOT NULL PRIMARY KEY, artikel_id INT NOT NULL PRIMARY KEY,
bezeichnung VARCHAR(255) NOT NULL,
kategorie_id INT NOT NULL,
einheit_id INT NOT NULL, einheit_id INT NOT NULL,
mwst_id INT NOT NULL, mwst_id INT NOT NULL,
preis INT NOT NULL, preis INT NOT NULL,
CONSTRAINT fk_mwst_artikel FOREIGN KEY (mwst_id) REFERENCES mwst(mwst_id), CONSTRAINT fk_mwst_artikel FOREIGN KEY (mwst_id) REFERENCES mwst(mwst_id),
CONSTRAINT fk_einheit_artikel FOREIGN KEY (einheit_id) REFERENCES einheit(einheit_id), CONSTRAINT fk_einheit_artikel FOREIGN KEY (einheit_id) REFERENCES einheit(einheit_id)
CONSTRAINT fk_kategorie_artikel FOREIGN KEY (kategorie_id) REFERENCES kategorie(kategorie_id)
); );

View file

@ -5,4 +5,3 @@ DROP TABLE IF EXISTS monatlicher_beitrag;
DROP TABLE IF EXISTS mwst; DROP TABLE IF EXISTS mwst;
DROP TABLE IF EXISTS einheit; DROP TABLE IF EXISTS einheit;
DROP TABLE IF EXISTS artikel; DROP TABLE IF EXISTS artikel;
DROP TABLE IF EXISTS kategorie;

21
server/.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
Cargo.lock
target/
guide/build/
/gh-pages
*.so
*.out
*.pyc
*.pid
*.sock
*~
.DS_Store
# These are backup files generated by rustfmt
**/*.rs.bk
# Configuration directory generated by CLion
.idea
# Configuration directory generated by VSCode
.vscode

10
server/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "server"
version = "1.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web ="4"
utoipa = { version = "3", features = ["actix_extras"] }

15
server/readme.md Normal file
View file

@ -0,0 +1,15 @@
# installation
## windows (step 1)
choco install -y
## Linux, macos (alernate step 1)
use apt, pacman, curl, brew or whatever.
## Always (steps 2 and so on)
- `rustup-init.sh` (Windows: rustup-init.ex, e.g. %AppData%\Local\Temp\chocolatey\rustup.install\1.25.1\rustup-init.exe)
- select a good match in the dialog of this CLI installer

28
server/src/main.rs Normal file
View file

@ -0,0 +1,28 @@
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body)
}
async fn manual_hello() -> impl Responder {
HttpResponse::Ok().body("Hey there!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
.service(echo)
.route("/hey", web::get().to(manual_hello))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}

319
shared_assets/sonako.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 622 KiB