Compare commits
	
		
			7 Commits
		
	
	
		
			feature/co
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e6471070a6 | |||
| c6544f29c8 | |||
| bd3ca2f413 | |||
| 87366336c5 | |||
| 
						 | 
					140b272f6c | ||
| 
						 | 
					a7461f6e2c | ||
| 
						 | 
					3c07be0160 | 
							
								
								
									
										25
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
FROM debian:latest as builder
 | 
			
		||||
 | 
			
		||||
RUN apt-get update 
 | 
			
		||||
RUN apt-get upgrade -y
 | 
			
		||||
RUN apt-get install -y elm-compiler make ca-certificates
 | 
			
		||||
 | 
			
		||||
COPY . /app
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
RUN rm -rf .venv venv
 | 
			
		||||
 | 
			
		||||
RUN make frontend
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FROM python:3.11-alpine as runner
 | 
			
		||||
COPY --from=builder /app /app
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
RUN pip install -r requirements.txt
 | 
			
		||||
RUN pip install gunicorn
 | 
			
		||||
 | 
			
		||||
EXPOSE 5000
 | 
			
		||||
ENV JON_DB_CONNECTION_STRING="host=fsmi-db.fsmi.org dbname=garfield"
 | 
			
		||||
ENV JON_SECRET_KEY="changeme"
 | 
			
		||||
 | 
			
		||||
CMD ["sh", "-c", "gunicorn -b '0.0.0.0:5000' --chdir /app 'jon:create_app()'"]
 | 
			
		||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							@ -45,6 +45,20 @@ flask --app jon run --debug
 | 
			
		||||
 | 
			
		||||
`--debug` restarts the server when a source file changes.
 | 
			
		||||
 | 
			
		||||
## Running with docker
 | 
			
		||||
When you prefer running the application using docker you can just use
 | 
			
		||||
```
 | 
			
		||||
docker compose up
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In case your local username does not line up with your FSMI-username, you need to specify your FSMI-username
 | 
			
		||||
using `USER=<username>`, e.g.:
 | 
			
		||||
```
 | 
			
		||||
USER=shirkanesi docker compose up
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This can also be persisted by following the instructions in the docker-compose.yml
 | 
			
		||||
 | 
			
		||||
## fsmi-db forward
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
@ -68,3 +82,4 @@ ssh -nNTvL 5432:fsmi-db.fsmi.org:5432 fsmi-login.fsmi.uni-karlsruhe.de
 | 
			
		||||
  - [x] Figure out/Add documentation about building `entry.js`
 | 
			
		||||
  - [ ] Clean up the code a little and add some comments
 | 
			
		||||
  - [ ] Needs good documentation for maintainability
 | 
			
		||||
- [ ] Use cool new function for deactivating items
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
---
 | 
			
		||||
version: '3.7'
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  jon:
 | 
			
		||||
    container_name: jon
 | 
			
		||||
    build: .
 | 
			
		||||
    ports:
 | 
			
		||||
      - "5000:5000"
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ~/.pgpass:/root/.pgpass:ro
 | 
			
		||||
    dns:
 | 
			
		||||
      - 1.1.1.1
 | 
			
		||||
      - 8.8.8.8
 | 
			
		||||
    network_mode: bridge
 | 
			
		||||
    environment:
 | 
			
		||||
      # If your local user is different from your fsmi user, change $USER here!
 | 
			
		||||
      - JON_DB_CONNECTION_STRING="host=fsmi-db.fsmi.org dbname=garfield user=$USER"
 | 
			
		||||
      - JON_SECRET_KEY="changemetosomethingsuperrandomandsecure"
 | 
			
		||||
@ -20,6 +20,7 @@ def create_app():
 | 
			
		||||
    # You don't need a config.json. If you don't provide one, default-config.json
 | 
			
		||||
    # is used.
 | 
			
		||||
    app.config.from_file("config.json", load=json.load, silent=True)
 | 
			
		||||
    app.config.from_prefixed_env(prefix="JON")
 | 
			
		||||
 | 
			
		||||
    db.init_app(app)
 | 
			
		||||
    auth.init_app(app)
 | 
			
		||||
 | 
			
		||||
@ -59,7 +59,6 @@ FROM garfield.inventory_items
 | 
			
		||||
ORDER BY inventory_items.name;
 | 
			
		||||
 | 
			
		||||
-- How many *other* active inventory lines exist with the same barcode in the same location that are newer?
 | 
			
		||||
 | 
			
		||||
CREATE TEMPORARY VIEW more_recent_inventory_lines_with_same_barcode AS
 | 
			
		||||
SELECT
 | 
			
		||||
  a.item_id,
 | 
			
		||||
@ -74,3 +73,41 @@ LEFT JOIN garfield.inventory_items AS b
 | 
			
		||||
      AND b.available
 | 
			
		||||
      AND b.bought > a.bought
 | 
			
		||||
GROUP BY a.item_id;
 | 
			
		||||
 | 
			
		||||
-- We need to create this table so that we can put an index on it
 | 
			
		||||
-- Otherwise the join in the consumption graph query becomes much slower
 | 
			
		||||
-- Perhaps it would be nicer to use a materialized view instead
 | 
			
		||||
DROP TABLE IF EXISTS last_n_days;
 | 
			
		||||
CREATE TEMPORARY TABLE last_n_days AS (
 | 
			
		||||
  SELECT
 | 
			
		||||
    generate_series(now() - interval '14 days', now(), interval '1 day')::date AS sale_date
 | 
			
		||||
);
 | 
			
		||||
CREATE UNIQUE INDEX last_n_days_sale_date ON last_n_days (sale_date);
 | 
			
		||||
 | 
			
		||||
-- Get an array of how often items were sold over the last 14 days
 | 
			
		||||
CREATE TEMPORARY VIEW inventory_last_n_days_sales AS
 | 
			
		||||
WITH
 | 
			
		||||
sales_by_date AS (
 | 
			
		||||
  SELECT
 | 
			
		||||
    inventory_line AS item_id,
 | 
			
		||||
    date_trunc('day', snack_sales_log_timestamp AT TIME ZONE 'UTC+1') AS sale_date,
 | 
			
		||||
    count(*)::int AS sales
 | 
			
		||||
  FROM garfield.snack_sales_log
 | 
			
		||||
  GROUP BY item_id, sale_date
 | 
			
		||||
),
 | 
			
		||||
beeg AS (
 | 
			
		||||
  SELECT item_id, sale_date, count(snack_sales_log_timestamp)::int AS sales
 | 
			
		||||
  FROM garfield.inventory_items
 | 
			
		||||
  CROSS JOIN last_n_days
 | 
			
		||||
  LEFT JOIN garfield.snack_sales_log
 | 
			
		||||
         ON inventory_line = item_id
 | 
			
		||||
-- snack_sales_log has an index on snack_sales_log_timestamp to speed up this query
 | 
			
		||||
-- If that index doesn't exist the query takes much longer.
 | 
			
		||||
        AND sale_date = date_trunc('day', snack_sales_log_timestamp AT TIME ZONE 'UTC+1')
 | 
			
		||||
  WHERE available
 | 
			
		||||
  GROUP BY item_id, sale_date
 | 
			
		||||
  ORDER BY item_id, sale_date
 | 
			
		||||
)
 | 
			
		||||
SELECT item_id, array_agg(sales) AS last_n_days_sales
 | 
			
		||||
FROM beeg
 | 
			
		||||
GROUP BY item_id
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ SELECT
 | 
			
		||||
  *
 | 
			
		||||
FROM all_inventory_item_overview
 | 
			
		||||
LEFT JOIN more_recent_inventory_lines_with_same_barcode USING (item_id)
 | 
			
		||||
LEFT JOIN inventory_last_n_days_sales USING (item_id)
 | 
			
		||||
WHERE (%(location_id)s IS NULL OR location = %(location_id)s)
 | 
			
		||||
  AND available
 | 
			
		||||
ORDER BY
 | 
			
		||||
 | 
			
		||||
@ -74,3 +74,7 @@ th {
 | 
			
		||||
details {
 | 
			
		||||
  font-size: 0.8em;
 | 
			
		||||
}
 | 
			
		||||
.consumption-graph {
 | 
			
		||||
  display: block;
 | 
			
		||||
  height: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,26 @@
 | 
			
		||||
{% macro consumption_graph_svg(values) -%}
 | 
			
		||||
{% set stroke_width = 8 %}
 | 
			
		||||
{% set width = 300 %}
 | 
			
		||||
{% set height = 100 %}
 | 
			
		||||
{% set padding = 4 %}
 | 
			
		||||
{% set dx = (width - 2 * padding) / ((values | length) + 1) %}
 | 
			
		||||
{% set dy = (height - 2 * padding) / ((values + [1]) | max) %}
 | 
			
		||||
<svg viewBox="0 0 {{ width }} {{ height }}" role="img" class="consumption-graph">
 | 
			
		||||
  <polyline
 | 
			
		||||
    points="
 | 
			
		||||
    {% for value in values %}
 | 
			
		||||
      {{ padding + loop.index * dx }}, {{ height - padding - value * dy }}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
    "
 | 
			
		||||
    stroke="green"
 | 
			
		||||
    stroke-width="{{ stroke_width }}"
 | 
			
		||||
    stroke-linecap="round"
 | 
			
		||||
    stroke-linejoin="round"
 | 
			
		||||
    fill="none"
 | 
			
		||||
  >
 | 
			
		||||
</svg>
 | 
			
		||||
{% endmacro -%}
 | 
			
		||||
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
@ -5,6 +28,7 @@
 | 
			
		||||
  <tr>
 | 
			
		||||
    <th>Stat</th>
 | 
			
		||||
    <th>ID</th>
 | 
			
		||||
    <th>Graph</th>
 | 
			
		||||
    <th>Barcode</th>
 | 
			
		||||
    <th>Name</th>
 | 
			
		||||
    <th>Preis (Netto)</th>
 | 
			
		||||
@ -23,6 +47,7 @@
 | 
			
		||||
      {% if item.other_lines_count != 0 %}<span title="{% if item.other_lines_count == 1 %}Neuerer Eintrag mit demselben Barcode ist aktiv{% else %}{{ item.other_lines_count }} Einträge mit demselben Barcode sind aktiv{% endif %}">🔄</span>{% endif %}
 | 
			
		||||
    </td>
 | 
			
		||||
    <td><a href="/inventory/item/{{ item.item_id }}">{{ item.item_id }}</a></td>
 | 
			
		||||
    <td>{{ consumption_graph_svg(item.last_n_days_sales) }}</td>
 | 
			
		||||
    <td><code>{{ item.item_barcode }}</code></td>
 | 
			
		||||
    <td>{{ item.name }}</td>
 | 
			
		||||
    <td class="--align-right">{{ format_currency(item.unit_price) }}</td>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user