Skip to content
Snippets Groups Projects
transaction.go 4.59 KiB
Newer Older
  • Learn to ignore specific revisions
  • meskio's avatar
    meskio committed
    package db
    
    import (
    	"errors"
    	"fmt"
    	"log"
    	"time"
    
    	"gorm.io/gorm"
    	"gorm.io/gorm/clause"
    )
    
    type Transaction struct {
    	gorm.Model
    	MemberNum int       `json:"-" gorm:"column:member"`
    	Member    *Member   `json:"member,omitempty" gorm:"foreignKey:MemberNum;references:Num"`
    	Date      time.Time `json:"date"`
    	Total     int       `json:"total"`
    	Type      string    `json:"type"`
    
    meskio's avatar
    meskio committed
    	ProxyNum  int       `json:"-" gorm:"column:proxy"`
    	Proxy     *Member   `json:"proxy,omitempty" gorm:"foreignKey:ProxyNum;references:Num"`
    
    meskio's avatar
    meskio committed
    
    	Purchase      []Purchase      `json:"purchase,omitempty"`
    	Topup         *Topup          `json:"topup,omitempty"`
    
    meskio's avatar
    meskio committed
    	OrderPurchase []OrderPurchase `json:"order_purchase,omitempty" gorm:"foreignKey:TransactionID;constraint:OnDelete:CASCADE"`
    	Order         *Order          `json:"order,omitempty" gorm:"constraint:OnDelete:CASCADE"`
    
    meskio's avatar
    meskio committed
    	OrderID       *uint           `json:"-"`
    	Refund        *Order          `json:"refund,omitempty" gorm:"foreignKey:TransactionID"`
    }
    
    type Topup struct {
    	gorm.Model    `json:"-"`
    	TransactionID uint   `json:"-" gorm:"column:transaction"`
    	Comment       string `json:"comment"`
    }
    
    type Purchase struct {
    	gorm.Model    `json:"-"`
    	TransactionID uint    `json:"-" gorm:"column:transaction"`
    	ProductCode   int     `json:"code" gorm:"column:product"`
    	Product       Product `json:"product" gorm:"foreignKey:ProductCode;references:Code"`
    	Price         int     `json:"price"`
    	Amount        int     `json:"amount"`
    }
    
    func (d *DB) ListTransactions() (transactions []Transaction, err error) {
    	err = d.transactionQuery().
    		Order("date desc").
    		Find(&transactions).Error
    	return
    }
    
    func (d *DB) TransactionByMember(num int) (transactions []Transaction, err error) {
    	err = d.transactionQuery().
    		Where("member = ?", num).
    		Order("date desc").
    		Find(&transactions).Error
    	return
    }
    
    func (d *DB) GetTransaction(id int) (transaction Transaction, err error) {
    	err = d.transactionQuery().
    		First(&transaction, id).Error
    	if errors.Is(err, gorm.ErrRecordNotFound) {
    		err = ErrorNotFound
    	}
    	return
    }
    
    func (d *DB) AddTopup(adminNum int, memberNum int, amount int, comment string) (transaction Transaction, err error) {
    	transaction = Transaction{
    		MemberNum: memberNum,
    
    meskio's avatar
    meskio committed
    		ProxyNum:  adminNum,
    
    meskio's avatar
    meskio committed
    		Date:      time.Now(),
    		Topup: &Topup{
    
    meskio's avatar
    meskio committed
    			Comment: comment,
    
    meskio's avatar
    meskio committed
    		},
    		Type:  "topup",
    		Total: amount,
    	}
    	err = createTransaction(d.db, &transaction)
    	return
    }
    
    
    meskio's avatar
    meskio committed
    func (d *DB) AddPurchase(adminNum int, memberNum int, purchase []Purchase) (transaction Transaction, err error) {
    
    meskio's avatar
    meskio committed
    	total := 0
    	for i, p := range purchase {
    		var product Product
    		err = d.db.Where("code = ?", p.ProductCode).First(&product).Error
    		if err != nil {
    			log.Printf("Can't get product %d: %v", p.ProductCode, err)
    			err = ErrorNotFound
    			return
    		}
    
    		total += product.Price * p.Amount
    		purchase[i].Price = product.Price
    	}
    	if total == 0 {
    		log.Printf("Empty purchase (%d)", memberNum)
    		err = ErrorInvalidRequest
    		return
    	}
    
    	transaction = Transaction{
    		MemberNum: memberNum,
    
    meskio's avatar
    meskio committed
    		ProxyNum:  adminNum,
    
    meskio's avatar
    meskio committed
    		Date:      time.Now(),
    		Purchase:  purchase,
    		Type:      "purchase",
    		Total:     -total,
    	}
    	err = d.db.Transaction(func(tx *gorm.DB) error {
    		err := createTransaction(tx, &transaction)
    		if err != nil {
    			return err
    		}
    
    		for _, p := range purchase {
    			err = tx.Model(&Product{}).
    				Where("code = ?", p.ProductCode).
    				Update("stock", gorm.Expr("stock - ?", p.Amount)).Error
    			if err != nil {
    				return fmt.Errorf("Can't update product stock %d-%d: %v", p.ProductCode, p.Amount, err)
    			}
    		}
    		return nil
    	})
    	return
    }
    
    func (d *DB) transactionQuery() *gorm.DB {
    	return d.db.Preload("Purchase.Product").
    		Preload("Order.Products").
    
    		Preload("OrderPurchase.OrderProduct.Product").
    
    meskio's avatar
    meskio committed
    		Preload(clause.Associations)
    }
    
    func createTransaction(db *gorm.DB, transaction *Transaction) error {
    	return db.Transaction(func(tx *gorm.DB) error {
    
    meskio's avatar
    meskio committed
    		err := updateMemberBalance(tx, transaction.MemberNum, transaction.Total)
    
    meskio's avatar
    meskio committed
    		if err != nil {
    			return err
    		}
    		return tx.Create(&transaction).Error
    	})
    }
    
    meskio's avatar
    meskio committed
    
    func updateMemberBalance(tx *gorm.DB, memberNum int, amount int) error {
    	var member Member
    	err := tx.Where("num = ?", memberNum).Find(&member).Error
    	if err != nil {
    		log.Printf("Can't find member for transaction %d: %v", memberNum, err)
    		return ErrorNotFound
    	}
    	if member.Balance < -amount {
    		log.Printf("Member %d don't have enough money (%d-%d)", member.Num, member.Balance, amount)
    		return ErrorInvalidRequest
    	}
    	err = tx.Model(&Member{}).
    		Where("num = ?", memberNum).
    		Update("balance", gorm.Expr("balance + ?", amount)).Error
    	if err != nil {
    		log.Printf("Can't update update member balance %d-%d: %v", member.Num, amount, err)
    	}
    	return err
    }