| package controllers |
|
|
| import ( |
| "encoding/json" |
| "fmt" |
| "io" |
| "log" |
| "math/rand" |
| "net/http" |
| "strconv" |
| "time" |
|
|
| "service-warungpos-go/config" |
| "service-warungpos-go/models" |
| "service-warungpos-go/services" |
|
|
| "github.com/gin-gonic/gin" |
| "gorm.io/gorm" |
| ) |
|
|
| |
| type OrderItemCreate struct { |
| ProductID uint `json:"product_id" binding:"required"` |
| Quantity int `json:"quantity" binding:"required,gt=0"` |
| } |
|
|
| type OrderCreateRequest struct { |
| BranchID *uint `json:"branch_id"` |
| Items []OrderItemCreate `json:"items" binding:"required,dive"` |
| } |
|
|
| type OrderItemResponse struct { |
| ID uint `json:"id"` |
| ProductID uint `json:"product_id"` |
| ProductName string `json:"product_name"` |
| Quantity int `json:"quantity"` |
| UnitPrice int `json:"unit_price"` |
| Subtotal int `json:"subtotal"` |
| } |
|
|
| type OrderResponse struct { |
| ID uint `json:"id"` |
| OrderNumber string `json:"order_number"` |
| BranchID *uint `json:"branch_id"` |
| BranchName string `json:"branch_name"` |
| TotalAmount int `json:"total_amount"` |
| PaidAmount int `json:"paid_amount"` |
| ChangeAmount int `json:"change_amount"` |
| PaymentMethod string `json:"payment_method"` |
| PaymentStatus string `json:"payment_status"` |
| MidtransOrderID string `json:"midtrans_order_id"` |
| MidtransToken string `json:"midtrans_token"` |
| Items []OrderItemResponse `json:"items"` |
| CreatedAt time.Time `json:"created_at"` |
| } |
|
|
| type CashPaymentRequest struct { |
| OrderID uint `json:"order_id" binding:"required"` |
| PaidAmount int `json:"paid_amount" binding:"required,gt=0"` |
| CustomerName string `json:"customer_name"` |
| CustomerPhone string `json:"customer_phone"` |
| } |
|
|
| type MidtransPaymentRequest struct { |
| OrderID uint `json:"order_id" binding:"required"` |
| CustomerName string `json:"customer_name"` |
| CustomerEmail string `json:"customer_email"` |
| CustomerPhone string `json:"customer_phone"` |
| } |
|
|
| type PaymentStatusResponse struct { |
| OrderID uint `json:"order_id"` |
| OrderNumber string `json:"order_number"` |
| PaymentStatus string `json:"payment_status"` |
| PaymentMethod string `json:"payment_method"` |
| TotalAmount int `json:"total_amount"` |
| PaidAmount int `json:"paid_amount"` |
| ChangeAmount int `json:"change_amount"` |
| } |
|
|
| func generateOrderNumber() string { |
| now := time.Now() |
| dateStr := now.Format("20060102") |
| |
| uniqueVal := rand.Intn(65536) |
| return fmt.Sprintf("WP-%s-%04X", dateStr, uniqueVal) |
| } |
|
|
| func toOrderResponse(order *models.Order) OrderResponse { |
| var itemsResp []OrderItemResponse |
| for _, item := range order.Items { |
| pName := "Produk Terhapus" |
| if item.Product != nil { |
| pName = item.Product.Name |
| } |
| itemsResp = append(itemsResp, OrderItemResponse{ |
| ID: item.ID, |
| ProductID: item.ProductID, |
| ProductName: pName, |
| Quantity: item.Quantity, |
| UnitPrice: item.UnitPrice, |
| Subtotal: item.Subtotal, |
| }) |
| } |
|
|
| bName := "" |
| if order.Branch != nil { |
| bName = order.Branch.Name |
| } |
|
|
| return OrderResponse{ |
| ID: order.ID, |
| OrderNumber: order.OrderNumber, |
| BranchID: order.BranchID, |
| BranchName: bName, |
| TotalAmount: order.TotalAmount, |
| PaidAmount: order.PaidAmount, |
| ChangeAmount: order.ChangeAmount, |
| PaymentMethod: order.PaymentMethod, |
| PaymentStatus: order.PaymentStatus, |
| MidtransOrderID: order.MidtransOrderID, |
| MidtransToken: order.MidtransToken, |
| Items: itemsResp, |
| CreatedAt: order.CreatedAt, |
| } |
| } |
|
|
| |
|
|
| func CreateOrder(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| user := userVal.(*models.User) |
|
|
| var req OrderCreateRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| |
| var effectiveBranchID *uint |
| if user.Role == "owner" { |
| effectiveBranchID = req.BranchID |
| } else { |
| effectiveBranchID = user.BranchID |
| } |
|
|
| orderNumber := generateOrderNumber() |
|
|
| tx := config.DB.Begin() |
| defer func() { |
| if r := recover(); r != nil { |
| tx.Rollback() |
| } |
| }() |
|
|
| order := models.Order{ |
| CompanyID: *user.CompanyID, |
| BranchID: effectiveBranchID, |
| OrderNumber: orderNumber, |
| TotalAmount: 0, |
| PaymentStatus: "pending", |
| } |
|
|
| if err := tx.Create(&order).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyimpan order"}) |
| return |
| } |
|
|
| total := 0 |
| for _, item := range req.Items { |
| var product models.Product |
| if err := tx.Where("id = ? AND company_id = ?", item.ProductID, *user.CompanyID).First(&product).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusBadRequest, gin.H{"detail": fmt.Sprintf("Produk dengan ID %d tidak ditemukan", item.ProductID)}) |
| return |
| } |
|
|
| if !product.IsActive { |
| tx.Rollback() |
| c.JSON(http.StatusBadRequest, gin.H{"detail": fmt.Sprintf("Produk '%s' sudah tidak aktif", product.Name)}) |
| return |
| } |
|
|
| |
| if !product.IsUnlimitedStock { |
| if product.Stock < item.Quantity { |
| tx.Rollback() |
| c.JSON(http.StatusBadRequest, gin.H{"detail": fmt.Sprintf("Stok '%s' tidak cukup (sisa: %d)", product.Name, product.Stock)}) |
| return |
| } |
| product.Stock -= item.Quantity |
| if err := tx.Save(&product).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal memperbarui stok produk"}) |
| return |
| } |
| } |
|
|
| subtotal := product.Price * item.Quantity |
| orderItem := models.OrderItem{ |
| OrderID: order.ID, |
| ProductID: product.ID, |
| Quantity: item.Quantity, |
| UnitPrice: product.Price, |
| Subtotal: subtotal, |
| } |
| if err := tx.Create(&orderItem).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyimpan item order"}) |
| return |
| } |
|
|
| total += subtotal |
| } |
|
|
| |
| order.TotalAmount = total |
| if err := tx.Save(&order).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal meng-update total transaksi"}) |
| return |
| } |
|
|
| if err := tx.Commit().Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal memproses transaksi"}) |
| return |
| } |
|
|
| |
| config.DB.Preload("Branch").Preload("Items.Product").First(&order, order.ID) |
|
|
| c.JSON(http.StatusCreated, toOrderResponse(&order)) |
| } |
|
|
| func ListOrders(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| user := userVal.(*models.User) |
|
|
| skipStr := c.DefaultQuery("skip", "0") |
| limitStr := c.DefaultQuery("limit", "50") |
| dateFrom := c.Query("date_from") |
| dateTo := c.Query("date_to") |
| branchFilter := c.Query("branch_id") |
|
|
| skip, _ := strconv.Atoi(skipStr) |
| limit, _ := strconv.Atoi(limitStr) |
|
|
| query := config.DB.Select("id, company_id, branch_id, order_number, total_amount, paid_amount, change_amount, payment_method, payment_status, midtrans_order_id, midtrans_token, created_at"). |
| Preload("Branch", func(db *gorm.DB) *gorm.DB { |
| return db.Select("id, name") |
| }). |
| Preload("Items", func(db *gorm.DB) *gorm.DB { |
| return db.Select("id, order_id, product_id, quantity, unit_price, subtotal") |
| }). |
| Preload("Items.Product", func(db *gorm.DB) *gorm.DB { |
| return db.Select("id, name") |
| }). |
| Where("company_id = ?", *user.CompanyID). |
| Order("created_at desc") |
|
|
| if dateFrom != "" { |
| dtFrom, err := time.Parse("2006-01-02", dateFrom) |
| if err == nil { |
| |
| query = query.Where("created_at >= ?", dtFrom) |
| } |
| } |
|
|
| if dateTo != "" { |
| dtTo, err := time.Parse("2006-01-02", dateTo) |
| if err == nil { |
| |
| dtToEnd := dtTo.Add(23*time.Hour + 59*time.Minute + 59*time.Second) |
| query = query.Where("created_at <= ?", dtToEnd) |
| } |
| } |
|
|
| if branchFilter != "" { |
| if bID, err := strconv.Atoi(branchFilter); err == nil { |
| query = query.Where("branch_id = ?", bID) |
| } |
| } |
|
|
| var orders []models.Order |
| if err := query.Offset(skip).Limit(limit).Find(&orders).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengambil histori order"}) |
| return |
| } |
|
|
| var resp []OrderResponse |
| for _, o := range orders { |
| resp = append(resp, toOrderResponse(&o)) |
| } |
|
|
| c.JSON(http.StatusOK, resp) |
| } |
|
|
| func GetOrder(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| user := userVal.(*models.User) |
|
|
| orderIDStr := c.Param("order_id") |
| orderID, err := strconv.Atoi(orderIDStr) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "ID order tidak valid"}) |
| return |
| } |
|
|
| var order models.Order |
| if err := config.DB.Preload("Branch").Preload("Items.Product").Where("id = ? AND company_id = ?", orderID, *user.CompanyID).First(&order).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"detail": "Order tidak ditemukan"}) |
| return |
| } |
|
|
| c.JSON(http.StatusOK, toOrderResponse(&order)) |
| } |
|
|
| |
|
|
| func PayCash(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| user := userVal.(*models.User) |
|
|
| var req CashPaymentRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| tx := config.DB.Begin() |
| defer func() { |
| if r := recover(); r != nil { |
| tx.Rollback() |
| } |
| }() |
|
|
| var order models.Order |
| if err := tx.Preload("Items.Product").Where("id = ? AND company_id = ?", req.OrderID, *user.CompanyID).First(&order).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusNotFound, gin.H{"detail": "Order tidak ditemukan"}) |
| return |
| } |
|
|
| if order.PaymentStatus == "paid" { |
| tx.Rollback() |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Order sudah dibayar"}) |
| return |
| } |
|
|
| if req.PaidAmount < order.TotalAmount { |
| tx.Rollback() |
| c.JSON(http.StatusBadRequest, gin.H{ |
| "detail": fmt.Sprintf("Uang kurang! Total: %s, Dibayar: %s, Kurang: %s", |
| fmtRupiah(order.TotalAmount), |
| fmtRupiah(req.PaidAmount), |
| fmtRupiah(order.TotalAmount-req.PaidAmount), |
| ), |
| }) |
| return |
| } |
|
|
| |
| order.PaymentMethod = "cash" |
| order.PaymentStatus = "paid" |
| order.PaidAmount = req.PaidAmount |
| order.ChangeAmount = req.PaidAmount - order.TotalAmount |
|
|
| |
| var company models.Company |
| if err := tx.First(&company, *user.CompanyID).Error; err == nil { |
| order.PlatformFee = int(float64(order.TotalAmount) * company.FeePercentage / 100) |
| } |
|
|
| if err := tx.Save(&order).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyelesaikan pembayaran"}) |
| return |
| } |
|
|
| if err := tx.Commit().Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal memproses pembayaran"}) |
| return |
| } |
|
|
| |
| if req.CustomerPhone != "" { |
| go func() { |
| var waItems []services.WAItem |
| for _, item := range order.Items { |
| pName := "Item" |
| if item.Product != nil { |
| pName = item.Product.Name |
| } |
| waItems = append(waItems, services.WAItem{ |
| Name: pName, |
| Quantity: item.Quantity, |
| Price: item.UnitPrice, |
| }) |
| } |
|
|
| var branchName string |
| if order.BranchID != nil { |
| var branch models.Branch |
| if err := config.DB.First(&branch, *order.BranchID).Error; err == nil { |
| branchName = branch.Name |
| } |
| } |
|
|
| services.SendPaymentNotification( |
| req.CustomerPhone, |
| order.OrderNumber, |
| order.TotalAmount, |
| "cash", |
| req.CustomerName, |
| waItems, |
| order.ChangeAmount, |
| company.Name, |
| branchName, |
| ) |
| }() |
| } |
|
|
| c.JSON(http.StatusOK, PaymentStatusResponse{ |
| OrderID: order.ID, |
| OrderNumber: order.OrderNumber, |
| PaymentStatus: order.PaymentStatus, |
| PaymentMethod: order.PaymentMethod, |
| TotalAmount: order.TotalAmount, |
| PaidAmount: order.PaidAmount, |
| ChangeAmount: order.ChangeAmount, |
| }) |
| } |
|
|
| func PayTripay(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| user := userVal.(*models.User) |
|
|
| var req MidtransPaymentRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| var order models.Order |
| if err := config.DB.Where("id = ? AND company_id = ?", req.OrderID, *user.CompanyID).First(&order).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"detail": "Order tidak ditemukan"}) |
| return |
| } |
|
|
| if order.PaymentStatus == "paid" { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Order sudah dibayar"}) |
| return |
| } |
|
|
| |
| var orderItems []models.OrderItem |
| config.DB.Preload("Product").Where("order_id = ?", order.ID).Find(&orderItems) |
|
|
| var tripayItems []services.TripayItem |
| for _, item := range orderItems { |
| skuStr := "" |
| if item.Product != nil && item.Product.SKU != nil { |
| skuStr = *item.Product.SKU |
| } |
| pName := "Product" |
| if item.Product != nil { |
| pName = item.Product.Name |
| } |
| tripayItems = append(tripayItems, services.TripayItem{ |
| SKU: skuStr, |
| Name: pName, |
| Price: item.UnitPrice, |
| Quantity: item.Quantity, |
| }) |
| } |
|
|
| |
| tripayRes, err := services.CreateTripayTransaction( |
| order.OrderNumber, |
| order.TotalAmount, |
| "QRIS", |
| req.CustomerName, |
| req.CustomerEmail, |
| req.CustomerPhone, |
| tripayItems, |
| order.CompanyID, |
| ) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": fmt.Sprintf("Gagal membuat transaksi Tripay: %v", err)}) |
| return |
| } |
|
|
| |
| order.MidtransOrderID = tripayRes.Data.Reference |
| order.MidtransToken = tripayRes.Data.CheckoutURL |
| order.PaymentMethod = "tripay" |
| config.DB.Save(&order) |
|
|
| |
| c.JSON(http.StatusOK, gin.H{ |
| "order_id": order.ID, |
| "order_number": order.OrderNumber, |
| "snap_token": tripayRes.Data.Reference, |
| "redirect_url": tripayRes.Data.CheckoutURL, |
| "total_amount": order.TotalAmount, |
| }) |
| } |
|
|
| func TripayWebhook(c *gin.Context) { |
| |
| rawBody, err := io.ReadAll(c.Request.Body) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Gagal membaca request body"}) |
| return |
| } |
|
|
| |
| var notification map[string]interface{} |
| if err := json.Unmarshal(rawBody, ¬ification); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Request body harus berupa JSON yang valid"}) |
| return |
| } |
|
|
| |
| signature := c.GetHeader("X-Callback-Signature") |
| if signature == "" { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Header X-Callback-Signature tidak ditemukan"}) |
| return |
| } |
|
|
| log.Printf("[WEBHOOK] Tripay payload: %s", string(rawBody)) |
|
|
| |
| if !services.VerifyTripaySignature(rawBody, signature) { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Signature verifikasi gagal/tidak valid"}) |
| return |
| } |
|
|
| merchantRefVal, ok := notification["merchant_ref"] |
| if !ok || merchantRefVal == nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Payload webhook tidak valid: merchant_ref wajib ada"}) |
| return |
| } |
|
|
| merchantRefStr := merchantRefVal.(string) |
| tripayStatus, _ := notification["status"].(string) |
|
|
| log.Printf("[WEBHOOK] Tripay: merchant_ref=%s, status=%s", merchantRefStr, tripayStatus) |
|
|
| tx := config.DB.Begin() |
| defer func() { |
| if r := recover(); r != nil { |
| tx.Rollback() |
| } |
| }() |
|
|
| var order models.Order |
| if err := tx.Preload("Items.Product").Where("order_number = ?", merchantRefStr).First(&order).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusNotFound, gin.H{"detail": "Order tidak ditemukan"}) |
| return |
| } |
|
|
| oldStatus := order.PaymentStatus |
|
|
| |
| switch tripayStatus { |
| case "PAID": |
| order.PaymentStatus = "paid" |
| order.PaidAmount = order.TotalAmount |
| case "EXPIRED", "FAILED": |
| order.PaymentStatus = "failed" |
| if oldStatus != "failed" { |
| restoreStock(tx, &order) |
| } |
| } |
|
|
| |
| if order.PaymentStatus == "paid" && oldStatus != "paid" { |
| var company models.Company |
| if err := tx.First(&company, order.CompanyID).Error; err == nil { |
| order.PlatformFee = int(float64(order.TotalAmount) * company.FeePercentage / 100) |
| } |
| } |
|
|
| if err := tx.Save(&order).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal memproses status webhook"}) |
| return |
| } |
|
|
| if err := tx.Commit().Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyelesaikan commit webhook"}) |
| return |
| } |
|
|
| log.Printf("[WEBHOOK] Order %s di-update via Tripay: %s -> %s", order.OrderNumber, oldStatus, order.PaymentStatus) |
|
|
| |
| if order.PaymentStatus == "paid" && oldStatus != "paid" { |
| go func() { |
| custPhone, _ := notification["customer_phone"].(string) |
| custName, _ := notification["customer_name"].(string) |
| if custName == "" { |
| custName = "Customer" |
| } |
|
|
| var waItems []services.WAItem |
| for _, item := range order.Items { |
| pName := "Item" |
| if item.Product != nil { |
| pName = item.Product.Name |
| } |
| waItems = append(waItems, services.WAItem{ |
| Name: pName, |
| Quantity: item.Quantity, |
| Price: item.UnitPrice, |
| }) |
| } |
|
|
| var company models.Company |
| config.DB.First(&company, order.CompanyID) |
|
|
| var branchName string |
| if order.BranchID != nil { |
| var branch models.Branch |
| if err := config.DB.First(&branch, *order.BranchID).Error; err == nil { |
| branchName = branch.Name |
| } |
| } |
|
|
| if custPhone != "" { |
| services.SendPaymentNotification( |
| custPhone, |
| order.OrderNumber, |
| order.TotalAmount, |
| "tripay", |
| custName, |
| waItems, |
| 0, |
| company.Name, |
| branchName, |
| ) |
| } |
| }() |
| } |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "success": true, |
| "order_number": order.OrderNumber, |
| }) |
| } |
|
|
| func GetPaymentStatus(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| user := userVal.(*models.User) |
|
|
| orderIDStr := c.Param("order_id") |
| orderID, err := strconv.Atoi(orderIDStr) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "ID order tidak valid"}) |
| return |
| } |
|
|
| var order models.Order |
| if err := config.DB.Where("id = ? AND company_id = ?", orderID, *user.CompanyID).First(&order).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"detail": "Order tidak ditemukan"}) |
| return |
| } |
|
|
| c.JSON(http.StatusOK, PaymentStatusResponse{ |
| OrderID: order.ID, |
| OrderNumber: order.OrderNumber, |
| PaymentStatus: order.PaymentStatus, |
| PaymentMethod: order.PaymentMethod, |
| TotalAmount: order.TotalAmount, |
| PaidAmount: order.PaidAmount, |
| ChangeAmount: order.ChangeAmount, |
| }) |
| } |
|
|
| func CancelMidtransOrder(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| user := userVal.(*models.User) |
|
|
| orderIDStr := c.Param("order_id") |
| orderID, err := strconv.Atoi(orderIDStr) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "ID order tidak valid"}) |
| return |
| } |
|
|
| tx := config.DB.Begin() |
| defer func() { |
| if r := recover(); r != nil { |
| tx.Rollback() |
| } |
| }() |
|
|
| var order models.Order |
| if err := tx.Preload("Items.Product").Where("id = ? AND company_id = ?", orderID, *user.CompanyID).First(&order).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusNotFound, gin.H{"detail": "Order tidak ditemukan"}) |
| return |
| } |
|
|
| if order.PaymentStatus == "paid" { |
| tx.Rollback() |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Order sudah dibayar"}) |
| return |
| } |
|
|
| if order.PaymentStatus == "cancelled" { |
| tx.Rollback() |
| c.JSON(http.StatusOK, gin.H{ |
| "status": "cancelled", |
| "order_id": order.ID, |
| "order_number": order.OrderNumber, |
| "payment_status": order.PaymentStatus, |
| }) |
| return |
| } |
|
|
| oldStatus := order.PaymentStatus |
| order.PaymentStatus = "cancelled" |
|
|
| if oldStatus != "cancelled" { |
| restoreStock(tx, &order) |
| } |
|
|
| if err := tx.Save(&order).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal membatalkan order"}) |
| return |
| } |
|
|
| if err := tx.Commit().Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyelesaikan proses pembatalan"}) |
| return |
| } |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "status": "cancelled", |
| "order_id": order.ID, |
| "order_number": order.OrderNumber, |
| "payment_status": order.PaymentStatus, |
| }) |
| } |
|
|
| |
| func restoreStock(tx *gorm.DB, order *models.Order) { |
| |
| for _, item := range order.Items { |
| var product models.Product |
| if err := tx.First(&product, item.ProductID).Error; err == nil { |
| if !product.IsUnlimitedStock { |
| product.Stock += item.Quantity |
| tx.Save(&product) |
| log.Printf("[RESTORE] Stok '%s' dikembalikan +%d (sekarang: %d)", product.Name, item.Quantity, product.Stock) |
| } |
| } |
| } |
| } |
|
|