Skip to content
Snippets Groups Projects
order.go 10.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • meskio's avatar
    meskio committed
    package db
    
    import (
    	"errors"
    	"log"
    	"time"
    
    	"gorm.io/gorm"
    	"gorm.io/gorm/clause"
    )
    
    type Order struct {
    	gorm.Model
    	Name        string    `json:"name"`
    	Description string    `json:"description"`
    	MemberNum   int       `json:"member_num" gorm:"column:member"`
    	Member      *Member   `json:"member,omitempty" gorm:"foreignKey:MemberNum;references:Num"`
    	Deadline    time.Time `json:"deadline"`
    	Active      bool      `json:"active" gorm:"index"`
    
    
    	Products      []OrderProduct `json:"products"`
    	Transactions  []Transaction  `json:"transactions" gorm:"foreignKey:OrderID"`
    	TransactionID *uint          `json:"-" gorm:"column:transaction"`
    }
    
    type OrderProduct struct {
    	gorm.Model
    	OrderID     uint     `json:"-"`
    	ProductCode int      `json:"code"`
    	Product     *Product `json:"product" gorm:"foreignKey:ProductCode;references:Code"`
    	Price       int      `json:"price"`
    
    meskio's avatar
    meskio committed
    }
    
    type OrderPurchase struct {
    
    	gorm.Model     `json:"-"`
    	TransactionID  uint          `json:"-"`
    	OrderProductID uint          `json:"order_product_id"`
    
    meskio's avatar
    meskio committed
    	OrderProduct   *OrderProduct `json:"order_product" gorm:"constraint:OnDelete:CASCADE"`
    
    	Amount         int           `json:"amount"`
    
    meskio's avatar
    meskio committed
    }
    
    func (d *DB) ListOrders(active bool) (orders []Order, err error) {
    	query := d.db.Preload(clause.Associations).
    		Preload("Transactions.OrderPurchase")
    	if active {
    		query = query.Where("active = ?", true)
    	}
    	err = query.Order("deadline desc").
    		Find(&orders).Error
    	return
    }
    
    
    meskio's avatar
    meskio committed
    func (d *DB) ListOrderPicks(num int) (orders []Order, err error) {
    	err = d.db.Select("*, member = ? as member_selected", num).
    		Table("(?) as orders", d.db.Model(&Order{}).Order("deadline desc")).
    		Group("name").Order("member_selected desc, deadline desc").Limit(15).
    		Preload(clause.Associations).
    
    meskio's avatar
    meskio committed
    		Preload("Products.Product").
    
    meskio's avatar
    meskio committed
    		Find(&orders).Error
    	return
    }
    
    
    meskio's avatar
    meskio committed
    func (d *DB) GetOrder(memberNum int, id int) (order Order, transaction Transaction, err error) {
    	err = d.db.Preload(clause.Associations).
    
    		Preload("Products.Product").
    
    meskio's avatar
    meskio committed
    		Preload("Transactions.OrderPurchase").
    		Preload("Transactions.Member").
    		First(&order, id).Error
    
    meskio's avatar
    meskio committed
    	if err != nil {
    		if errors.Is(err, gorm.ErrRecordNotFound) {
    			err = ErrorNotFound
    		}
    
    meskio's avatar
    meskio committed
    		return
    	}
    
    meskio's avatar
    meskio committed
    
    	if memberNum == 0 {
    		return
    	}
    
    
    meskio's avatar
    meskio committed
    	err = d.db.Where("member = ? AND type = 'order' AND order_id = ?", memberNum, id).
    
    		Preload("OrderPurchase.OrderProduct.Product").
    
    meskio's avatar
    meskio committed
    		Find(&transaction).Error
    	return
    }
    
    func (d *DB) AddOrder(order *Order) error {
    	return d.db.Create(&order).Error
    }
    
    
    meskio's avatar
    meskio committed
    func (d *DB) UpdateOrder(memberNum int, id int, order *Order) error {
    	dbOrder, _, err := d.GetOrder(0, id)
    
    meskio's avatar
    meskio committed
    	if err != nil {
    
    meskio's avatar
    meskio committed
    		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 {
    		totalSum := 0
    		for i, t := range dbOrder.Transactions {
    			var transaction Transaction
    
    meskio's avatar
    meskio committed
    			err = tx.Preload("OrderPurchase.OrderProduct").First(&transaction, t.ID).Error
    			if err != nil {
    				return err
    			}
    
    			total, _ := calculateOrderPurchaseTotal(transaction.OrderPurchase, order.Products)
    
    meskio's avatar
    meskio committed
    			err = updateOrderPurchase(tx, t.MemberNum, &dbOrder.Transactions[i], total, t.OrderPurchase)
    			if err != nil {
    				return err
    			}
    			totalSum += total
    		}
    
    
    meskio's avatar
    meskio committed
    		products, err := updateOrderProducts(tx, *order, dbOrder)
    
    meskio's avatar
    meskio committed
    		if err != nil {
    			return err
    		}
    
    meskio's avatar
    meskio committed
    
    		if dbOrder.TransactionID != nil {
    			err = updateOrderTransaction(tx, int(*dbOrder.TransactionID), totalSum, &dbOrder)
    			if err != nil {
    				return err
    			}
    		}
    
    		dbOrder.Products = products
    		dbOrder.Transactions = []Transaction{}
    		return tx.Save(&dbOrder).Error
    
    meskio's avatar
    meskio committed
    	})
    }
    
    
    meskio's avatar
    meskio committed
    func updateOrderProducts(tx *gorm.DB, order Order, dbOrder Order) (products []OrderProduct, err error) {
    	for _, product := range order.Products {
    		dbProduct := findOrderProduct(product.ProductCode, dbOrder.Products)
    		if dbProduct != nil {
    			dbProduct.Price = product.Price
    			products = append(products, *dbProduct)
    
    			err = tx.Save(dbProduct).Error
    
    meskio's avatar
    meskio committed
    		} else {
    			product.OrderID = uint(dbOrder.ID)
    			err = tx.Create(&product).Error
    			products = append(products, product)
    		}
    		if err != nil {
    			return
    		}
    	}
    	for _, product := range dbOrder.Products {
    		if findOrderProduct(product.ProductCode, order.Products) == nil {
    			err = tx.Where("order_product_id = ?", product.ID).Delete(&OrderPurchase{}).Error
    			if err != nil {
    				return
    			}
    			err = tx.Delete(&product).Error
    			if err != nil {
    				return
    			}
    
    		}
    	}
    	return
    }
    
    
    meskio's avatar
    meskio committed
    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
    
    meskio's avatar
    meskio committed
    		}
    
    meskio's avatar
    meskio committed
    	}
    	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 {
    
    meskio's avatar
    meskio committed
    		return err
    	}
    	if memberNum != 0 && order.MemberNum != memberNum {
    		return ErrorInvalidRequest
    	}
    
    	return d.db.Transaction(func(tx *gorm.DB) error {
    		if order.TransactionID != nil {
    			var transaction Transaction
    			err = tx.First(&transaction, order.TransactionID).Error
    			if err != nil {
    				return err
    			}
    			order.Transactions = append(order.Transactions, transaction)
    		}
    
    		for _, transaction := range order.Transactions {
    			err := updateMemberBalance(tx, transaction.MemberNum, -transaction.Total)
    			if err != nil {
    				return err
    			}
    
    			err = tx.Select("OrderPurchase").Delete(&transaction).Error
    			if err != nil {
    				return err
    			}
    		}
    
    		return tx.Delete(&order).Error
    	})
    }
    
    
    meskio's avatar
    meskio committed
    func (d *DB) AddOrderPurchase(memberNum int, orderID int, purchase []OrderPurchase) (transaction Transaction, err error) {
    	order, transaction, err := d.GetOrder(memberNum, orderID)
    
    meskio's avatar
    meskio committed
    	if err != nil {
    		return
    	}
    	if !order.Active {
    		err = ErrorInvalidRequest
    		log.Printf("Order is not active %d: %v", order.ID, purchase)
    		return
    	}
    
    
    meskio's avatar
    meskio committed
    	total, err := calculateOrderPurchaseTotal(purchase, order.Products)
    	if err != nil {
    		return
    
    meskio's avatar
    meskio committed
    	if transaction.ID != 0 {
    
    meskio's avatar
    meskio committed
    		transaction.Date = time.Now()
    		err = updateOrderPurchase(d.db, memberNum, &transaction, total, purchase)
    
    meskio's avatar
    meskio committed
    	transaction = Transaction{
    		MemberNum:     memberNum,
    		Total:         -total,
    		Type:          "order",
    		Date:          time.Now(),
    		OrderPurchase: purchase,
    		OrderID:       &order.ID,
    	}
    	err = createTransaction(d.db, &transaction)
    	return
    }
    
    
    meskio's avatar
    meskio committed
    func calculateOrderPurchaseTotal(purchase []OrderPurchase, products []OrderProduct) (total int, err error) {
    	total = 0
    	for _, p := range purchase {
    		found := false
    		for _, product := range products {
    			if (p.OrderProduct != nil && product.ProductCode == p.OrderProduct.ProductCode) ||
    				product.ID == p.OrderProductID {
    				total += product.Price * p.Amount
    				found = true
    				break
    			}
    		}
    
    		if !found {
    
    meskio's avatar
    meskio committed
    			log.Printf("Order purchase product %d not in order: %v", p.OrderProductID, products)
    
    meskio's avatar
    meskio committed
    			err = ErrorInvalidRequest
    		}
    	}
    	return total, err
    }
    
    func updateOrderPurchase(tx *gorm.DB, memberNum int, transaction *Transaction, total int, purchase []OrderPurchase) error {
    
    meskio's avatar
    meskio committed
    	totalDiff := -(transaction.Total + total)
    	transaction.Total = -total
    
    
    meskio's avatar
    meskio committed
    	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 {
    
    meskio's avatar
    meskio committed
    				transaction.OrderPurchase[i].Amount = new_purchase.Amount
    
    meskio's avatar
    meskio committed
    				updatePurchases = append(updatePurchases, transaction.OrderPurchase[i])
    				found = true
    
    meskio's avatar
    meskio committed
    				break
    			}
    		}
    
    meskio's avatar
    meskio committed
    
    		if !found {
    			newPurchases = append(newPurchases, OrderPurchase{
    				Amount:         new_purchase.Amount,
    				TransactionID:  transaction.ID,
    				OrderProductID: new_purchase.OrderProductID,
    			})
    		}
    
    meskio's avatar
    meskio committed
    	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 {
    
    meskio's avatar
    meskio committed
    		err := updateMemberBalance(tx, memberNum, totalDiff)
    		if err != nil {
    			return err
    		}
    
    meskio's avatar
    meskio committed
    		for _, p := range delPurchases {
    			err = tx.Delete(&p).Error
    			if err != nil {
    				return err
    			}
    		}
    		for _, p := range updatePurchases {
    
    meskio's avatar
    meskio committed
    			err = tx.Save(&p).Error
    			if err != nil {
    				return err
    			}
    		}
    
    meskio's avatar
    meskio committed
    		for _, p := range newPurchases {
    			err = tx.Create(&p).Error
    			if err != nil {
    				return err
    			}
    		}
    
    meskio's avatar
    meskio committed
    		return tx.Save(&transaction).Error
    	})
    }
    
    
    meskio's avatar
    meskio committed
    func (d *DB) DeactivateOrders() []Order {
    	var orders []Order
    
    	now := time.Now().UTC()
    
    meskio's avatar
    meskio committed
    	err := d.db.Where("active = ? AND deadline < ?", true, now).
    
    meskio's avatar
    meskio committed
    		Preload("Member").
    
    		Preload("Transactions.OrderPurchase.OrderProduct.Product").
    
    meskio's avatar
    meskio committed
    		Preload("Transactions.Member").
    		Find(&orders).Error
    	if err != nil {
    		log.Println("Error refunding orders:", err)
    		return []Order{}
    	}
    
    	var deactivatedOrders []Order
    	for _, order := range orders {
    		total := 0
    		for _, transaction := range order.Transactions {
    			for _, purchase := range transaction.OrderPurchase {
    
    				total += purchase.OrderProduct.Price * purchase.Amount
    
    meskio's avatar
    meskio committed
    			}
    		}
    
    		transaction := Transaction{
    			MemberNum: order.MemberNum,
    			Date:      time.Now(),
    			Type:      "refund",
    			Total:     total,
    		}
    		err = d.db.Transaction(func(tx *gorm.DB) error {
    			err := createTransaction(tx, &transaction)
    			if err != nil {
    				return err
    			}
    			return tx.Model(&Order{}).
    				Where("id = ?", order.ID).
    				Updates(map[string]interface{}{
    					"active":      false,
    					"transaction": transaction.ID}).
    				Error
    		})
    		if err != nil {
    			log.Printf("Can't create refund: %v\n%v", err, order)
    			continue
    		}
    
    		deactivatedOrders = append(deactivatedOrders, order)
    		log.Println("Refund order", order.Name, total)
    	}
    	return deactivatedOrders
    }