Files
SpiceManagementSystem/README.md
T
Malek TellissiandClaude Sonnet 4.6 5c034ec895
Deploy / deploy (push) Successful in 14s
Add lot_prefix to products; compose LOT number from prefix + date suffix
Products now have a configurable lot_prefix (e.g. "112"). In the batch form
the prefix is shown read-only next to the suffix input (JJMMTT), and the full
lot_number (e.g. "112-260912") is composed automatically and stored.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-12 20:35:37 +02:00

296 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SpiceManagementSystem
Warenwirtschafts-Panel für Gewürzmischungen — verwaltet Rohstoffe, Rezepte, Produkte, Chargen und Shopify-Sync.
Built with [Laravel](https://laravel.com) + [Filament](https://filamentphp.com), deployed via Docker.
---
## Lokale Entwicklung
**Voraussetzungen:** PHP 8.4, Composer, Node.js, MariaDB
```bash
composer install
npm install && npm run build
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan serve
```
Die App läuft dann auf `http://localhost:8000`.
---
## Produktion (Docker)
Der Deploy läuft automatisch via Gitea Actions bei jedem Push auf `main`:
1. Dateien per SCP auf den VPS kopieren
2. `docker compose up -d --build` — baut das Image neu und startet die Container
3. `php artisan migrate --force` — läuft automatisch im Container
### Artisan-Befehle auf dem Server
Da die App in Docker mit PHP 8.4 läuft, müssen alle artisan-Befehle **im Container** ausgeführt werden:
```bash
cd ~/app && docker compose exec app php artisan <befehl>
```
---
## Shopify-Import
### Erstimport (Produkte + Lagerbestand + Beschreibung)
```bash
cd ~/app && docker compose exec app php artisan shopify:import
```
Optionen:
- `--dry-run` — zeigt nur an was importiert würde, speichert nichts
### Produktbilder herunterladen
```bash
cd ~/app && docker compose exec app php artisan shopify:images
```
Optionen:
- `--force` — lädt Bilder auch für Produkte neu, die bereits welche haben
### Lagerbestand + Preise synchronisieren
```bash
cd ~/app && docker compose exec app php artisan shopify:sync
```
---
## Chargen (LOT) & Rohstoff-Abzug
Beim Anlegen einer Charge wird der Rohstoffbestand automatisch abgezogen:
```
Charge: 50 Einheiten × 100g Standbodenbeutel = 5000g
→ Kurkuma (60%): 3000g vom Lager abgezogen
→ Pfeffer (30%): 1500g vom Lager abgezogen
→ Ingwer (10%): 500g vom Lager abgezogen
```
- Beim **Bearbeiten** einer Charge wird der Abzug neu berechnet
- Beim **Löschen** werden die Mengen zurückgebucht
- Chargen können direkt auf der Produktseite angelegt werden
---
## Rezept-Versionierung
Rezepte sind versioniert und einem Produkt zugeordnet:
- Jedes Rezept hat eine Versionsnummer (v1, v2, …)
- Beim Anlegen eines neuen Rezepts über das Produktformular wird die Version automatisch hochgezählt
- Ältere Versionen bleiben erhalten (z.B. für Chargen-Rückverfolgung)
---
## LOT-API
Öffentliche JSON-API zur Rückverfolgung von Chargen. Kein Auth erforderlich.
### Endpunkt
```
GET https://api.sms.spices.blue/api/v1/{lot_number}
```
### Beispiel-Antwort
```json
{
"data": {
"lot_number": "112-260912",
"produced_at": "2024-10-01",
"expires_at": "2026-10-01",
"units_produced": 50,
"quantity_g": 5000,
"product": {
"name": "Curry Klassik",
"description": "..."
},
"packing": {
"name": "Standbodenbeutel",
"size_g": 100,
"sku": "CUR-100",
"gtin": "4012345678901"
},
"recipe": {
"version": 3,
"created_at": "2024-09-15"
},
"spices": [
{
"name": "Kurkuma",
"percentage": 60.0,
"origins": [
{
"country": "Indien",
"supplier": "Gewürzhandel GmbH",
"farmer": "Singh Farm",
"notes": null
},
{
"country": "Sri Lanka",
"supplier": "Ceylon Spices",
"farmer": null,
"notes": "Restmenge aus Vorlieferung"
}
]
}
]
}
}
```
Fehler (nicht gefunden):
```json
{ "error": "LOT-Nummer nicht gefunden." }
```
### Subdomain-Setup auf dem Server
Der Container läuft intern auf `127.0.0.1:5000`. Im Caddy-Reverse-Proxy auf dem Host einfach einen weiteren Block ergänzen — Caddy übernimmt SSL automatisch:
```caddy
api.sms.spices.blue {
handle /api/* {
reverse_proxy 127.0.0.1:5000
}
respond 404
}
```
Nur `/api/*`-Pfade werden an den Container weitergeleitet — alle anderen Routen (Panel, Assets usw.) bekommen direkt eine 404 von Caddy, ohne dass Laravel sie zu Gesicht bekommt.
Danach `caddy reload` (oder `systemctl reload caddy`).
---
## Cronjob
Auf dem Server einrichten, damit der Laravel-Scheduler läuft:
```
* * * * * cd ~/app && docker compose exec -T app php artisan schedule:run >> /dev/null 2>&1
```
---
## Datenbankstruktur
```
Table spices {
id int [pk]
name varchar(255)
quantity decimal(10,2) -- immer in Gramm
unit varchar
}
Table recipes {
id int [pk]
product_id int [ref: > products.id, nullable]
version int -- auto-increment pro Produkt
name varchar(255)
description text
}
Table recipes_spices {
recipe_id int [ref: > recipes.id]
spice_id int [ref: > spices.id]
amount_in_percent decimal(6,2)
}
Table packings {
id int [pk]
name varchar(255)
size decimal(10,2) -- Gramm pro Einheit
}
Table products {
id int [pk]
title text
lot_prefix varchar -- Präfix für Chargennummer, z.B. "112"
images json
description text
recipes_id int [ref: > recipes.id]
}
Table products_sizes {
id int [pk]
product_id int [ref: > products.id]
packing_id int [ref: > packings.id]
sku varchar
shopify_variant_id varchar
gtin varchar
price float
}
Table products_inventory {
id int [pk]
product_id int [ref: > products.id]
size_id int [ref: > products_sizes.id]
out_at timestamp
}
Table batches {
id int [pk]
lot_number varchar [unique]
product_id int [ref: > products.id]
size_id int [ref: > products_sizes.id]
recipe_id int [ref: > recipes.id]
units_produced int
quantity_g decimal(10,2) -- berechnet: units × packing.size
produced_at date
expires_at date
}
Table batch_origins {
id int [pk]
batch_id int [ref: > batches.id]
spice_id int [ref: > spices.id]
}
Table batch_origin_entries {
id int [pk]
batch_origin_id int [ref: > batch_origins.id]
supplier_id int [ref: > suppliers.id, nullable]
origin_country varchar
farmer_id int [ref: > farmers.id, nullable]
notes text
}
Table farmers {
id int [pk]
name varchar(255)
}
```
---
## Vision & Todo
**Fokus: Warenwirtschaft (Lager, Rohstoffe, Produkte)**
- [x] DB-Schema mit Produkthistorie
- [x] Rezepte mit Versionierung
- [x] Produktgrößen, Preise, SKU, GTIN mit Barcode-Vorschau
- [x] Inventar (eindeutige Items mit Ausgangs-Flag)
- [x] Chargen (LOT) mit automatischem Rohstoff-Abzug und mehreren Lieferanten/Landwirten pro Rohstoff
- [x] Warenausgang (Produktverkauf)
- [x] Shopify-Import (Produkte, Varianten, Lagerbestand, Bilder, Beschreibung, Preise)
- [x] Continuous Deployment via Gitea Actions + Docker
- [x] Frontend (Sidebar, Navigation, Barcode-Preview, Bild-Grid)
- [x] LOT-API (JSON, öffentlich, Subdomain api.sms.spices.blue)
- [ ] Dashboard (Füllstände, Lager, Sales-Zeitspanne)