diff --git a/api/controllers/itemController.go b/api/controllers/itemController.go index a9ab3c3..aa63856 100644 --- a/api/controllers/itemController.go +++ b/api/controllers/itemController.go @@ -231,3 +231,53 @@ func AddItemToOrder(pool *pgxpool.Pool) gin.HandlerFunc { ctx.JSON(http.StatusOK, order_item_price) } } + +func GetOrderItems(pool *pgxpool.Pool) gin.HandlerFunc { + return func(ctx *gin.Context) { + conn, conn_err := pool.Acquire(ctx) + if conn_err != nil { + log.Printf("GetOrderItems(): ERROR: Failed to connect... %s", conn_err.Error()) + ctx.AbortWithStatus(http.StatusInternalServerError) + return + } + defer conn.Release() + + order_id := ctx.Query("order_id") + + if order_id == "" { + ctx.String(http.StatusBadRequest, "GetOrderItems(): ERROR: order id not supplied") + return + } + + _, parse_err := strconv.ParseInt(order_id, 10, 64) + + if parse_err != nil { + ctx.String(http.StatusBadRequest, "GetOrderItem(): ERROR... order id not an integer") + return + } + + rows, query_err := conn.Query(context.Background(), queries.GET_ORDER_ITEMS, order_id) + if query_err != nil { + ctx.String(http.StatusInternalServerError, "GetOrderItems(): ERROR... failed to query database.") + log.Printf("GetOrderItems(): ERROR... Failed to query database... %s", query_err.Error()) + return + } + defer rows.Close() + + var items []dto.OrderItemPriceResponse + + for rows.Next() { + var item dto.OrderItemPriceResponse + scan_err := rows.Scan(&item.ItemId, &item.OrderId, &item.ItemName, &item.Quantity, &item.Made, &item.CreatedAt, &item.TotalPrice, &item.UnitPrice) + if scan_err != nil { + ctx.String(http.StatusInternalServerError, "GetOrderItems(): ERROR... Failed to scan data...") + log.Printf("GetOrderItems(): ERROR... failed to scan data... %s", scan_err.Error()) + return + } + + items = append(items, item) + } + + ctx.JSON(http.StatusOK, items) + } +} diff --git a/api/controllers/orderController.go b/api/controllers/orderController.go index 1bb4bea..ac95052 100644 --- a/api/controllers/orderController.go +++ b/api/controllers/orderController.go @@ -6,9 +6,12 @@ import ( "net/http" "ordr-api/dto" "ordr-api/queries" + "ordr-api/utils" + "strconv" "time" "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) @@ -71,7 +74,7 @@ func CreateOrder(pool *pgxpool.Pool) gin.HandlerFunc { } var order dto.OrderResponse - order_query_err := conn.QueryRow(context.Background(), queries.GET_ORDER_BY_ORDER_INFORMATION, current_user.Id, orderer, date_placed).Scan(&order.Id, &order.UserId, &order.Orderer, &order.DateDue, &order.DatePlaced, &order.AmountPaid, &order.Filled, &order.Delivered) + order_query_err := conn.QueryRow(context.Background(), queries.GET_TOTAL_ORDER_FROM_ORDER_INFORMATION, current_user.Id, orderer, date_placed).Scan(&order.Id, &order.UserId, &order.Orderer, &order.DateDue, &order.DatePlaced, &order.AmountPaid, &order.OrderTotal, &order.AmountDue, &order.Filled, &order.Delivered) if order_query_err != nil { ctx.String(http.StatusInternalServerError, "CreateOrder(): Failed to create order") log.Printf("CreateOrder(): ERROR... failed to query order after creation ... %s", order_query_err.Error()) @@ -82,6 +85,93 @@ func CreateOrder(pool *pgxpool.Pool) gin.HandlerFunc { } } +func GetOrderByOrderId(pool *pgxpool.Pool) gin.HandlerFunc { + return func(ctx *gin.Context) { + conn, conn_err := pool.Acquire(ctx) + if conn_err != nil { + log.Printf("GetOrderByOrderId(): ERROR: Failed to connect... %s", conn_err.Error()) + ctx.AbortWithStatus(http.StatusInternalServerError) + return + } + defer conn.Release() + + order_id := ctx.Query("order_id") + _, order_id_parse_err := strconv.ParseInt(order_id, 10, 64) + if order_id == "" || order_id_parse_err != nil { + ctx.String(http.StatusBadRequest, "GetOrderByOrderId(): ERROR... order_id not valid") + return + } + + row := conn.QueryRow(context.Background(), queries.GET_TOTAL_ORDER_FROM_ORDER_ID, order_id) + + var order_response dto.OrderResponse + + scan_err := row.Scan(&order_response.Id, &order_response.UserId, &order_response.Orderer, &order_response.DateDue, &order_response.DatePlaced, &order_response.AmountPaid, &order_response.OrderTotal, &order_response.AmountDue, &order_response.Filled, &order_response.Delivered) + + if scan_err != nil { + if scan_err == pgx.ErrNoRows { + ctx.String(http.StatusBadRequest, "GetOrderByOrderId(): ERROR... no order matches the query") + return + } + ctx.String(http.StatusInternalServerError, "GetOrderByOrderId(): ERROR... internal server error") + log.Printf("GetOrderByOrderId(): ERROR... Failed to scan row... %s", scan_err.Error()) + return + } + + ctx.JSON(http.StatusOK, order_response) + } +} + +func GetOrderTable(pool *pgxpool.Pool) gin.HandlerFunc { + return func(ctx *gin.Context) { + conn, conn_err := pool.Acquire(ctx) + if conn_err != nil { + log.Printf("GetOrderTable(): ERROR: Failed to connect... %s", conn_err.Error()) + ctx.AbortWithStatus(http.StatusInternalServerError) + return + } + defer conn.Release() + + page := ctx.Query("page") + page_int, page_parse_err := strconv.ParseInt(page, 10, 64) + if page == "" || page_parse_err != nil { + ctx.String(http.StatusBadRequest, "GetOrderTable(): Invalid page number") + return + } + + filter := ctx.Query("filter") + filter_int, filter_parse_err := strconv.ParseInt(filter, 10, 64) + if filter == "" || filter_parse_err != nil { + ctx.String(http.StatusBadRequest, "GetOrderTable(): Invalid Filter Options") + return + } + + query_string := utils.GetOrderTableQueryString(filter_int) + rows, query_err := conn.Query(context.Background(), query_string, utils.PAGE_SIZE*page_int, utils.PAGE_SIZE) + if query_err != nil { + 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()) + return + } + defer rows.Close() + + var orders []dto.OrderResponse + for rows.Next() { + var order_response dto.OrderResponse + scan_err := rows.Scan(&order_response.Id, &order_response.UserId, &order_response.Orderer, &order_response.DateDue, &order_response.DatePlaced, &order_response.AmountPaid, &order_response.OrderTotal, &order_response.AmountDue, &order_response.Filled, &order_response.Delivered) + if scan_err != nil { + ctx.String(http.StatusInternalServerError, "GetOrdertable(): ERROR... internal server error") + log.Printf("GetOrderTable(): ERROR... Failed to scan query... %s", scan_err.Error()) + return + } + + orders = append(orders, order_response) + } + + ctx.JSON(http.StatusOK, orders) + } +} + func SetOrderFilled(pool *pgxpool.Pool) gin.HandlerFunc { return func(ctx *gin.Context) { diff --git a/api/controllers/userController.go b/api/controllers/userController.go index bd209d6..3106249 100644 --- a/api/controllers/userController.go +++ b/api/controllers/userController.go @@ -6,6 +6,7 @@ import ( "net/http" "ordr-api/dto" "ordr-api/queries" + "ordr-api/utils" "strconv" "github.com/gin-gonic/gin" @@ -118,7 +119,7 @@ func GetUserTable(pool *pgxpool.Pool) gin.HandlerFunc { ctx.String(http.StatusBadRequest, "GetUserTable(): Not an integer") return } - rows, query_err := conn.Query(context.Background(), queries.USER_GET_TABLE_DATA, page_int*10, 10) + rows, query_err := conn.Query(context.Background(), queries.USER_GET_TABLE_DATA, page_int*utils.PAGE_SIZE, utils.PAGE_SIZE) if query_err != nil { ctx.String(http.StatusInternalServerError, "GetUserTable(): Failed to query database...") log.Printf("GetUserTable(): ERROR... %s", query_err.Error()) diff --git a/api/dto/order_response.go b/api/dto/order_response.go index efa67be..a19c1c0 100644 --- a/api/dto/order_response.go +++ b/api/dto/order_response.go @@ -9,6 +9,8 @@ type OrderResponse struct { DateDue time.Time DatePlaced time.Time AmountPaid float64 + OrderTotal float64 + AmountDue float64 Filled bool Delivered bool } diff --git a/api/main.go b/api/main.go index dc3504e..ae3ec13 100644 --- a/api/main.go +++ b/api/main.go @@ -70,6 +70,9 @@ func main() { router.GET("/users", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.GetUserTable(pool)) router.GET("/user/current", user_authenticated, middleware.GetUserProfile, user_in_db, controllers.GetCurrentAuthenticatedUser(pool)) router.GET("/item/price/current", user_authenticated, middleware.GetUserProfile, user_in_db, controllers.GetCurrentItemPrice(pool)) + router.GET("/order/items", user_authenticated, middleware.GetUserProfile, user_in_db, controllers.GetOrderItems(pool)) + router.GET("/order", user_authenticated, middleware.GetUserProfile, user_in_db, controllers.GetOrderByOrderId(pool)) + router.GET("/order/table", user_authenticated, middleware.GetUserProfile, user_in_db, controllers.GetOrderTable(pool)) router.POST("/position/create", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.CreatePosition(pool)) router.POST("/item/create", user_authenticated, middleware.GetUserProfile, user_in_db, user_active, user_is_admin, controllers.CreateItem(pool)) diff --git a/api/ordr b/api/ordr new file mode 160000 index 0000000..5ddedad --- /dev/null +++ b/api/ordr @@ -0,0 +1 @@ +Subproject commit 5ddedadd242b68a83654f0944a06c855571d45b7 diff --git a/api/queries/ItemQueries.go b/api/queries/ItemQueries.go index bddf5f0..a9a59d9 100644 --- a/api/queries/ItemQueries.go +++ b/api/queries/ItemQueries.go @@ -71,7 +71,7 @@ SELECT iph.price AS unit_price FROM order_item oi - INNER JOIN item i ON oi.item_id = i.init_db_pool + INNER JOIN item i ON oi.item_id = i.id AND oi.order_id = $1 INNER JOIN item_price_history iph ON iph.item_id = i.id AND iph.valid_from <= oi.created_at diff --git a/api/queries/OrderQueries.go b/api/queries/OrderQueries.go index 31f22e2..6ccc74b 100644 --- a/api/queries/OrderQueries.go +++ b/api/queries/OrderQueries.go @@ -4,24 +4,6 @@ const CREATE_ORDER = ` INSERT INTO order_record(user_id, orderer, date_due, date_placed) VALUES ($1, $2, $3, $4); ` -const GET_ORDER_BY_ORDER_INFORMATION = ` -SELECT - id, - user_id, - orderer, - date_due, - date_placed, - amount_paid, - filled, - delivered -FROM - order_record -WHERE - user_id = $1 - AND orderer = $2 - AND date_placed = $3; -` - const SET_ORDER_FILLED = ` UPDATE order_record SET filled = $1 WHERE id = $2 ` @@ -37,6 +19,7 @@ UPDATE order_record SET amount_paid = $1 WHERE id = $2 const GET_ORDER_TOTAL_AND_BALANCE = ` SELECT orec.id, + order_total, order_total - orec.amount_paid AS balance FROM order_record orec @@ -48,10 +31,135 @@ FROM order_record orec INNER JOIN order_item oi ON oi.order_id = orec.id INNER JOIN item i ON oi.item_id = i.id - AND oi.order_id = 1 + AND oi.order_id = $1 INNER JOIN item_price_history iph ON iph.item_id = i.id AND iph.valid_from <= oi.created_at AND (iph.valid_to IS NULL OR iph.valid_to > oi.created_at) GROUP BY orec.id ) totals ON orec.id = totals.id ` + +const GET_TOTAL_ORDER_FROM_ORDER_INFORMATION = ` +WITH order_bill AS ( + SELECT + orec.id, + COALESCE(order_total, 0) AS order_total, + COALESCE(order_total - orec.amount_paid, 0) AS balance + FROM + order_record orec + INNER JOIN ( + SELECT + orec.id, + SUM(quantity * price) AS order_total + FROM + order_record orec + LEFT JOIN order_item oi ON oi.order_id = orec.id + LEFT JOIN item i ON oi.item_id = i.id + LEFT JOIN item_price_history iph ON iph.item_id = i.id + AND iph.valid_from <= oi.created_at + AND (iph.valid_to IS NULL OR iph.valid_to > oi.created_at) + WHERE + orec.user_id = $1 + AND orec.orderer = $2 + AND orec.date_place = $3 + GROUP BY orec.id + ) totals ON orec.id = totals.id +) + +SELECT + orec.id, + user_id, + orderer, + date_due, + date_placed, + amount_paid, + order_total, + balance, + filled, + delivered +FROM + order_record orec + INNER JOIN order_bill ON order_bill.id = orec.id; + ` + +const GET_TOTAL_ORDER_FROM_ORDER_ID = ` +WITH order_bill AS ( +SELECT + orec.id, + COALESCE(order_total, 0) AS order_total, + COALESCE(order_total - orec.amount_paid, 0) AS balance +FROM + order_record orec + INNER JOIN ( + SELECT + orec.id, + SUM(quantity * price) AS order_total + FROM + order_record orec + LEFT JOIN order_item oi ON oi.order_id = orec.id + LEFT JOIN item i ON oi.item_id = i.id + LEFT JOIN item_price_history iph ON iph.item_id = i.id + AND iph.valid_from <= oi.created_at + AND (iph.valid_to IS NULL OR iph.valid_to > oi.created_at) + WHERE orec.id = $1 + GROUP BY orec.id + ) totals ON orec.id = totals.id +) + +SELECT + orec.id, + user_id, + orderer, + date_due, + date_placed, + amount_paid, + order_total, + balance, + filled, + delivered +FROM + order_record orec + INNER JOIN order_bill ON order_bill.id = orec.id; +` + +const GET_ORDER_TABLE = ` +WITH order_bill AS ( +SELECT + orec.id, + COALESCE(order_total, 0) AS order_total, + COALESCE(order_total - orec.amount_paid, 0) AS balance +FROM + order_record orec + INNER JOIN ( + SELECT + orec.id, + SUM(quantity * price) AS order_total + FROM + order_record orec + LEFT JOIN order_item oi ON oi.order_id = orec.id + LEFT JOIN item i ON oi.item_id = i.id + LEFT JOIN item_price_history iph ON iph.item_id = i.id + AND iph.valid_from <= oi.created_at + AND (iph.valid_to IS NULL OR iph.valid_to > oi.created_at) + GROUP BY orec.id + ) totals ON orec.id = totals.id +) + +SELECT + orec.id, + user_id, + orderer, + date_due, + date_placed, + amount_paid, + order_total, + balance, + filled, + delivered +FROM + order_record orec + INNER JOIN order_bill ON order_bill.id = orec.id +ORDER BY date_due DESC +OFFSET $1 +LIMIT $2; +` diff --git a/api/utils/consts.go b/api/utils/consts.go new file mode 100644 index 0000000..9689a8f --- /dev/null +++ b/api/utils/consts.go @@ -0,0 +1,105 @@ +package utils + +const PAGE_SIZE = 10 + +const PAGE_SIZE_STRING = "10" + +// order page filters +const FUTURE_FILTER = 1 +const PAST_FILTER = 2 +const FILLED_FILTER = 4 +const UNFILLED_FILTER = 8 +const DELIVERED_FILTER = 16 +const UNDELIVERED_FILTER = 32 +const PAID_FILTER = 64 +const UNPAID_FILTER = 128 +const ASCEND_DATE_DUE = 256 + +func GetOrderTableQueryString(filter int64) string { + query_string_first_part := ` + WITH order_bill AS ( + SELECT + orec.id, + COALESCE(order_total, 0) AS order_total, + COALESCE(order_total - orec.amount_paid, 0) AS balance + FROM + order_record orec + INNER JOIN ( + SELECT + orec.id, + SUM(quantity * price) AS order_total + FROM + order_record orec + LEFT JOIN order_item oi ON oi.order_id = orec.id + LEFT JOIN item i ON oi.item_id = i.id + LEFT JOIN item_price_history iph ON iph.item_id = i.id + AND iph.valid_from <= oi.created_at + AND (iph.valid_to IS NULL OR iph.valid_to > oi.created_at) + GROUP BY orec.id + ) totals ON orec.id = totals.id + ) + + SELECT + orec.id, + user_id, + orderer, + date_due, + date_placed, + amount_paid, + order_total, + balance, + filled, + delivered + FROM + order_record orec + INNER JOIN order_bill ON order_bill.id = orec.id + ` + + order := "DESC" + if filter&ASCEND_DATE_DUE > 0 { + order = "ASC" + } + + query_string_second_part := ` + ORDER BY date_due ` + order + ` + OFFSET $1 + LIMIT $2; + + ` + filter = filter & ^ASCEND_DATE_DUE + + conditions := "" + if filter > 0 { + if filter&FUTURE_FILTER > 0 { + conditions += "AND date_due > now()\n" + } + if filter&PAST_FILTER > 0 { + conditions += "AND date_due < now()\n" + } + if filter&FILLED_FILTER > 0 { + conditions += "AND filled = TRUE\n" + } + if filter&UNFILLED_FILTER > 0 { + conditions += "AND filled = FALSE\n" + } + if filter&DELIVERED_FILTER > 0 { + conditions += "AND delivered = TRUE\n" + } + if filter&UNDELIVERED_FILTER > 0 { + conditions += "AND delivered = FALSE\n" + } + if filter&PAID_FILTER > 0 { + conditions += "AND paid = TRUE\n" + } + if filter&UNPAID_FILTER > 0 { + conditions += "AND paid = FALSE\n" + } + if len(conditions) > 0 { + conditions = "WHERE " + conditions[4:] + } + } + + query_string := query_string_first_part + conditions + query_string_second_part + + return query_string +}