feat: finish back-end work (untested)

This commit is contained in:
2025-11-12 12:58:21 -07:00
parent b6a08573f3
commit 8b351524b8
18 changed files with 300 additions and 27 deletions

View File

@@ -156,8 +156,7 @@ func IsAuthenticated(auth *auth.Authenticator) gin.HandlerFunc {
context.Next() context.Next()
} else { } else {
if !HandleRefreshToken(session) { if !HandleRefreshToken(session) {
context.String(http.StatusUnauthorized, "Failed to refresh access token") context.Redirect(http.StatusSeeOther, "/auth/login")
context.Abort()
return return
} else { } else {
context.Next() context.Next()

View File

@@ -33,6 +33,8 @@ func UserInDatabase(pool *pgxpool.Pool) gin.HandlerFunc {
return return
} }
log.Printf("%d", count)
if count == 0 { if count == 0 {
_, exec_err := conn.Exec(context.Background(), queries.USER_CREATE_QUERY, sub_id, nickname) _, exec_err := conn.Exec(context.Background(), queries.USER_CREATE_QUERY, sub_id, nickname)

View File

@@ -217,6 +217,14 @@ func AddItemToOrder(pool *pgxpool.Pool) gin.HandlerFunc {
return return
} }
_, update_order_filled_exec_err := conn.Exec(context.Background(), queries.SET_ORDER_FILLED, false, order_id)
if update_order_filled_exec_err != nil {
ctx.String(http.StatusInternalServerError, "AddItemToOrder(): ERROR... failed to set order state")
log.Printf("AddItemToOrder(): ERROR... failed to set order state... %s", update_order_filled_exec_err.Error())
return
}
var order_item_price dto.OrderItemPriceResponse var order_item_price dto.OrderItemPriceResponse
price_data_row := conn.QueryRow(context.Background(), queries.GET_ORDER_ITEM_PRICE, item_id, order_id) price_data_row := conn.QueryRow(context.Background(), queries.GET_ORDER_ITEM_PRICE, item_id, order_id)
@@ -281,3 +289,150 @@ func GetOrderItems(pool *pgxpool.Pool) gin.HandlerFunc {
ctx.JSON(http.StatusOK, items) ctx.JSON(http.StatusOK, items)
} }
} }
func SetItemMade(pool *pgxpool.Pool) gin.HandlerFunc {
return func(ctx *gin.Context) {
conn, conn_err := pool.Acquire(ctx)
if conn_err != nil {
log.Printf("SetItemMade(): ERROR: Failed to connect... %s", conn_err.Error())
ctx.AbortWithStatus(http.StatusInternalServerError)
return
}
defer conn.Release()
var request dto.ItemOrderSetMadeRequest
request_read_err := ctx.ShouldBindJSON(&request)
if request_read_err != nil {
ctx.String(http.StatusBadRequest, "SetItemMade(): ERROR... Error Request in bad format")
return
}
_, exec_set_made_err := conn.Exec(context.Background(), queries.SET_MADE, request.Made, request.ItemId, request.OrderId)
if exec_set_made_err != nil {
ctx.String(http.StatusInternalServerError, "SetItemMade(): ERROR... Internal server error")
log.Printf("SetItemMade(): ERROR... Failed to set item made... %s", exec_set_made_err.Error())
return
}
_, exec_update_order_err := conn.Exec(context.Background(), queries.UPDATE_ORDER_FILLED, request.OrderId)
if exec_update_order_err != nil {
ctx.String(http.StatusInternalServerError, "SetItemMade(): ERROR... Failed to update order")
log.Printf("SetItemMade(): ERROR... Failed to update order... %s", exec_update_order_err.Error())
return
}
var order_filled dto.OrderFilledResponse
scan_err := conn.QueryRow(context.Background(), queries.GET_ORDER_MADE_DETAILS, request.OrderId).Scan(&order_filled.OrderId, &order_filled.Filled)
if scan_err != nil {
if scan_err == pgx.ErrNoRows {
ctx.String(http.StatusBadRequest, "SetItemMade(): Invalid data")
return
}
ctx.String(http.StatusInternalServerError, "SetItemMade(): ERROR... Error querying result")
log.Printf("SetItemMade(): ERROR... Error querying result... %s", scan_err.Error())
return
}
ctx.JSON(http.StatusOK, order_filled)
}
}
func SetItemQuantity(pool *pgxpool.Pool) gin.HandlerFunc {
return func(ctx *gin.Context) {
conn, conn_err := pool.Acquire(ctx)
if conn_err != nil {
log.Printf("SetItemMade(): ERROR: Failed to connect... %s", conn_err.Error())
ctx.AbortWithStatus(http.StatusInternalServerError)
return
}
defer conn.Release()
var request dto.ItemOrderSetQuantityRequest
request_read_err := ctx.ShouldBindJSON(&request)
if request_read_err != nil {
ctx.String(http.StatusBadRequest, "SetItemQuantity(): ERROR... Error Request in bad format")
return
}
_, exec_set_quantity_err := conn.Exec(context.Background(), queries.SET_QUANTITY, request.Quantity, request.ItemId, request.OrderId)
if exec_set_quantity_err != nil {
ctx.String(http.StatusInternalServerError, "SetItemMade(): ERROR... Internal server error")
log.Printf("SetItemQuantity(): ERROR... Failed to set item quantity... %s", exec_set_quantity_err.Error())
return
}
_, exec_update_order_err := conn.Exec(context.Background(), queries.UPDATE_ORDER_FILLED, request.OrderId)
if exec_update_order_err != nil {
ctx.String(http.StatusInternalServerError, "SetItemMade(): ERROR... Failed to update order")
log.Printf("SetItemQuantity(): ERROR... Failed to update order... %s", exec_update_order_err.Error())
return
}
var order_filled dto.OrderFilledResponse
scan_err := conn.QueryRow(context.Background(), queries.GET_ORDER_MADE_DETAILS, request.OrderId).Scan(&order_filled.OrderId, &order_filled.Filled)
if scan_err != nil {
if scan_err == pgx.ErrNoRows {
ctx.String(http.StatusBadRequest, "SetItemQuantity(): Invalid data")
return
}
ctx.String(http.StatusInternalServerError, "SetItemQuantity(): ERROR... Error querying result")
log.Printf("SetItemQuantity(): ERROR... Error querying result... %s", scan_err.Error())
return
}
ctx.JSON(http.StatusOK, order_filled)
}
}
func DeleteOrderItem(pool *pgxpool.Pool) gin.HandlerFunc {
return func(ctx *gin.Context) {
conn, conn_err := pool.Acquire(ctx)
if conn_err != nil {
log.Printf("DeleteOrderItem(): ERROR: Failed to connect... %s", conn_err.Error())
ctx.AbortWithStatus(http.StatusInternalServerError)
return
}
defer conn.Release()
var request dto.DeleteOrderItemRequest
request_read_err := ctx.ShouldBindJSON(&request)
if request_read_err != nil {
ctx.String(http.StatusBadRequest, "DeleteOrderItem(): ERROR... invalid request")
return
}
_, exec_err := conn.Exec(context.Background(), queries.REMOVE_ITEM_FROM_ORDER, request.ItemId, request.OrderId)
if exec_err != nil {
ctx.String(http.StatusInternalServerError, "DeleteOrderItem(): ERROR.. Failed to delete item")
log.Printf("DeleteOrderItem(): ERROR... Failed to delte item... %s", exec_err.Error())
return
}
}
}
func DeleteItem(pool *pgxpool.Pool) gin.HandlerFunc {
return func(ctx *gin.Context) {
conn, conn_err := pool.Acquire(ctx)
if conn_err != nil {
log.Printf("DeleteItem(): ERROR: Failed to connect... %s", conn_err.Error())
ctx.AbortWithStatus(http.StatusInternalServerError)
return
}
defer conn.Release()
item_id := ctx.Query("item_id")
item_id_int, parse_err := strconv.ParseInt(item_id, 10, 64)
if parse_err != nil || item_id == "" {
ctx.String(http.StatusBadRequest, "DeleteItem(): Invalid data entry")
return
}
_, exec_err := conn.Exec(context.Background(), "DELETE FROM item WHERE id = $1", item_id_int)
if exec_err != nil {
ctx.String(http.StatusInternalServerError, "DeleteItem(): Failed to delete item")
log.Printf("DeleteItem(): ERROR... Failed to delete item... %s", exec_err.Error())
return
}
}
}

View File

@@ -31,7 +31,7 @@ func CreateOrder(pool *pgxpool.Pool) gin.HandlerFunc {
var current_user dto.UserResponse var current_user dto.UserResponse
user_query_err := conn.QueryRow(context.Background(), queries.GET_CURRENT_USER_OBJECT, sub_id).Scan(&current_user.Id, &current_user.Name, &current_user.Job_Position, &current_user.Active, &current_user.Admin) user_query_err := conn.QueryRow(context.Background(), queries.GET_CURRENT_USER_OBJECT, sub_id).Scan(&current_user.Id, &current_user.Name, &current_user.JobPosition, &current_user.Active, &current_user.Admin)
if user_query_err != nil { if user_query_err != nil {
log.Printf("CreateOrder(): ERROR Failed to query user... %s", user_query_err.Error()) log.Printf("CreateOrder(): ERROR Failed to query user... %s", user_query_err.Error())
@@ -139,6 +139,14 @@ func GetOrderTable(pool *pgxpool.Pool) gin.HandlerFunc {
return return
} }
var table_query dto.OrderTableQuery
bind_err := ctx.ShouldBindJSON(&table_query)
if bind_err != nil {
ctx.String(http.StatusBadRequest, "GetOrderTable(): ERROR... invalid query")
return
}
filter := ctx.Query("filter") filter := ctx.Query("filter")
filter_int, filter_parse_err := strconv.ParseInt(filter, 10, 64) filter_int, filter_parse_err := strconv.ParseInt(filter, 10, 64)
if filter == "" || filter_parse_err != nil { if filter == "" || filter_parse_err != nil {
@@ -147,7 +155,7 @@ func GetOrderTable(pool *pgxpool.Pool) gin.HandlerFunc {
} }
query_string := utils.GetOrderTableQueryString(filter_int) query_string := utils.GetOrderTableQueryString(filter_int)
rows, query_err := conn.Query(context.Background(), query_string, utils.PAGE_SIZE*page_int, utils.PAGE_SIZE) rows, query_err := conn.Query(context.Background(), query_string, utils.PAGE_SIZE*page_int, utils.PAGE_SIZE, table_query.Orderer, table_query.DateDue, table_query.DatePlaced)
if query_err != nil { if query_err != nil {
ctx.String(http.StatusInternalServerError, "GetOrdertable(): ERROR... internal server error") ctx.String(http.StatusInternalServerError, "GetOrdertable(): ERROR... internal server error")
log.Printf("GetOrderTable(): ERROR... Failed to query table...\nQUERY: %s\n %s", query_string, query_err.Error()) log.Printf("GetOrderTable(): ERROR... Failed to query table...\nQUERY: %s\n %s", query_string, query_err.Error())
@@ -172,8 +180,28 @@ func GetOrderTable(pool *pgxpool.Pool) gin.HandlerFunc {
} }
} }
func SetOrderFilled(pool *pgxpool.Pool) gin.HandlerFunc { func DeleteOrder(pool *pgxpool.Pool) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
conn, conn_err := pool.Acquire(ctx)
if conn_err != nil {
log.Printf("DeleteOrder(): ERROR: Failed to connect... %s", conn_err.Error())
ctx.AbortWithStatus(http.StatusInternalServerError)
return
}
defer conn.Release()
order_id := ctx.Query("order_id")
order_id_int, parse_err := strconv.ParseInt(order_id, 10, 64)
if parse_err != nil {
ctx.String(http.StatusBadRequest, "DeleteOrder(): Invalid input")
return
}
_, exec_err := conn.Exec(context.Background(), "DELETE FROM order_record WHERE id = $1", order_id_int)
if exec_err != nil {
ctx.String(http.StatusInternalServerError, "DeleteOrder(): ERROR... failed to delete order")
log.Printf("DeleteOrder(): ERROR... Failed to delete order %s", exec_err.Error())
return
}
} }
} }

View File

@@ -21,6 +21,7 @@ func SetUserName(pool *pgxpool.Pool) gin.HandlerFunc {
if err != nil { if err != nil {
// TODO: Log this error // TODO: Log this error
ctx.String(http.StatusInternalServerError, err.Error()) ctx.String(http.StatusInternalServerError, err.Error())
log.Printf("SetUserName(): ERROR... Failed to acquire connection... %s", err.Error())
return return
} }
user_profile, _ := ctx.Get("user_profile") user_profile, _ := ctx.Get("user_profile")
@@ -109,6 +110,13 @@ func GetUserTable(pool *pgxpool.Pool) gin.HandlerFunc {
} }
defer conn.Release() defer conn.Release()
var request dto.UserRequest
body_read_err := ctx.ShouldBindJSON(&request)
if body_read_err != nil {
ctx.String(http.StatusBadRequest, "GetUserTable(): ERROR... Invalid user query object")
return
}
page := ctx.Query("page") page := ctx.Query("page")
if page == "" { if page == "" {
ctx.String(http.StatusBadRequest, "GetUserTable(): Missing page") ctx.String(http.StatusBadRequest, "GetUserTable(): Missing page")
@@ -119,7 +127,7 @@ func GetUserTable(pool *pgxpool.Pool) gin.HandlerFunc {
ctx.String(http.StatusBadRequest, "GetUserTable(): Not an integer") ctx.String(http.StatusBadRequest, "GetUserTable(): Not an integer")
return return
} }
rows, query_err := conn.Query(context.Background(), queries.USER_GET_TABLE_DATA, page_int*utils.PAGE_SIZE, utils.PAGE_SIZE) rows, query_err := conn.Query(context.Background(), queries.USER_GET_TABLE_DATA, page_int*utils.PAGE_SIZE, utils.PAGE_SIZE, request.Name, request.JobPosition)
if query_err != nil { if query_err != nil {
ctx.String(http.StatusInternalServerError, "GetUserTable(): Failed to query database...") ctx.String(http.StatusInternalServerError, "GetUserTable(): Failed to query database...")
log.Printf("GetUserTable(): ERROR... %s", query_err.Error()) log.Printf("GetUserTable(): ERROR... %s", query_err.Error())
@@ -129,7 +137,7 @@ func GetUserTable(pool *pgxpool.Pool) gin.HandlerFunc {
var users []dto.UserResponse var users []dto.UserResponse
for rows.Next() { for rows.Next() {
var user dto.UserResponse var user dto.UserResponse
scan_err := rows.Scan(&user.Id, &user.Name, &user.Job_Position, &user.Active, &user.Admin) scan_err := rows.Scan(&user.Id, &user.Name, &user.JobPosition, &user.Active, &user.Admin)
if scan_err != nil { if scan_err != nil {
ctx.String(http.StatusInternalServerError, "GetUserTable(): ERROR: Failed to scan..") ctx.String(http.StatusInternalServerError, "GetUserTable(): ERROR: Failed to scan..")
log.Printf("GetUserTable(): ERROR... %s", scan_err.Error()) log.Printf("GetUserTable(): ERROR... %s", scan_err.Error())
@@ -158,7 +166,7 @@ func GetCurrentAuthenticatedUser(pool *pgxpool.Pool) gin.HandlerFunc {
var user dto.UserResponse var user dto.UserResponse
query_err := conn.QueryRow(context.Background(), queries.GET_CURRENT_USER_OBJECT, sub_id).Scan(&user.Id, &user.Name, &user.Job_Position, &user.Active, &user.Admin) query_err := conn.QueryRow(context.Background(), queries.GET_CURRENT_USER_OBJECT, sub_id).Scan(&user.Id, &user.Name, &user.JobPosition, &user.Active, &user.Admin)
if query_err != nil { if query_err != nil {
ctx.String(http.StatusInternalServerError, "GetCurrentAuthenticatedUser(): ERROR.... Failed to query") ctx.String(http.StatusInternalServerError, "GetCurrentAuthenticatedUser(): ERROR.... Failed to query")
log.Printf("GetCurrentAuthenticatedUser(): ERROR in querying user table... %s", query_err.Error()) log.Printf("GetCurrentAuthenticatedUser(): ERROR in querying user table... %s", query_err.Error())

View File

@@ -0,0 +1,6 @@
package dto
type DeleteOrderItemRequest struct {
OrderId int `json:"order_id"`
ItemId int `json:"item_id"`
}

View File

@@ -0,0 +1,7 @@
package dto
type ItemOrderSetMadeRequest struct {
OrderId int `json:"order_id"`
ItemId int `json:"item_id"`
Made int `json:"made"`
}

View File

@@ -0,0 +1,7 @@
package dto
type ItemOrderSetQuantityRequest struct {
OrderId int `json:"order_id"`
ItemId int `json:"item_id"`
Quantity int `json:"quantity"`
}

View File

@@ -0,0 +1,6 @@
package dto
type OrderFilledResponse struct {
OrderId int `json:"order_id"`
Filled bool `json:"filled"`
}

View File

@@ -0,0 +1,7 @@
package dto
type OrderTableQuery struct {
Orderer string `json:"orderer"`
DateDue string `json:"date_due"`
DatePlaced string `json:"date_placed"`
}

6
api/dto/user_request.go Normal file
View File

@@ -0,0 +1,6 @@
package dto
type UserRequest struct {
Name string `json:"name"`
JobPosition string `json:"job_position"`
}

View File

@@ -3,9 +3,9 @@ package dto
// User response for exposing to the front-end // User response for exposing to the front-end
// :3 // :3
type UserResponse struct { type UserResponse struct {
Id int Id int `json: "id"`
Name string Name string `json: "name"`
Job_Position string JobPosition string `json: "job_position"`
Active bool Active bool `json: "active"`
Admin bool Admin bool `json: "admin"`
} }

View File

@@ -84,8 +84,13 @@ func main() {
router.PUT("/user/position", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.SetUserPosition(pool)) router.PUT("/user/position", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.SetUserPosition(pool))
router.PUT("/item/price", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.SetItemPrice(pool)) router.PUT("/item/price", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.SetItemPrice(pool))
router.PUT("/order/item", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, controllers.AddItemToOrder(pool)) router.PUT("/order/item", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, controllers.AddItemToOrder(pool))
router.PUT("/item/made", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, controllers.SetItemMade(pool))
router.PUT("/item/quantity", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, controllers.SetItemQuantity(pool))
router.DELETE("/user/deactivate", middleware.IsAuthenticated(authenticator), middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.DeactivateUser(pool)) router.DELETE("/user/deactivate", middleware.IsAuthenticated(authenticator), middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.DeactivateUser(pool))
router.DELETE("/order/item", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, controllers.DeleteOrderItem(pool))
router.DELETE("/order", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, controllers.DeleteOrder(pool))
router.DELETE("/item", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.DeleteItem(pool))
router.Run("localhost:8080") router.Run("localhost:8080")
} }

View File

@@ -85,3 +85,13 @@ DELETE FROM order_item WHERE item_id = $1 AND order_id = $2;
const SET_ITEM_IS_IN_SEASON = ` const SET_ITEM_IS_IN_SEASON = `
UPDATE item SET in_season = $1 WHERE id = $2; UPDATE item SET in_season = $1 WHERE id = $2;
` `
const GET_ORDER_MADE_DETAILS = `
SELECT
id,
filled
FROM
order_record
WHERE
id = $1;
`

View File

@@ -61,7 +61,7 @@ WITH order_bill AS (
WHERE WHERE
orec.user_id = $1 orec.user_id = $1
AND orec.orderer = $2 AND orec.orderer = $2
AND orec.date_place = $3 AND orec.date_placed = $3
GROUP BY orec.id GROUP BY orec.id
) totals ON orec.id = totals.id ) totals ON orec.id = totals.id
) )
@@ -163,3 +163,19 @@ ORDER BY date_due DESC
OFFSET $1 OFFSET $1
LIMIT $2; LIMIT $2;
` `
const UPDATE_ORDER_FILLED = `UPDATE order_record
SET filled = (
SELECT order_filled
FROM order_record orec
INNER JOIN (SELECT
orec.id,
COALESCE(EVERY(made = quantity), false) AS order_filled
FROM
order_record orec
LEFT JOIN order_item oi ON oi.order_id = orec.id
GROUP BY
orec.id) orec_result ON orec.id = orec_result.id
AND orec.id = $1
) WHERE order_record.id = $1
`

View File

@@ -31,6 +31,9 @@ FROM
ordr_user ordr_user
INNER JOIN ordr_position INNER JOIN ordr_position
ON job_position = ordr_position.id ON job_position = ordr_position.id
WHERE
user_name LIKE '%' || $3 ||'%'
AND position_name LIKE '%' || $4 || '%'
ORDER BY user_name ORDER BY user_name
OFFSET $1 OFFSET $1
LIMIT $2; LIMIT $2;
@@ -45,7 +48,7 @@ SELECT
is_admin::boolean is_admin::boolean
FROM FROM
ordr_user ordr_user
INNER JOIN ordr_position LEFT JOIN ordr_position
ON job_position = ordr_position.id ON job_position = ordr_position.id
AND ordr_user.sub_id = $1; AND ordr_user.sub_id = $1;
` `

View File

@@ -64,11 +64,15 @@ func GetOrderTableQueryString(filter int64) string {
ORDER BY date_due ` + order + ` ORDER BY date_due ` + order + `
OFFSET $1 OFFSET $1
LIMIT $2; LIMIT $2;
` `
filter = filter & ^ASCEND_DATE_DUE filter = filter & ^ASCEND_DATE_DUE
conditions := "" conditions := `
WHERE
orderer LIKE '%' || $3 || '%'
AND TO_CHAR(date_due, 'YYYY-MM-DD') LIKE '%' || $4 || '%'
AND TO_CHAR(date_placed, 'YYYY-MM-DD') LIKE '%' || $5 || '%'
`
if filter > 0 { if filter > 0 {
if filter&FUTURE_FILTER > 0 { if filter&FUTURE_FILTER > 0 {
conditions += "AND date_due > now()\n" conditions += "AND date_due > now()\n"
@@ -94,9 +98,6 @@ func GetOrderTableQueryString(filter int64) string {
if filter&UNPAID_FILTER > 0 { if filter&UNPAID_FILTER > 0 {
conditions += "AND paid = FALSE\n" conditions += "AND paid = FALSE\n"
} }
if len(conditions) > 0 {
conditions = "WHERE " + conditions[4:]
}
} }
query_string := query_string_first_part + conditions + query_string_second_part query_string := query_string_first_part + conditions + query_string_second_part

View File

@@ -1,8 +1,15 @@
CREATE TABLE IF NOT EXISTS ordr_position (
id SERIAL NOT NULL PRIMARY KEY,
position_name TEXT NOT NULL
);
INSERT INTO ordr_position VALUES (0, 'Unassigned');
CREATE TABLE IF NOT EXISTS ordr_user ( CREATE TABLE IF NOT EXISTS ordr_user (
id SERIAL NOT NULL PRIMARY KEY, id SERIAL NOT NULL PRIMARY KEY,
sub_id TEXT UNIQUE NOT NULL, sub_id TEXT UNIQUE NOT NULL,
user_name TEXT NOT NULL, user_name TEXT NOT NULL,
position TEXT DEFAULT "", position INTEGER NOT NULL DEFAULT 0 REFERENCES ordr_position ON DELETE SET NULL,
active BOOLEAN NOT NULL DEFAULT TRUE, active BOOLEAN NOT NULL DEFAULT TRUE,
is_admin BOOLEAN NOT NULL DEFAULT FALSE is_admin BOOLEAN NOT NULL DEFAULT FALSE
); );
@@ -25,7 +32,7 @@ CREATE TABLE IF NOT EXISTS item (
); );
CREATE TABLE IF NOT EXISTS item_price_history ( CREATE TABLE IF NOT EXISTS item_price_history (
item_id INTEGER NOT NULL REFERENCES item(id), item_id INTEGER NOT NULL REFERENCES item(id) ON DELETE CASCADE,
price REAL NOT NULL, price REAL NOT NULL,
valid_from TIMESTAMP NOT NULL DEFAULT now(), valid_from TIMESTAMP NOT NULL DEFAULT now(),
valid_to TIMESTAMP, valid_to TIMESTAMP,
@@ -33,8 +40,8 @@ CREATE TABLE IF NOT EXISTS item_price_history (
); );
CREATE TABLE IF NOT EXISTS order_item ( CREATE TABLE IF NOT EXISTS order_item (
item_id INTEGER NOT NULL REFERENCES item(id), item_id INTEGER NOT NULL REFERENCES item(id) ON DELETE CASCADE,
order_id INTEGER NOT NULL REFERENCES order_record(id), order_id INTEGER NOT NULL REFERENCES order_record(id) ON DELETE CASCADE,
quantity INTEGER NOT NULL, quantity INTEGER NOT NULL,
made INTEGER NOT NULL DEFAULT 0, made INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT now(), created_at TIMESTAMP NOT NULL DEFAULT now(),
@@ -48,6 +55,6 @@ CREATE TABLE IF NOT EXISTS label (
); );
CREATE TABLE IF NOT EXISTS order_label ( CREATE TABLE IF NOT EXISTS order_label (
order_id INTEGER NOT NULL REFERENCES order_record(id), order_id INTEGER NOT NULL REFERENCES order_record(id) ON DELETE CASCADE,
label_id INTEGER NOT NULL REFERENCES label(id) label_id INTEGER NOT NULL REFERENCES label(id) ON DELETE CASCADE
); );