diff --git a/cmd/websrv/main.go b/cmd/websrv/main.go index fcde303..cc5c3b8 100644 --- a/cmd/websrv/main.go +++ b/cmd/websrv/main.go @@ -1,12 +1,16 @@ package main import ( + "encoding/json" "fmt" - "gittea.marcokittel.de/elio/eliotools/datawriter/internal/api" - "gittea.marcokittel.de/elio/eliotools/datawriter/internal/database" + "io" "log" "net/http" "os" + + "github.com/google/uuid" + "gittea.marcokittel.de/elio/eliotools/datawriter/internal/api" + "gittea.marcokittel.de/elio/eliotools/datawriter/internal/database" ) var ( @@ -16,33 +20,165 @@ var ( type ( //RegistrierungsID -> ProductID -> Product - ProductStore map[string]map[string]api.Product + ProductStore map[string]map[string]database.Product ) const ( - curlhelp = `curl -X POST localhost:8080/api/products -d '{ "products": { "A6053": 2, "B3009": 1200 }, "context": { "country": "EU", "state": "" } }'` + curlhelp = ` + Bewerbungsaufgabe von Marco Kittel 2025 + + Produkte abrufen + curl -X POST localhost:8080/api/products -d '{ "products": { "A6053": 2, "B3009": 1200 }, "context": { "country": "EU", "state": "" } }' + + Reservierung reservieren + curl -X POST localhost:8080/api/products/reserve -d '{"id":"ab0d7184-a4ce-4802-897a-d8597335143a"}' + + Reservierung bestätigen + curl -X POST localhost:8080/api/products/confirm -d '{"id":"ab0d7184-a4ce-4802-897a-d8597335143a"}' + + Reservierung abbrechen + curl -X POST localhost:8080/api/products/abort -d '{"id":"ab0d7184-a4ce-4802-897a-d8597335143a"}' + + Reservierung freigeben + curl -X POST localhost:8080/api/products/release -d '{"id":"ab0d7184-a4ce-4802-897a-d8597335143a"}' + ` ) func main() { connectionString := os.Getenv("CONNECTIONSTRING") nps := database.NewProductService(connectionString) + //Hier prüfe ich nach alten Registrierungen und gebe Sie frei. + nps.Autorelease() if len(connectionString) == 0 { fmt.Println("Connectionstring fehlt!. Bsp.: :@tcp(127.0.0.1:3306)/elio?parseTime=true") return } //Dependency Injection http.HandleFunc("/api/products", api.GetProductApiHandleFunc(nps)) + + //Todo schöner machen http.HandleFunc("/api/products/reserve", func(w http.ResponseWriter, r *http.Request) { - fmt.Println("In Arbeit: reserve") + if r.Method != "POST" { + return + } + defer r.Body.Close() + data, err := io.ReadAll(r.Body) + if err != nil { + log.Println(err) + return + } + var payload database.Container + err = json.Unmarshal(data, &payload) + if err != nil { + log.Printf("Could not parse Json: %s", err) + return + } + groupId := uuid.New().String() + result, err := nps.FetchReservationData(&payload, database.UUID(groupId)) + if err != nil { + //Todo Fehlerhandling + log.Println(err) + } + if len(result) == 0 { + w.WriteHeader(http.StatusNoContent) + fmt.Fprintln(w) + return + } + _, err = nps.ReserviereBestellungen(result, database.UUID(groupId)) + if err != nil { + log.Println(err) + } + jsonResult, err := json.Marshal(result[0]) + if err != nil { + log.Println(err) + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, string(jsonResult)) }) + http.HandleFunc("/api/products/confirm", func(w http.ResponseWriter, r *http.Request) { - fmt.Println("In Arbeit: confirm") + if r.Method != "POST" { + return + } + defer r.Body.Close() + data, err := io.ReadAll(r.Body) + if err != nil { + log.Println(err) + return + } + payload := struct { + Id string `json:"id"` + }{} + err = json.Unmarshal(data, &payload) + if err != nil { + //Todo Fehlerhandling + log.Println(err) + } + err = nps.ConfirmBestellung(database.UUID(payload.Id)) + if err != nil { + //Todo Fehlerhandling + log.Println(err) + } + w.WriteHeader(http.StatusNoContent) + fmt.Fprintln(w) + return }) + http.HandleFunc("/api/products/release", func(w http.ResponseWriter, r *http.Request) { - fmt.Println("In Arbeit: release") + if r.Method != "POST" { + return + } + defer r.Body.Close() + data, err := io.ReadAll(r.Body) + if err != nil { + log.Println(err) + return + } + payload := struct { + Id string `json:"id"` + }{} + err = json.Unmarshal(data, &payload) + if err != nil { + //Todo Fehlerhandling + log.Println(err) + } + err = nps.ReleaseBestellung(database.UUID(payload.Id)) + if err != nil { + //Todo Fehlerhandling + log.Println(err) + } + w.WriteHeader(http.StatusNoContent) + fmt.Fprintln(w) + return }) + http.HandleFunc("/api/products/abort", func(w http.ResponseWriter, r *http.Request) { - fmt.Println("In Arbeit: abort") + if r.Method != "POST" { + return + } + defer r.Body.Close() + data, err := io.ReadAll(r.Body) + if err != nil { + log.Println(err) + return + } + payload := struct { + Id string `json:"id"` + }{} + err = json.Unmarshal(data, &payload) + if err != nil { + //Todo Fehlerhandling + log.Println(err) + } + err = nps.AbortBestellung(database.UUID(payload.Id)) + if err != nil { + //Todo Fehlerhandling + log.Println(err) + } + w.WriteHeader(http.StatusNoContent) + fmt.Fprintln(w) + return }) log.Printf("Easy Peasy: Die Party startet auf Port %s\n", port) diff --git a/go.mod b/go.mod index 798fe5c..758ac24 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/go.sum b/go.sum index a65d254..b1a283b 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= diff --git a/initdb/init_table.sql b/initdb/init_table.sql index 221baba..1bc4e25 100644 --- a/initdb/init_table.sql +++ b/initdb/init_table.sql @@ -4,7 +4,19 @@ CREATE DATABASE IF NOT EXISTS elio; -- Haupttabellen erzeugen -CREATE TABLE IF NOT EXISTS elio.blablabla ( +-- CREATE TABLE IF NOT EXISTS elio.blablabla ( +-- id INT AUTO_INCREMENT PRIMARY KEY, +-- warehouse char(2) NOT NULL, +-- productid VARCHAR(20) NOT NULL, +-- amount INT DEFAULT 0, +-- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +-- CONSTRAINT location_product_must_be_one UNIQUE (warehouse, productid) +-- ) ENGINE=InnoDB; + + + +-- Doppelter Boden: Tabellen werden zur Not auch beim Starten des Services im ersten Datenbank Call erzeugt +CREATE TABLE IF NOT EXISTS elio.warehouseproducts ( id INT AUTO_INCREMENT PRIMARY KEY, warehouse char(2) NOT NULL, productid VARCHAR(20) NOT NULL, @@ -23,6 +35,24 @@ CREATE TABLE IF NOT EXISTS elio.deliverytimes ( CONSTRAINT delivery_from_to_country_must_be_one UNIQUE (fromcountry, tocountry) ) ENGINE=InnoDB; +CREATE TABLE IF NOT EXISTS elio.reservations ( + id CHAR(36) PRIMARY KEY DEFAULT UUID(), + deliveryId INT, + warehouseId INT, + amount INT DEFAULT 0, + status VARCHAR(30) CHECK(status IN ('RESERVED', 'CONFIRMED', 'ABORTED', 'RELEASED')), + reservationGroupId CHAR(36) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT fk_delivery_id + FOREIGN KEY (deliveryId) REFERENCES deliverytimes (id) + ON DELETE CASCADE + ON UPDATE RESTRICT, + CONSTRAINT fk_warehouse_id + FOREIGN KEY (warehouseId) REFERENCES warehouseproducts (id) + ON DELETE CASCADE + ON UPDATE RESTRICT +) ENGINE=InnoDB; + -- Testtabellen erzeugen CREATE TABLE IF NOT EXISTS elio_test.warehouseproducts ( id INT AUTO_INCREMENT PRIMARY KEY, @@ -31,7 +61,7 @@ CREATE TABLE IF NOT EXISTS elio_test.warehouseproducts ( amount INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT location_product_must_be_one UNIQUE (warehouse, productid) -) ENGINE=InnoDB; +) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS elio_test.deliverytimes ( id INT AUTO_INCREMENT PRIMARY KEY, @@ -43,6 +73,26 @@ CREATE TABLE IF NOT EXISTS elio_test.deliverytimes ( CONSTRAINT delivery_from_to_country_must_be_one UNIQUE (fromcountry, tocountry) ) ENGINE=InnoDB; +CREATE TABLE IF NOT EXISTS elio_test.reservations ( + id CHAR(36) PRIMARY KEY DEFAULT UUID(), + deliveryId INT, + warehouseId INT, + amount INT DEFAULT 0, + status VARCHAR(30) CHECK(status IN ('RESERVED', 'CONFIRMED', 'ABORTED', 'RELEASED')), + reservationGroupId CHAR(36) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT fk_delivery_id + FOREIGN KEY (deliveryId) REFERENCES deliverytimes (id) + ON DELETE CASCADE + ON UPDATE RESTRICT, + CONSTRAINT fk_warehouse_id + FOREIGN KEY (warehouseId) REFERENCES warehouseproducts (id) + ON DELETE CASCADE + ON UPDATE RESTRICT +) ENGINE=InnoDB; + + + -- Userkram CREATE USER IF NOT EXISTS 'elio'@'%' IDENTIFIED BY 'eliogeheim'; CREATE USER IF NOT EXISTS 'elio_test'@'%' IDENTIFIED BY 'eliogeheim'; diff --git a/internal/api/products.go b/internal/api/products.go index 06865ea..f268fe0 100644 --- a/internal/api/products.go +++ b/internal/api/products.go @@ -10,33 +10,6 @@ import ( "gittea.marcokittel.de/elio/eliotools/datawriter/internal/database" ) -type Container struct { - Products map[string]int `json:"products"` - Context Context `json:"context"` -} - -type Product struct { - Warehouse string `json:"warehouse"` - Quantity int `json:"quantity"` - Delivery int `json:"delivery_time"` -} - -type OutgoingProducts struct { - Products map[string][]Product `json:"products"` -} - -func NewOutgoingProducts() *OutgoingProducts { - op := OutgoingProducts{ - Products: make(map[string][]Product), - } - return &op -} - -type Context struct { - Country string `json:"country"` - State string `json:"state"` -} - func GetProductApiHandleFunc(nps *database.ProductService) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { @@ -59,7 +32,12 @@ func GetProductApiHandleFunc(nps *database.ProductService) http.HandlerFunc { //Todo Fehlerhandling log.Println(err) } - jsonResult, err := json.Marshal(result) + if len(result) == 0 { + w.WriteHeader(http.StatusNoContent) + fmt.Fprintln(w) + return + } + jsonResult, err := json.Marshal(result[0]) if err != nil { log.Println(err) } diff --git a/internal/database/database.go b/internal/database/database.go index ab244ed..35b029e 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -88,6 +88,7 @@ func (d *DatabaseWriter) createReservationTableIfNotExist() error { warehouseId INT, amount INT DEFAULT 0, status VARCHAR(30) CHECK(status IN ('RESERVED', 'CONFIRMED', 'ABORTED', 'RELEASED')), + reservationGroupId CHAR(36) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_delivery_id FOREIGN KEY (deliveryId) REFERENCES deliverytimes (id) @@ -127,6 +128,16 @@ func NewDatabaseWriter(connectionString string) *DatabaseWriter { return &db } +func (d *DatabaseWriter) ReleaseReservierungenAfterOneDay() error { + _, err := d.db.Exec(` + UPDATE reservations r SET status = 'RELEASED' + WHERE r.created_at < DATE_SUB(NOW(), INTERVAL 1 DAY );`) + if err != nil { + return err + } + return nil +} + func (d *DatabaseWriter) UpdateOrInsertWarehouseProduct(warehouse string, productID string, amount int) error { _, err := d.db.Exec(` INSERT INTO warehouseproducts (warehouse, productid, amount) @@ -167,15 +178,22 @@ type ProductDelivery struct { func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, delivery_country, delivery_country_state string) ([]ProductDelivery, error) { stmt := ` - SELECT whp.id, warehouse, amount, d.delivery, d.id deliveryId - FROM warehouseproducts whp - left join deliverytimes d - on whp.warehouse = d.fromcountry - where productid = ? - and d.tocountry = ? - and d.state = ? - order by d.delivery asc, amount desc - ` + SELECT id, warehouse, (amount - reserviert) amount, delivery, deliveryId FROM( + SELECT whp.id, warehouse, whp.amount, sum(coalesce(r.amount,0) )reserviert, d.delivery, d.id deliveryId + FROM warehouseproducts whp + left join deliverytimes d + on whp.warehouse = d.fromcountry + left join reservations r + on whp.id = r.warehouseId + and r.status in ('RESERVED', 'CONFIRMED') + where productid = ? + and d.tocountry = ? + and d.state = ? + GROUP BY whp.id, d.delivery + order by d.delivery asc, whp.amount desc + ) as zumAufsummieren + having amount > 0 + ` rows, err := d.db.Query(stmt, prod_id, delivery_country, delivery_country_state) if err != nil { @@ -211,6 +229,24 @@ func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error) return status, nil } +func (d *DatabaseReader) GetReservationItemsByGroupId(groupId UUID) (string, error) { + stmt := ` + SELECT status FROM reservations WHERE id = ? + ` + row, err := d.db.Query(stmt, groupId) + if err != nil { + return "", err + } + defer row.Close() + row.Next() + var status string + err = row.Scan(&status) + if err != nil { + return "", err + } + return status, nil +} + func (d *DatabaseWriter) updateReservationState(Id UUID, status string) error { _, err := d.db.Exec("UPDATE reservations SET Status=? WHERE id=?", status, Id) if err != nil { @@ -219,6 +255,14 @@ func (d *DatabaseWriter) updateReservationState(Id UUID, status string) error { return nil } +func (d *DatabaseWriter) updateReservationStateByGroupId(groupId UUID, status string) error { + _, err := d.db.Exec("UPDATE reservations SET Status=? WHERE reservationGroupId=?", status, groupId) + if err != nil { + return err + } + return nil +} + func (d *DatabaseWriter) AbortReservation(Id UUID) error { return d.updateReservationState(Id, "ABORTED") } @@ -231,9 +275,21 @@ func (d *DatabaseWriter) ReleaseReservation(Id UUID) error { return d.updateReservationState(Id, "RELEASED") } -func (d *DatabaseWriter) ReserveReservation(deliveryId, warehouseId, amount int) (UUID, error) { +func (d *DatabaseWriter) AbortReservationGroup(groupId UUID) error { + return d.updateReservationStateByGroupId(groupId, "ABORTED") +} + +func (d *DatabaseWriter) ConfirmReservationGroup(groupId UUID) error { + return d.updateReservationStateByGroupId(groupId, "CONFIRMED") +} + +func (d *DatabaseWriter) ReleaseReservationGroup(groupId UUID) error { + return d.updateReservationStateByGroupId(groupId, "RELEASED") +} + +func (d *DatabaseWriter) ReserveReservationItem(deliveryId, warehouseId, amount int, groupId UUID) (UUID, error) { newUUID := uuid.New().String() - _, err := d.db.Exec("INSERT INTO reservations (id, deliveryId, warehouseId, amount, status) VALUES (?,?,?,?,'RESERVED')", newUUID, deliveryId, warehouseId, amount) + _, err := d.db.Exec("INSERT INTO reservations (id, deliveryId, warehouseId, amount, status, reservationGroupId) VALUES (?,?,?,?,'RESERVED', ?)", newUUID, deliveryId, warehouseId, amount, groupId) if err != nil { return "", err } diff --git a/internal/database/database_test.go b/internal/database/database_test.go index 7a6f775..84c448f 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -3,9 +3,12 @@ package database import ( "encoding/json" "fmt" + "log" "strings" "testing" + + "github.com/google/uuid" ) // Unit Tests sollten niemals auf der Produktivdatenbank stattfinden. @@ -123,8 +126,9 @@ func TestInsertStatement(t *testing.T) { if err != nil { t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) } + groupId := uuid.New().String() for _, item := range data { - uuid, err := dbr.DatabaseWriter.ReserveReservation(item.DeliveryId, item.Id, item.Amount) + uuid, err := dbr.DatabaseWriter.ReserveReservationItem(item.DeliveryId, item.Id, item.Amount, UUID(groupId)) if err != nil { t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err) } @@ -141,8 +145,9 @@ func TestAbortReservationStatement(t *testing.T) { if err != nil { t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) } + groupId := uuid.New().String() for _, item := range data { - uuid, err := dbr.DatabaseWriter.ReserveReservation(item.DeliveryId, item.Id, item.Amount) + uuid, err := dbr.DatabaseWriter.ReserveReservationItem(item.DeliveryId, item.Id, item.Amount, UUID(groupId)) if err != nil { t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err) } @@ -166,8 +171,9 @@ func TestConfirmReservationStatement(t *testing.T) { if err != nil { t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) } + groupId := uuid.New().String() for _, item := range data { - uuid, err := dbr.DatabaseWriter.ReserveReservation(item.DeliveryId, item.Id, item.Amount) + uuid, err := dbr.DatabaseWriter.ReserveReservationItem(item.DeliveryId, item.Id, item.Amount, UUID(groupId)) if err != nil { t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err) } @@ -191,8 +197,9 @@ func TestReleasedReservationStatement(t *testing.T) { if err != nil { t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) } + groupId := uuid.New().String() for _, item := range data { - uuid, err := dbr.DatabaseWriter.ReserveReservation(item.DeliveryId, item.Id, item.Amount) + uuid, err := dbr.DatabaseWriter.ReserveReservationItem(item.DeliveryId, item.Id, item.Amount, UUID(groupId)) if err != nil { t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err) } @@ -210,6 +217,20 @@ func TestReleasedReservationStatement(t *testing.T) { } } +func TestFetchReservationData(t *testing.T) { + payloadStr := `{ "products": { "A6053": 30 }, "context": { "country": "EU", "state": "" }}` + var payload Container + err := json.Unmarshal([]byte(payloadStr), &payload) + if err != nil { + t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err) + return + } + ps := NewProductService(connectionString) + // groupId := uuid.New().String() + op, err := ps.FetchData(&payload) + log.Println(op) +} + func TestFetchDataReservation(t *testing.T) { payloadStr := `{ "products": { "A6053": 30 }, "context": { "country": "EU", "state": "" }}` @@ -220,7 +241,8 @@ func TestFetchDataReservation(t *testing.T) { return } ps := NewProductService(connectionString) - op, err := ps.FetchData(&payload) + groupId := uuid.New().String() + op, err := ps.FetchReservationData(&payload, UUID(groupId)) if err != nil { t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) } @@ -312,16 +334,17 @@ func TestOutgoingReservationInDatabase(t *testing.T) { t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err) return } + groupId := uuid.New().String() ps := NewProductService(connectionString) - op, err := ps.FetchData(&payload) + op, err := ps.FetchReservationData(&payload, UUID(groupId)) if err != nil { t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) } - valTocompare := `[{map[A6053:[{AT 4 1 8508 63} {DE 1 2 7507 18}]]}]` + valTocompare := fmt.Sprintf(`[{%s map[A6053:[{AT 4 1 8508 63} {DE 1 2 7507 18}]]}]`, groupId) if fmt.Sprint(op) != valTocompare { t.Errorf("Mist, sollte sein %s ist aber %s", valTocompare, fmt.Sprint(op)) } - uuids, err := ps.ReserviereBestellungen(op) + uuids, err := ps.ReserviereBestellungen(op, UUID(groupId)) if err != nil { t.Errorf("Das Reservieren ist fehlgeschlagen! \n%s", err) } @@ -331,10 +354,20 @@ func TestOutgoingReservationInDatabase(t *testing.T) { t.Errorf("Der Status muss RESERVED sein. Stattdessen ist er: %s", status) } + } + ps.dbr.ReleaseReservationGroup(UUID(groupId)) + for _, uuid := range *uuids { + status, err := ps.dbr.GetReservationStateById(uuid) + if err != nil || status != "RELEASED" { + t.Errorf("Der Status muss RELEASED sein. Stattdessen ist er: %s", status) + } + } jsonResult, err := json.Marshal(op) - jsonStrForApiResponse := `[{"products":{"A6053":[{"warehouse":"AT","quantity":4,"delivery_time":1},{"warehouse":"DE","quantity":1,"delivery_time":2}]}}]` + jsonGuid := fmt.Sprintf("\"id\":\"%s\"", UUID(groupId)) + jsonStrForApiResponse := fmt.Sprintf(`[{%s,"products":{"A6053":[{"warehouse":"AT","quantity":4,"delivery_time":1},{"warehouse":"DE","quantity":1,"delivery_time":2}]}}]`, jsonGuid) if string(jsonResult) != jsonStrForApiResponse { t.Errorf("Mist, sollte sein %s ist aber %s", string(jsonResult), jsonStrForApiResponse) } + } diff --git a/internal/database/service.go b/internal/database/service.go index 3d26057..49d4056 100644 --- a/internal/database/service.go +++ b/internal/database/service.go @@ -1,6 +1,10 @@ package database -import "log" +import ( + "fmt" + "log" + "time" +) type ProductService struct { dbr *DatabaseReader @@ -21,6 +25,10 @@ type Product struct { type OutgoingProducts struct { Products map[string][]Product `json:"products"` } +type OutgoingReservationProducts struct { + Id UUID `json:"id"` + Products map[string][]Product `json:"products"` +} func NewOutgoingProducts() *OutgoingProducts { op := OutgoingProducts{ @@ -28,6 +36,13 @@ func NewOutgoingProducts() *OutgoingProducts { } return &op } +func NewOutgoingReservationsProducts(groupId UUID) *OutgoingReservationProducts { + op := OutgoingReservationProducts{ + Id: groupId, + Products: make(map[string][]Product), + } + return &op +} type Context struct { Country string `json:"country"` @@ -40,7 +55,22 @@ func NewProductService(connectionString string) *ProductService { return &p } -func (p *ProductService) ReserviereBestellungen(op []OutgoingProducts) (*[]UUID, error) { +func (p *ProductService) Autorelease() { + //Das müsste saubererer sein. Spare ich mir aber, weil der Webserver alles blockiert. + //Besser wäre das Arbeiten mit Context. Gefahr ist aber minimal + go func() { + for { + fmt.Println("Prüfe nach Registrierungen die Freigegeben werden können. (Älter als 24 Stunden)") + err := p.dbr.ReleaseReservierungenAfterOneDay() + if err != nil { + log.Printf("Fehler beim Ausführen von Autorelease() des Logs. %s", err) + } + time.Sleep(30 * time.Second) + } + }() +} + +func (p *ProductService) ReserviereBestellungen(op []OutgoingReservationProducts, groupId UUID) (*[]UUID, error) { var uuids []UUID for _, hsmp := range op { for _, prod := range hsmp.Products { @@ -49,7 +79,7 @@ func (p *ProductService) ReserviereBestellungen(op []OutgoingProducts) (*[]UUID, warehouseId := concreteProduct.WhdId amount := concreteProduct.Quantity // Todo: Handler Injection wäre hier interessant - uuid, err := p.dbr.ReserveReservation(deliveryId, warehouseId, amount) + uuid, err := p.dbr.ReserveReservationItem(deliveryId, warehouseId, amount, groupId) if err != nil { return nil, err } @@ -60,13 +90,25 @@ func (p *ProductService) ReserviereBestellungen(op []OutgoingProducts) (*[]UUID, return &uuids, nil } +func (p *ProductService) ConfirmBestellung(groudId UUID) error { + return p.dbr.ConfirmReservationGroup(groudId) +} + +func (p *ProductService) AbortBestellung(groudId UUID) error { + return p.dbr.AbortReservationGroup(groudId) +} + +func (p *ProductService) ReleaseBestellung(groudId UUID) error { + return p.dbr.ReleaseReservationGroup(groudId) +} + func (p *ProductService) FetchData(payload *Container) ([]OutgoingProducts, error) { result := []OutgoingProducts{} for key, item := range payload.Products { //Checken ob die Felder leer sind / Validitätsprüfung einbauen products, err := p.dbr.GetProductByProductIdDeliveryCountryAndState(key, payload.Context.Country, payload.Context.State) if err != nil { - log.Println(err) + return nil, err } if len(products) == 0 { continue @@ -91,3 +133,35 @@ func (p *ProductService) FetchData(payload *Container) ([]OutgoingProducts, erro } return result, nil } + +func (p *ProductService) FetchReservationData(payload *Container, groupId UUID) ([]OutgoingReservationProducts, error) { + result := []OutgoingReservationProducts{} + for key, item := range payload.Products { + //Checken ob die Felder leer sind / Validitätsprüfung einbauen + products, err := p.dbr.GetProductByProductIdDeliveryCountryAndState(key, payload.Context.Country, payload.Context.State) + if err != nil { + return nil, err + } + if len(products) == 0 { + continue + } + gebrauchteProduktAnzahl := item + op := NewOutgoingReservationsProducts(groupId) + for _, db_products := range products { + if gebrauchteProduktAnzahl <= 0 { + continue + } + if db_products.Amount >= gebrauchteProduktAnzahl { + newProduct := Product{Delivery: db_products.DeliveryDays, Quantity: gebrauchteProduktAnzahl, Warehouse: db_products.Warehouse, WhdId: db_products.Id, DeliveryId: db_products.DeliveryId} + gebrauchteProduktAnzahl = 0 + op.Products[key] = append(op.Products[key], newProduct) + } else if db_products.Amount < gebrauchteProduktAnzahl { + newProduct := Product{Delivery: db_products.DeliveryDays, Quantity: db_products.Amount, Warehouse: db_products.Warehouse, WhdId: db_products.Id, DeliveryId: db_products.DeliveryId} + gebrauchteProduktAnzahl -= db_products.Amount + op.Products[key] = append(op.Products[key], newProduct) + } + } + result = append(result, *op) + } + return result, nil +}