From 94cac3d5638559a78bb1112bd0fa29210bfff8f9 Mon Sep 17 00:00:00 2001 From: meskio <meskio@sindominio.net> Date: Wed, 30 Dec 2020 19:08:48 +0100 Subject: [PATCH] Update order --- api/api.go | 1 + api/db/order.go | 231 +++++++++++++++++++++++++++++++++++++++------- api/order.go | 39 ++++++++ api/order_test.go | 224 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 463 insertions(+), 32 deletions(-) diff --git a/api/api.go b/api/api.go index bfab481..8a29074 100644 --- a/api/api.go +++ b/api/api.go @@ -58,6 +58,7 @@ func Init(dbPath string, signKey string, mail *Mail, r *mux.Router) error { r.HandleFunc("/order", a.auth(a.ListOrders)).Methods("GET") r.HandleFunc("/order", a.authOrderNum(a.AddOrder)).Methods("POST") r.HandleFunc("/order/{id:[0-9]+}", a.authNum(a.GetOrder)).Methods("GET") + r.HandleFunc("/order/{id:[0-9]+}", a.authNumRole(a.UpdateOrder)).Methods("PUT") r.HandleFunc("/order/{id:[0-9]+}", a.authNumRole(a.DeleteOrder)).Methods("DELETE") r.HandleFunc("/order/active", a.auth(a.ListActiveOrders)).Methods("GET") r.HandleFunc("/order/picks", a.authOrderNum(a.ListOrderPicks)).Methods("GET") diff --git a/api/db/order.go b/api/db/order.go index e4cfc5c..ece51ef 100644 --- a/api/db/order.go +++ b/api/db/order.go @@ -68,10 +68,14 @@ func (d *DB) GetOrder(memberNum int, id int) (order Order, transaction Transacti if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { err = ErrorNotFound - return } return } + + if memberNum == 0 { + return + } + err = d.db.Where("member = ? AND type = 'order' AND order_id = ?", memberNum, id). Preload("OrderPurchase.OrderProduct"). Find(&transaction).Error @@ -82,16 +86,131 @@ func (d *DB) AddOrder(order *Order) error { return d.db.Create(&order).Error } -func (d *DB) DeleteOrder(memberNum int, id int) error { - var order Order - err := d.db.Preload(clause.Associations). - Preload("Transactions.OrderPurchase"). - Preload("Transactions.Member"). - First(&order, id).Error +func (d *DB) UpdateOrder(memberNum int, id int, order *Order) error { + dbOrder, _, err := d.GetOrder(0, id) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrorNotFound + return err + } + + if memberNum != 0 && dbOrder.MemberNum != memberNum { + return ErrorInvalidRequest + } + if dbOrder.Deadline.Add(5 * 24 * time.Hour).Before(time.Now()) { + return ErrorInvalidRequest + } + for _, p := range order.Products { + err = d.orderProductExist(&p) + if err != nil { + return err + } + } + + dbOrder.Name = order.Name + dbOrder.Description = order.Description + dbOrder.Deadline = order.Deadline + + return d.db.Transaction(func(tx *gorm.DB) error { + for _, product := range order.Products { + var err error + dbProduct := findOrderProduct(product.ProductCode, dbOrder.Products) + if dbProduct != nil { + dbProduct.Price = product.Price + err = tx.Save(&dbProduct).Error + } else { + err = tx.Create(&product).Error + } + if err != nil { + return err + } + } + for _, product := range dbOrder.Products { + if findOrderProduct(product.ProductCode, order.Products) == nil { + err = tx.Delete(&product).Error + if err != nil { + return err + } + } + } + + totalSum := 0 + for i, t := range dbOrder.Transactions { + var transaction Transaction + err := tx.Preload("OrderPurchase.OrderProduct").First(&transaction, id).Error + if err != nil { + return err + } + total, _ := calculateOrderPurchaseTotal(transaction.OrderPurchase, order.Products) + err = updateOrderPurchase(tx, t.MemberNum, &dbOrder.Transactions[i], total, t.OrderPurchase) + if err != nil { + return err + } + totalSum += total + } + + if dbOrder.TransactionID != nil { + updateOrderTransaction(tx, int(*dbOrder.TransactionID), totalSum, &dbOrder) + } + err := tx.Save(&dbOrder).Error + if err != nil { + return err + } + return nil + }) +} + +func updateOrderTransaction(tx *gorm.DB, id int, total int, order *Order) error { + var transaction Transaction + err := tx.First(&transaction, id).Error + if err != nil { + return err + } + + if order.Deadline.After(time.Now()) { + err := updateMemberBalance(tx, order.MemberNum, -transaction.Total) + if err != nil { + return err + } + err = tx.Delete(&transaction).Error + if err != nil { + return err + } + order.Active = true + order.TransactionID = nil + } else { + totalDiff := total - transaction.Total + err := updateMemberBalance(tx, order.MemberNum, totalDiff) + if err != nil { + return err + } + transaction.Total = total + err = tx.Save(&transaction).Error + if err != nil { + return err + } + } + return nil +} + +func findOrderProduct(code int, products []OrderProduct) *OrderProduct { + for _, p := range products { + if p.ProductCode == code { + return &p } + } + return nil +} + +func (d *DB) orderProductExist(product *OrderProduct) error { + err := d.db.Where("code = ?", product.ProductCode).Find(&Product{}).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + err = ErrorNotFound + } + return err +} + +func (d *DB) DeleteOrder(memberNum int, id int) error { + order, _, err := d.GetOrder(0, id) + if err != nil { return err } if memberNum != 0 && order.MemberNum != memberNum { @@ -135,26 +254,14 @@ func (d *DB) AddOrderPurchase(memberNum int, orderID int, purchase []OrderPurcha return } - total := 0 - for _, p := range purchase { - found := false - for _, product := range order.Products { - if product.ID == p.OrderProductID { - total += product.Price * p.Amount - found = true - break - } - } - - if !found { - log.Printf("Order purchase product %d not in order: %v", p.OrderProductID, purchase) - err = ErrorInvalidRequest - return - } + total, err := calculateOrderPurchaseTotal(purchase, order.Products) + if err != nil { + return } if transaction.ID != 0 { - err = d.updateOrderPurchase(memberNum, &transaction, total, purchase) + transaction.Date = time.Now() + err = updateOrderPurchase(d.db, memberNum, &transaction, total, purchase) return } @@ -170,32 +277,92 @@ func (d *DB) AddOrderPurchase(memberNum int, orderID int, purchase []OrderPurcha return } -func (d *DB) updateOrderPurchase(memberNum int, transaction *Transaction, total int, purchase []OrderPurchase) error { +func calculateOrderPurchaseTotal(purchase []OrderPurchase, products []OrderProduct) (total int, err error) { + total = 0 + for _, p := range purchase { + found := false + for _, product := range products { + log.Printf("%v", p.OrderProduct) + if (p.OrderProduct != nil && product.ProductCode == p.OrderProduct.ProductCode) || + product.ID == p.OrderProductID { + total += product.Price * p.Amount + found = true + break + } + } + + if !found { + log.Printf("Order purchase product %d not in order: %v", p.OrderProductID, purchase) + err = ErrorInvalidRequest + } + } + return total, err +} + +func updateOrderPurchase(tx *gorm.DB, memberNum int, transaction *Transaction, total int, purchase []OrderPurchase) error { totalDiff := -(transaction.Total + total) transaction.Total = -total - transaction.Date = time.Now() - for i, p := range transaction.OrderPurchase { - for _, new_purchase := range purchase { + var updatePurchases []OrderPurchase + var newPurchases []OrderPurchase + for _, new_purchase := range purchase { + found := false + for i, p := range transaction.OrderPurchase { if new_purchase.OrderProductID == p.OrderProductID { transaction.OrderPurchase[i].Amount = new_purchase.Amount + updatePurchases = append(updatePurchases, transaction.OrderPurchase[i]) + found = true break } } + + if !found { + newPurchases = append(newPurchases, OrderPurchase{ + Amount: new_purchase.Amount, + TransactionID: transaction.ID, + OrderProductID: new_purchase.OrderProductID, + }) + } } - return d.db.Transaction(func(tx *gorm.DB) error { + var delPurchases []OrderPurchase + for _, p := range transaction.OrderPurchase { + found := false + for _, p2 := range purchase { + if p.OrderProductID == p2.OrderProductID { + found = true + break + } + } + if !found { + delPurchases = append(delPurchases, p) + } + } + + return tx.Transaction(func(tx *gorm.DB) error { err := updateMemberBalance(tx, memberNum, totalDiff) if err != nil { return err } - for _, p := range transaction.OrderPurchase { + for _, p := range delPurchases { + err = tx.Delete(&p).Error + if err != nil { + return err + } + } + for _, p := range updatePurchases { err = tx.Save(&p).Error if err != nil { return err } } + for _, p := range newPurchases { + err = tx.Create(&p).Error + if err != nil { + return err + } + } return tx.Save(&transaction).Error }) } diff --git a/api/order.go b/api/order.go index dddd9c1..259aa47 100644 --- a/api/order.go +++ b/api/order.go @@ -154,6 +154,45 @@ func (a *api) AddOrder(num int, w http.ResponseWriter, req *http.Request) { } } +func (a *api) UpdateOrder(num int, role string, w http.ResponseWriter, req *http.Request) { + var order db.Order + err := json.NewDecoder(req.Body).Decode(&order) + if err != nil { + log.Printf("Can't parse order: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + vars := mux.Vars(req) + id, _ := strconv.Atoi(vars["id"]) + if role == "admin" { + num = 0 + } + err = a.db.UpdateOrder(num, id, &order) + if err != nil { + if errors.Is(err, db.ErrorNotFound) { + w.WriteHeader(http.StatusNotFound) + return + } + if errors.Is(err, db.ErrorInvalidRequest) { + w.WriteHeader(http.StatusUnauthorized) + return + } + log.Printf("Can't update order %s: %v", vars["id"], err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + err = json.NewEncoder(w).Encode(order) + if err != nil { + log.Printf("Can't encode order: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } +} + func (a *api) AddOrderPurchase(num int, w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) id, _ := strconv.Atoi(vars["id"]) diff --git a/api/order_test.go b/api/order_test.go index 5833fbb..1130f13 100644 --- a/api/order_test.go +++ b/api/order_test.go @@ -423,6 +423,230 @@ func TestUpdateOrderPurchase(t *testing.T) { } } +func TestOrderUpdate(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + tapi.addTestMember() + tapi.addTestOrder() + + var orders []db.Order + resp := tapi.do("GET", "/order/active", nil, &orders) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get orders:", resp.Status) + } + + purchase := []db.OrderPurchase{ + { + OrderProductID: orders[0].Products[0].ID, + Amount: 3, + }, + } + resp = tapi.do("POST", fmt.Sprintf("/order/%d/purchase", orders[0].ID), purchase, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create order:", resp.Status) + } + + order := testOrder + order.Products[0].Price = 1000 + resp = tapi.doOrder("PUT", fmt.Sprintf("/order/%d", orders[0].ID), order, nil) + if resp.StatusCode != http.StatusAccepted { + tapi.t.Fatal("Can't update order:", resp.Status) + } + + var transactions []db.Transaction + resp = tapi.do("GET", "/transaction/mine", nil, &transactions) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get transactions:", resp.Status) + } + if len(transactions) != 1 { + t.Fatal("Wrong number of transactions", len(orders), orders) + } + total := 3 * order.Products[0].Price + if transactions[0].Total != -total { + t.Error("Wrong total", transactions[0].Total) + } + + var member db.Member + resp = tapi.do("GET", "/member/me", nil, &member) + if resp.StatusCode != http.StatusOK { + t.Error("Can't find the member:", resp.Status) + } + if member.Balance != testMember.Balance-total { + t.Error("Wrong product balance:", member.Balance) + } +} + +func TestOrderUpdateProduct(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + tapi.addTestMember() + tapi.addTestOrder() + + var orders []db.Order + resp := tapi.do("GET", "/order/active", nil, &orders) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get orders:", resp.Status) + } + + purchase := []db.OrderPurchase{ + { + OrderProductID: orders[0].Products[0].ID, + Amount: 3, + }, + } + resp = tapi.do("POST", fmt.Sprintf("/order/%d/purchase", orders[0].ID), purchase, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create order:", resp.Status) + } + + testProduct2 := db.Product{ + Code: 123, + Name: "Huevos", + Price: 120, + Stock: 15, + } + resp = tapi.doAdmin("POST", "/product", testProduct2, nil) + if resp.StatusCode != http.StatusCreated { + tapi.t.Fatal("Can't create product:", resp.Status) + } + + order := testOrder + order.Products[0].Price = testProduct2.Price + order.Products[0].ProductCode = testProduct2.Code + resp = tapi.doOrder("PUT", fmt.Sprintf("/order/%d", orders[0].ID), order, nil) + if resp.StatusCode != http.StatusAccepted { + tapi.t.Fatal("Can't update order:", resp.Status) + } + + var transactions []db.Transaction + resp = tapi.do("GET", "/transaction/mine", nil, &transactions) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get transactions:", resp.Status) + } + if len(transactions) != 1 { + t.Fatal("Wrong number of transactions", len(orders), orders) + } + if transactions[0].Total != 0 { + t.Error("Wrong total", transactions[0].Total) + } +} + +func TestOrderUpdateReactivate(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + tapi.addTestMember() + tapi.addTestProducts() + + order := testOrder + now := time.Now() + order.Deadline = time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local) + resp := tapi.doOrder("POST", "/order", order, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create order:", resp.Status) + } + + var orders []db.Order + resp = tapi.do("GET", "/order/active", nil, &orders) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get orders:", resp.Status) + } + + purchase := []db.OrderPurchase{ + { + OrderProductID: orders[0].Products[0].ID, + Amount: 3, + }, + } + resp = tapi.do("POST", fmt.Sprintf("/order/%d/purchase", orders[0].ID), purchase, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create order:", resp.Status) + } + + orders = tapi.deactivateOrders() + if len(orders) != 1 { + t.Error("Deactivated none orders:", orders) + } + + order.Deadline = testOrder.Deadline + resp = tapi.doOrder("PUT", fmt.Sprintf("/order/%d", orders[0].ID), order, nil) + if resp.StatusCode != http.StatusAccepted { + tapi.t.Fatal("Can't update order:", resp.Status) + } + + resp = tapi.do("GET", "/order/active", nil, &orders) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get orders:", resp.Status) + } + if len(orders) != 1 { + t.Error("The order is not being reactivated", orders) + } + + var transactions []db.Transaction + resp = tapi.doOrder("GET", "/transaction/mine", nil, &transactions) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get transactions:", resp.Status) + } + if len(transactions) != 0 { + t.Fatal("Wrong number of transactions", transactions) + } +} + +func TestOrderUpdateDeactivated(t *testing.T) { + tapi := newTestAPI(t) + defer tapi.close() + tapi.addTestMember() + tapi.addTestProducts() + + order := testOrder + now := time.Now() + order.Deadline = time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local) + resp := tapi.doOrder("POST", "/order", order, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create order:", resp.Status) + } + + var orders []db.Order + resp = tapi.do("GET", "/order/active", nil, &orders) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get orders:", resp.Status) + } + + purchase := []db.OrderPurchase{ + { + OrderProductID: orders[0].Products[0].ID, + Amount: 3, + }, + } + resp = tapi.do("POST", fmt.Sprintf("/order/%d/purchase", orders[0].ID), purchase, nil) + if resp.StatusCode != http.StatusCreated { + t.Fatal("Can't create order:", resp.Status) + } + + orders = tapi.deactivateOrders() + if len(orders) != 1 { + t.Error("Deactivated none orders:", orders) + } + + order.Products[0].Price = 1000 + resp = tapi.doOrder("PUT", fmt.Sprintf("/order/%d", orders[0].ID), order, nil) + if resp.StatusCode != http.StatusAccepted { + tapi.t.Fatal("Can't update order:", resp.Status) + } + + total := 3 * order.Products[0].Price + var transactions []db.Transaction + resp = tapi.doOrder("GET", "/transaction/mine", nil, &transactions) + if resp.StatusCode != http.StatusOK { + t.Fatal("Can't get transactions:", resp.Status) + } + if len(transactions) != 1 { + t.Fatal("Wrong number of transactions", transactions) + } + if transactions[0].Total != total { + t.Fatal("Wrong updated total", transactions[0].Total, total) + } +} + func TestOrderPicks(t *testing.T) { tapi := newTestAPI(t) defer tapi.close() -- GitLab