package controllers import ( "fmt" "net/http" "strconv" "strings" "time" "service-warungpos-go/config" "service-warungpos-go/models" "github.com/gin-gonic/gin" ) type TopProductRow struct { ProductName string `json:"product_name"` TotalQuantity int `json:"total_quantity"` TotalRevenue int `json:"total_revenue"` } type SummaryTopProduct struct { Name string `json:"name"` TotalSold int `json:"total_sold"` TotalRevenue int `json:"total_revenue"` } type ReportOrderRow struct { OrderNumber string `json:"order_number"` TotalAmount int `json:"total_amount"` SubtotalAmount int `json:"subtotal_amount"` PaymentMethod string `json:"payment_method"` CreatedAt time.Time `json:"created_at"` Items string `json:"items"` } func getDayRange(targetDate time.Time) (time.Time, time.Time) { // Buat range awal dan akhir hari di timezone lokal/WIB (Asia/Jakarta) loc, _ := time.LoadLocation("Asia/Jakarta") if loc == nil { loc = time.Local } start := time.Date(targetDate.Year(), targetDate.Month(), targetDate.Day(), 0, 0, 0, 0, loc) end := start.Add(24 * time.Hour) return start, end } func fmtRupiah(amount int) string { // Helper formatting rupiah untuk internal log atau respons str := fmt.Sprintf("%d", amount) var result []string length := len(str) for i := length; i > 0; i -= 3 { start := i - 3 if start < 0 { start = 0 } result = append([]string{str[start:i]}, result...) } return "Rp " + strings.Join(result, ".") } // Helper strings.Join untuk formatting di atas func DailyReport(c *gin.Context) { userVal, _ := c.Get("user") user := userVal.(*models.User) dateQuery := c.Query("date") // YYYY-MM-DD branchQuery := c.Query("branch_id") var targetDate time.Time var err error if dateQuery != "" { targetDate, err = time.Parse("2006-01-02", dateQuery) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": "Format tanggal salah, gunakan YYYY-MM-DD"}) return } } else { // Default: Hari ini targetDate = time.Now() } startDt, endDt := getDayRange(targetDate) query := config.DB.Preload("Items.Product"). Where("company_id = ? AND created_at >= ? AND created_at < ? AND payment_status = ?", *user.CompanyID, startDt, endDt, "paid") if branchQuery != "" { if bID, err := strconv.Atoi(branchQuery); err == nil { query = query.Where("branch_id = ?", bID) } } var orders []models.Order if err := query.Find(&orders).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengambil data laporan harian"}) return } totalRevenue := 0 totalCash := 0 totalMidtrans := 0 totalPlatformFee := 0 var orderRows []ReportOrderRow for _, o := range orders { totalRevenue += o.TotalAmount if o.PaymentMethod == "cash" { totalCash += o.TotalAmount } else if o.PaymentMethod == "midtrans" || o.PaymentMethod == "tripay" { totalMidtrans += o.TotalAmount } totalPlatformFee += o.PlatformFee itemNames := []string{} for _, item := range o.Items { name := "Item" if item.Product != nil { name = item.Product.Name } itemNames = append(itemNames, fmt.Sprintf("%s (x%d)", name, item.Quantity)) } itemsStr := strings.Join(itemNames, ", ") orderRows = append(orderRows, ReportOrderRow{ OrderNumber: o.OrderNumber, TotalAmount: o.TotalAmount, SubtotalAmount: o.SubtotalAmount, PaymentMethod: o.PaymentMethod, CreatedAt: o.CreatedAt, Items: itemsStr, }) } // Query Top 5 produk terlaris hari ini var topProducts []TopProductRow topQuery := config.DB.Table("products"). Select("products.name as product_name, sum(order_items.quantity) as total_quantity, sum(order_items.subtotal) as total_revenue"). Joins("join order_items on products.id = order_items.product_id"). Joins("join orders on order_items.order_id = orders.id"). Where("orders.company_id = ? AND orders.payment_status = ? AND orders.created_at >= ? AND orders.created_at < ?", *user.CompanyID, "paid", startDt, endDt) if branchQuery != "" { if bID, err := strconv.Atoi(branchQuery); err == nil { topQuery = topQuery.Where("orders.branch_id = ?", bID) } } topQuery.Group("products.name"). Order("total_quantity desc"). Limit(5). Scan(&topProducts) c.JSON(http.StatusOK, gin.H{ "date": targetDate.Format("2006-01-02"), "total_transactions": len(orders), "total_revenue": totalRevenue, "total_cash": totalCash, "total_midtrans": totalMidtrans, "total_platform_fee": totalPlatformFee, "top_products": topProducts, "orders": orderRows, }) } func SummaryReport(c *gin.Context) { userVal, _ := c.Get("user") user := userVal.(*models.User) branchQuery := c.Query("branch_id") // 1. Total Semua Transaksi Sukses totalOrders := int64(0) totalRevenue := int64(0) totalQuery := config.DB.Model(&models.Order{}). Where("company_id = ? AND payment_status = ?", *user.CompanyID, "paid") if branchQuery != "" { if bID, err := strconv.Atoi(branchQuery); err == nil { totalQuery = totalQuery.Where("branch_id = ?", bID) } } totalQuery.Count(&totalOrders) totalQuery.Select("coalesce(sum(total_amount), 0)").Row().Scan(&totalRevenue) // 2. Total Produk Aktif yang Tersedia var totalProducts int64 prodQuery := config.DB.Model(&models.Product{}). Where("company_id = ? AND is_active = ?", *user.CompanyID, true) if branchQuery != "" { if bID, err := strconv.Atoi(branchQuery); err == nil { // Subquery pengecualian produk tidak aktif di cabang ini subQuery := config.DB.Model(&models.BranchProduct{}). Select("product_id"). Where("branch_id = ? AND is_available = ?", bID, false) prodQuery = prodQuery.Where("id NOT IN (?)", subQuery) } } prodQuery.Count(&totalProducts) // 3. Top 5 Produk Terlaris All-Time var topProducts []SummaryTopProduct topQuery := config.DB.Table("products"). Select("products.name as name, sum(order_items.quantity) as total_sold, sum(order_items.subtotal) as total_revenue"). Joins("join order_items on products.id = order_items.product_id"). Joins("join orders on order_items.order_id = orders.id"). Where("orders.company_id = ? AND orders.payment_status = ?", *user.CompanyID, "paid") if branchQuery != "" { if bID, err := strconv.Atoi(branchQuery); err == nil { topQuery = topQuery.Where("orders.branch_id = ?", bID) } } topQuery.Group("products.name"). Order("total_sold desc"). Limit(5). Scan(&topProducts) // 4. Laporan Hari Ini todayOrders := int64(0) todayRevenue := int64(0) startToday, endToday := getDayRange(time.Now()) todayQuery := config.DB.Model(&models.Order{}). Where("company_id = ? AND payment_status = ? AND created_at >= ? AND created_at < ?", *user.CompanyID, "paid", startToday, endToday) if branchQuery != "" { if bID, err := strconv.Atoi(branchQuery); err == nil { todayQuery = todayQuery.Where("branch_id = ?", bID) } } todayQuery.Count(&todayOrders) todayQuery.Select("coalesce(sum(total_amount), 0)").Row().Scan(&todayRevenue) // 5. Laporan QRIS var qrisRevenue int64 var qrisFee int64 qrisQuery := config.DB.Model(&models.Order{}). Where("company_id = ? AND payment_status = ? AND payment_method != 'cash'", *user.CompanyID, "paid") if branchQuery != "" { if bID, err := strconv.Atoi(branchQuery); err == nil { qrisQuery = qrisQuery.Where("branch_id = ?", bID) } } qrisQuery.Select("coalesce(sum(total_amount), 0), coalesce(sum(platform_fee), 0)").Row().Scan(&qrisRevenue, &qrisFee) c.JSON(http.StatusOK, gin.H{ "all_time": gin.H{ "total_orders": totalOrders, "total_revenue": totalRevenue, }, "today": gin.H{ "total_orders": todayOrders, "total_revenue": todayRevenue, }, "qris_report": gin.H{ "total_transactions": qrisRevenue, "system_fee": qrisFee, "net_amount": qrisRevenue - qrisFee, }, "total_products": totalProducts, "top_products": topProducts, }) }