335 lines
9.4 KiB
Go
335 lines
9.4 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
"github.com/google/uuid"
|
|
"gittea.marcokittel.de/elio/eliotools/eliofile"
|
|
"gittea.marcokittel.de/elio/eliotools/logger"
|
|
)
|
|
|
|
type DatabaseWriter struct {
|
|
log logger.Logger
|
|
db *sql.DB
|
|
}
|
|
|
|
type DatabaseReader struct {
|
|
DatabaseWriter
|
|
}
|
|
|
|
type UUID string
|
|
|
|
func NewDatabaseReader(connectionString string) *DatabaseReader {
|
|
return &DatabaseReader{*NewDatabaseWriter(connectionString)}
|
|
}
|
|
|
|
func (d *DatabaseWriter) connectDB(connectionString string) (*sql.DB, error) {
|
|
db, err := sql.Open("mysql", connectionString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = db.Ping()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Verbindungspool konfigurieren
|
|
db.SetMaxOpenConns(40)
|
|
db.SetMaxIdleConns(25)
|
|
db.SetConnMaxLifetime(5 * time.Minute)
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func (d *DatabaseWriter) createWarehouseTableIfNotExist() error {
|
|
_, err := d.db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS warehouseproducts (
|
|
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;
|
|
`)
|
|
return err
|
|
}
|
|
|
|
func (d *DatabaseWriter) createDeliveryTableIfNotExist() error {
|
|
_, err := d.db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS deliverytimes (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
fromcountry varchar(4) NOT NULL,
|
|
tocountry varchar(4) NOT NULL,
|
|
state varchar(4) NULL,
|
|
delivery INT DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT delivery_from_to_country_must_be_one UNIQUE (fromcountry, tocountry)
|
|
) ENGINE=InnoDB;`)
|
|
return err
|
|
}
|
|
|
|
// Die Idee ist, dass ich die Reservierungen via mit Verweisen auf die Primärschlüssel
|
|
// von deliverytimes und warehouseproducts verknüpfe. Ziel ist es, über den Status
|
|
// das Fetch Select SQL Statement für die Abfrage der Reservierungen zu erweitern um
|
|
// über die SUM Methode im SQL Query bei STATUS RESERVED, den reduzierten Mengenwert anzuzuzeigen
|
|
// für das Registrierte Produkt
|
|
func (d *DatabaseWriter) createReservationTableIfNotExist() error {
|
|
_, err := d.db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS 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;`)
|
|
return err
|
|
}
|
|
|
|
func NewDatabaseWriter(connectionString string) *DatabaseWriter {
|
|
db := DatabaseWriter{log: logger.NewMarcoLogger()}
|
|
sql, err := db.connectDB(connectionString)
|
|
if err != nil {
|
|
fmt.Printf("Datenbank nicht gefunden. %s", err)
|
|
os.Exit(1)
|
|
}
|
|
db.db = sql
|
|
err = db.createWarehouseTableIfNotExist()
|
|
if err != nil {
|
|
fmt.Printf("Warenhaus-Tabellen-Erstellung fehlgeschlagen. %s", err)
|
|
os.Exit(1)
|
|
}
|
|
err = db.createDeliveryTableIfNotExist()
|
|
if err != nil {
|
|
fmt.Printf("Delivery-Tabellen-Erstellung fehlgeschlagen. %s", err)
|
|
os.Exit(1)
|
|
}
|
|
err = db.createReservationTableIfNotExist()
|
|
if err != nil {
|
|
fmt.Printf("Reservations-Tabellen-Erstellung fehlgeschlagen. %s", err)
|
|
os.Exit(1)
|
|
}
|
|
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)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE amount = VALUES(amount)`,
|
|
warehouse, productID, amount)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("upsert failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *DatabaseWriter) UpdateOrInsertDelivery(fromcountry string, tocountry string, state string, delivery int) error {
|
|
_, err := d.db.Exec(`
|
|
INSERT INTO deliverytimes (fromcountry, tocountry, state, delivery)
|
|
VALUES (?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
fromcountry = VALUES(fromcountry),
|
|
tocountry = VALUES(tocountry),
|
|
state = VALUES(state),
|
|
delivery = VALUES(delivery)`,
|
|
fromcountry, tocountry, state, delivery)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("upsert failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ProductDelivery struct {
|
|
Id int //Warehouse Id
|
|
Warehouse string
|
|
Amount int
|
|
DeliveryDays int
|
|
DeliveryId int
|
|
}
|
|
|
|
func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, delivery_country, delivery_country_state string) ([]ProductDelivery, error) {
|
|
stmt := `
|
|
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 {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var products []ProductDelivery
|
|
for rows.Next() {
|
|
var pd ProductDelivery
|
|
if err := rows.Scan(&pd.Id, &pd.Warehouse, &pd.Amount, &pd.DeliveryDays, &pd.DeliveryId); err != nil {
|
|
return products, err
|
|
}
|
|
products = append(products, pd)
|
|
}
|
|
return products, nil
|
|
}
|
|
|
|
func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error) {
|
|
stmt := `
|
|
SELECT status FROM reservations WHERE id = ?
|
|
`
|
|
row, err := d.db.Query(stmt, productId)
|
|
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 *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 {
|
|
return err
|
|
}
|
|
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")
|
|
}
|
|
|
|
func (d *DatabaseWriter) ConfirmReservation(Id UUID) error {
|
|
return d.updateReservationState(Id, "CONFIRMED")
|
|
}
|
|
|
|
func (d *DatabaseWriter) ReleaseReservation(Id UUID) error {
|
|
return d.updateReservationState(Id, "RELEASED")
|
|
}
|
|
|
|
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, reservationGroupId) VALUES (?,?,?,?,'RESERVED', ?)", newUUID, deliveryId, warehouseId, amount, groupId)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return UUID(newUUID), nil
|
|
}
|
|
|
|
func (db *DatabaseWriter) HandleData(ctx context.Context, data eliofile.CountryCsvData) error {
|
|
// a.log.Info("HandleData")
|
|
if strings.Contains(strings.Join(data.Data, ","), "product_id") || strings.Contains(strings.Join(data.Data, ","), "delivery_time") {
|
|
return nil
|
|
}
|
|
//Todo: Csv reparieren.
|
|
switch data.Type {
|
|
case "stock":
|
|
{
|
|
menge := strings.Split(data.Data[0], ";")
|
|
amount, err := strconv.Atoi(menge[1])
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return err
|
|
}
|
|
err = db.UpdateOrInsertWarehouseProduct(data.CountryID, menge[0], amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case "delivery":
|
|
{
|
|
menge := strings.Split(data.Data[0], ";")
|
|
amount, err := strconv.Atoi(menge[2])
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return err
|
|
}
|
|
err = db.UpdateOrInsertDelivery(data.CountryID, menge[0], menge[1], amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|