Newer
Older
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"`
ProxyNum int `json:"-" gorm:"column:proxy"`
Proxy *Member `json:"proxy,omitempty" gorm:"foreignKey:ProxyNum;references:Num"`
Purchase []Purchase `json:"purchase,omitempty"`
Topup *Topup `json:"topup,omitempty"`
OrderPurchase []OrderPurchase `json:"order_purchase,omitempty" gorm:"foreignKey:TransactionID;constraint:OnDelete:CASCADE"`
Order *Order `json:"order,omitempty" gorm:"constraint:OnDelete:CASCADE"`
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 {
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(num int, query map[string][]string) (transactions []Transaction, err error) {
for k, v := range query {
switch k {
case "start-date":
var date time.Time
date, err = time.Parse(dateLayout, v[0])
if err != nil {
return
}
tx = tx.Where("date >= ?", date)
case "end-date":
var date time.Time
date, err = time.Parse(dateLayout, v[0])
if err != nil {
return
}
tx = tx.Where("date <= ?", date)
case "member":
case "product":
var ids []interface{}
where := make([]string, len(v))
for i := range v {
var id int
id, err = strconv.Atoi(v[i])
if err != nil {
return
}
ids = append(ids, id, id)
where[i] = "purchases.product = ? OR order_products.product_code = ?"
}
tx = tx.Joins("left join purchases on purchases.`transaction` = transactions.id").
Joins("left join order_purchases on order_purchases.transaction_id = transactions.id").
Joins("left join order_products on order_products.id = order_purchases.order_product_id").
Where(strings.Join(where, " OR "), ids...)
default:
log.Printf("Unexpected transaction query: %s %v", k, v)
}
}
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,
},
Type: "topup",
Total: amount,
}
err = createTransaction(d.db, &transaction)
return
}
func (d *DB) AddPurchase(adminNum int, memberNum int, purchase []Purchase) (transaction Transaction, err error) {
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,
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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").
Preload(clause.Associations)
}
func createTransaction(db *gorm.DB, transaction *Transaction) error {
return db.Transaction(func(tx *gorm.DB) error {
err := updateMemberBalance(tx, transaction.MemberNum, transaction.Total)
if err != nil {
return err
}
return tx.Create(&transaction).Error
})
}
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
}