Compare commits
	
		
			4 Commits
		
	
	
		
			38c827d0d8
			...
			37179e65a1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 37179e65a1 | |||
|   | 73bebf0aec | ||
|   | 8fda78c466 | ||
| 9211d1f9b4 | 
							
								
								
									
										1
									
								
								py/TODO.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								py/TODO.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | - [ ] Fix date handling in entry | ||||||
| @ -3,7 +3,13 @@ import json | |||||||
| 
 | 
 | ||||||
| from flask import Flask, render_template | from flask import Flask, render_template | ||||||
| 
 | 
 | ||||||
| from . import db, inventory, location, template_utils | from . import ( | ||||||
|  |     db, | ||||||
|  |     entry, | ||||||
|  |     inventory, | ||||||
|  |     location, | ||||||
|  |     template_utils | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def create_app(): | def create_app(): | ||||||
| @ -21,6 +27,7 @@ def create_app(): | |||||||
| 
 | 
 | ||||||
|     app.register_blueprint(location.bp) |     app.register_blueprint(location.bp) | ||||||
|     app.register_blueprint(inventory.bp) |     app.register_blueprint(inventory.bp) | ||||||
|  |     app.register_blueprint(entry.bp) | ||||||
|     @app.route("/") |     @app.route("/") | ||||||
|     def index(): |     def index(): | ||||||
|         return render_template("index.html") |         return render_template("index.html") | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								py/jon/db/get_groups.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								py/jon/db/get_groups.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | SELECT | ||||||
|  |   group_id, | ||||||
|  |   group_name | ||||||
|  | FROM garfield.inventory_item_groups | ||||||
|  | ORDER BY | ||||||
|  |   group_name ASC | ||||||
							
								
								
									
										11
									
								
								py/jon/db/get_snacks_by_barcode.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								py/jon/db/get_snacks_by_barcode.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | SELECT | ||||||
|  |   * | ||||||
|  | FROM garfield.inventory_map | ||||||
|  | LEFT JOIN garfield.snacks USING (snack_id) | ||||||
|  | LEFT JOIN garfield.snacks_available USING (snack_id) | ||||||
|  | LEFT JOIN garfield.tax_groups USING (tax_group_id) | ||||||
|  | LEFT JOIN garfield.locations USING (location_id) | ||||||
|  | WHERE snack_barcode = %(snack_barcode)s | ||||||
|  | ORDER BY | ||||||
|  |   snack_available DESC, | ||||||
|  |   snack_timestamp DESC | ||||||
							
								
								
									
										59
									
								
								py/jon/entry.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								py/jon/entry.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | import datetime | ||||||
|  | import zoneinfo | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | from flask import Blueprint, redirect, render_template, request, session | ||||||
|  | 
 | ||||||
|  | from . import db | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | bp = Blueprint("entry", __name__, url_prefix="/entry") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @bp.get("/") | ||||||
|  | def index(): | ||||||
|  |     return render_template("entry/index.html") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @bp.route("/edit-item-data", methods=["GET", "POST"]) | ||||||
|  | def edit_item_data(): | ||||||
|  |     if "entry" not in session: | ||||||
|  |         session["entry"] = dict() | ||||||
|  | 
 | ||||||
|  |     if request.method == "POST": | ||||||
|  |         session["entry"] = { | ||||||
|  |             "item_bought": datetime.datetime.strptime(request.form.get("item_bought"), "%Y-%m-%d"), | ||||||
|  |             "item_barcode": request.form.get("item_barcode"), | ||||||
|  |             "item_name": request.form.get("item_name"), | ||||||
|  |             "item_group_id": int(request.form.get("item_group")), | ||||||
|  |             "item_net_unit_price": float(request.form.get("item_net_unit_price")), | ||||||
|  |             "item_tax_group_id": int(request.form.get("item_tax_group")), | ||||||
|  |             "item_amount": int(request.form.get("item_amount")), | ||||||
|  |             "item_location_id": int(request.form.get("item_location")) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return redirect("/entry/select-snack-entry") | ||||||
|  | 
 | ||||||
|  |     groups = db.run_query("get_groups.sql").fetchall() | ||||||
|  |     locations = db.run_query("get_locations.sql").fetchall() | ||||||
|  | 
 | ||||||
|  |     return render_template("entry/edit-item-data.html", **{ | ||||||
|  |         "groups": groups, | ||||||
|  |         "locations": locations, | ||||||
|  |         "entry": session["entry"] | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @bp.route("/select-snack-entry", methods=["GET", "POST"]) | ||||||
|  | def edit_snack_data(): | ||||||
|  |     if "entry" not in session: | ||||||
|  |         return redirect("/entry/edit-item-data") | ||||||
|  | 
 | ||||||
|  |     snacks = db.run_query("get_snacks_by_barcode.sql", { | ||||||
|  |         "snack_barcode": session["entry"]["item_barcode"] | ||||||
|  |     }).fetchall() | ||||||
|  | 
 | ||||||
|  |     return render_template("entry/select-snack-entry.html", **{ | ||||||
|  |         "entry": session["entry"], | ||||||
|  |         "snacks": snacks | ||||||
|  |     }) | ||||||
| @ -1,3 +1,6 @@ | |||||||
|  | import datetime | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def format_currency(x): | def format_currency(x): | ||||||
|     # It would be nicer to format this using the German locale |     # It would be nicer to format this using the German locale | ||||||
|     # Too lazy to bother tho. |     # Too lazy to bother tho. | ||||||
| @ -10,3 +13,18 @@ def format_date(d): | |||||||
| 
 | 
 | ||||||
| def format_bool(x): | def format_bool(x): | ||||||
|     return "✅" if x else "❌" |     return "✅" if x else "❌" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def now(): | ||||||
|  |     return datetime.datetime.now() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def get_garfield_price(net_unit_price, tax_group_id): | ||||||
|  |     if tax_group_id == 1: | ||||||
|  |         tax_factor = 1.19 | ||||||
|  |     elif tax_group_id == 2: | ||||||
|  |         tax_factor = 1.07 | ||||||
|  |     else: | ||||||
|  |         raise Error("Unknown tax group ID") | ||||||
|  | 
 | ||||||
|  |     return net_unit_price * tax_factor + 0.01 | ||||||
|  | |||||||
| @ -62,6 +62,13 @@ | |||||||
|           font-size: 8px; |           font-size: 8px; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |       .form-input > label { | ||||||
|  |         font-size: .8em; | ||||||
|  |       } | ||||||
|  |       .form-input > input:not([type=radio]), | ||||||
|  |       .form-input > select { | ||||||
|  |         display: block; | ||||||
|  |       } | ||||||
|     </style> |     </style> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
| @ -71,6 +78,7 @@ | |||||||
|         <ul> |         <ul> | ||||||
|           <li {{ "class=current-page" if request.path == "/" else "" }}><a href="/">Home</a></li> |           <li {{ "class=current-page" if request.path == "/" else "" }}><a href="/">Home</a></li> | ||||||
|           <li {{ "class=current-page" if request.path.startswith("/inventory") else "" }}><a href="/inventory">Inventar</a></li> |           <li {{ "class=current-page" if request.path.startswith("/inventory") else "" }}><a href="/inventory">Inventar</a></li> | ||||||
|  |           <li {{ "class=current-page" if request.path.startswith("/entry") else "" }}><a href="/entry">Eintragen</a></li> | ||||||
|           <li {{ "class=current-page" if request.path.startswith("/location") else "" }}> |           <li {{ "class=current-page" if request.path.startswith("/location") else "" }}> | ||||||
|             <a href="/location"> |             <a href="/location"> | ||||||
|             {% if "location" not in session %} |             {% if "location" not in session %} | ||||||
|  | |||||||
							
								
								
									
										56
									
								
								py/jon/templates/entry/edit-item-data.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								py/jon/templates/entry/edit-item-data.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  | <pre>{{ entry }}</pre> | ||||||
|  | 
 | ||||||
|  | <fieldset> | ||||||
|  |   <legend>Neuer Inventareintrag</legend> | ||||||
|  | 
 | ||||||
|  |   <form method="POST" action="/entry/edit-item-data"> | ||||||
|  |     <div class="form-input"> | ||||||
|  |       <label for="item_bought">Kaufdatum</label> | ||||||
|  |       <input name="item_bought" id="item_bought" type="date" value="{{ (entry.item_bought or now()).strftime('%Y-%m-%d') }}"> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-input"> | ||||||
|  |       <label for="item_barcode">Barcode</label> | ||||||
|  |       <input name="item_barcode" id="item_barcode" type="text" value="{{ entry.item_barcode }}" placeholder="Barcode"> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-input"> | ||||||
|  |       <label for="item_name">Artikel</label> | ||||||
|  |       <input name="item_name" id="item_name" type="text" value="{{ entry.item_name }}" placeholder="Artikel"> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-input"> | ||||||
|  |       <label for="item_group">Gruppe</label> | ||||||
|  |       <select name="item_group" id="item_group"> | ||||||
|  |         {% for group in groups %} | ||||||
|  |         <option value="{{ group.group_id }}"{% if entry.item_group_id == group.group_id %} selected{% endif %}>{{ group.group_name }} ({{ group.group_id }})</option> | ||||||
|  |         {% endfor %} | ||||||
|  |       </select> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-input"> | ||||||
|  |       <label for="item_net_unit_price">Stückpreis (Netto) in €</label> | ||||||
|  |       <input name="item_net_unit_price" id="item_net_unit_price" type="number" step="0.01" value="{{ entry.item_net_unit_price }}" placeholder="Stückpreis (Netto) in €"> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-input"> | ||||||
|  |       <input name="item_tax_group" id="item_tax_group_1" type="radio" value="1"{% if entry.item_tax_group_id == 1 %} selected{% endif %}> | ||||||
|  |       <label for="item_tax_group_1">Volle Umsatzsteuer (19%)</label> | ||||||
|  | 
 | ||||||
|  |       <input name="item_tax_group" id="item_tax_group_2" type="radio" value="2"{% if entry.item_tax_group_id == 2 %} selected{% endif %}> | ||||||
|  |       <label for="item_tax_group_2">Ermäßigte Umsatzsteuer (7%)</label> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-input"> | ||||||
|  |       <label for="item_amount">Anzahl</label> | ||||||
|  |       <input name="item_amount" id="item_amount" type="number" value="{{ entry.item_amount }}" placeholder="Anzahl"> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-input"> | ||||||
|  |       <label for="item_group">Raum</label> | ||||||
|  |       <select name="item_location" id="item_location"> | ||||||
|  |         {% for location in locations %} | ||||||
|  |         <option value="{{ location.location_id }}"{% if entry.item_location_id == location.location_id or ("item_location" not in entry and (session.location.location_id == location.location_id)) %} selected{% endif %}>{{ location.location_name }} ({{ location.location_id }})</option> | ||||||
|  |         {% endfor %} | ||||||
|  |       </select> | ||||||
|  |     </div> | ||||||
|  |     <button>Weiter zu den Snackeinträgen</button> | ||||||
|  |   </form> | ||||||
|  | </fieldset> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										5
									
								
								py/jon/templates/entry/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								py/jon/templates/entry/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  | <a href="/entry/edit-item-data">Neuer Eintrag</a> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										83
									
								
								py/jon/templates/entry/select-snack-entry.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								py/jon/templates/entry/select-snack-entry.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  | <pre>{{ entry }}</pre> | ||||||
|  | 
 | ||||||
|  | <fieldset> | ||||||
|  |   <legend>Neuer Inventareintrag</legend> | ||||||
|  |   <table> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">ID</th> | ||||||
|  |       <td>{{ entry.item_id }}</td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">Barcode</th> | ||||||
|  |       <td><code>{{ entry.item_barcode }}</code></td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">Name</th> | ||||||
|  |       <td>{{ entry.item_name }}</td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">Einkaufspreis (Netto)</th> | ||||||
|  |       <td>{{ format_currency(entry.item_net_unit_price) }}</td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">Empfohlener Garfield-Verkaufspreis</th> | ||||||
|  |       <td>{{ format_currency(get_garfield_price(entry.item_net_unit_price, entry.item_tax_group_id)) }}</td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">Kaufdatum</th> | ||||||
|  |       <td>{{ format_date(entry.item_bought) }}</td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">Gruppe</th> | ||||||
|  |       <td>{{ entry.item_group_name }} ({{ entry.item_group_id }})</td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">Anzahl</th> | ||||||
|  |       <td>{{ entry.item_amount }}</td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |       <th class="--align-left">Raum</th> | ||||||
|  |       <td>{{ entry.item_location_name }} ({{ entry.item_location_id }})</td> | ||||||
|  |     </tr> | ||||||
|  |   </table> | ||||||
|  | </fieldset> | ||||||
|  | 
 | ||||||
|  | <fieldset> | ||||||
|  |   <legend>Snackeinträge mit Barcode <code>{{ entry.item_barcode }}</code></legend> | ||||||
|  | 
 | ||||||
|  |   <table> | ||||||
|  |     <tr> | ||||||
|  |       <th>ID</th> | ||||||
|  |       <th>Barcode</th> | ||||||
|  |       <th>Name</th> | ||||||
|  |       <th>Verkaufspreis (Brutto)</th> | ||||||
|  |       <th>Eintragedatum</th> | ||||||
|  |       <th>Steuersatz</th> | ||||||
|  |       <th>Raum</th> | ||||||
|  |       <th>Aktiv?</th> | ||||||
|  |       <th>Aktionen</th> | ||||||
|  |     </tr> | ||||||
|  |     {% for snack in snacks %} | ||||||
|  |     <tr> | ||||||
|  |       <td>{{ snack.snack_id }}</td> | ||||||
|  |       <td><code>{{ snack.snack_barcode }}</code></td> | ||||||
|  |       <td>{{ snack.snack_name }}</td> | ||||||
|  |       <td class="--align-right">{{ format_currency(snack.snack_price) }}</td> | ||||||
|  |       <td>{{ format_date(snack.snack_timestamp) }}</td> | ||||||
|  |       <td>{{ snack.description }} ({{ snack.tax_group_id }})</td> | ||||||
|  |       <td>{{ snack.location_name }} ({{ snack.location_id }})</td> | ||||||
|  |       <td>{{ format_bool(snack.snack_available) }}</td> | ||||||
|  |       <td> | ||||||
|  |         <form method="POST" action="/entry/select-snack-entry"> | ||||||
|  |           <input type="hidden" name="snack_id" value="{{ snack.snack_id }}"> | ||||||
|  |           <button>Snackeintrag übernehmen</button> | ||||||
|  |         </form> | ||||||
|  |       </td> | ||||||
|  |     </tr> | ||||||
|  |     {% endfor %} | ||||||
|  |   </table> | ||||||
|  | </fieldset> | ||||||
|  | {% endblock %} | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user