Dev / controllers /fee_controller.go
Mhamdans17
Fix GORM method chaining state sharing bug in GetFeeReport
f086228
Raw
History Blame
8.56 kB
package controllers
import (
"fmt"
"net/http"
"strconv"
"time"
"service-warungpos-go/config"
"service-warungpos-go/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type FeeSummaryData struct {
TotalTransactions int64 `json:"total_transactions"`
TotalRevenue int `json:"total_revenue"`
TotalFee int `json:"total_fee"`
TotalCash int `json:"total_cash"`
TotalMidtrans int `json:"total_midtrans"`
FeeCash int `json:"fee_cash"`
FeeMidtrans int `json:"fee_midtrans"`
}
type CompanyFeeRow struct {
CompanyID uint `json:"company_id"`
CompanyName string `json:"company_name"`
FeePercentage float64 `json:"fee_percentage"`
TotalTransactions int64 `json:"total_transactions"`
TotalRevenue int `json:"total_revenue"`
TotalFee int `json:"total_fee"`
TotalCash int `json:"total_cash"`
TotalMidtrans int `json:"total_midtrans"`
}
type BranchFeeRow struct {
CompanyID uint `json:"company_id"`
CompanyName string `json:"company_name"`
BranchID *uint `json:"branch_id"`
BranchName string `json:"branch_name"`
TotalTransactions int64 `json:"total_transactions"`
TotalRevenue int `json:"total_revenue"`
TotalFee int `json:"total_fee"`
TotalCash int `json:"total_cash"`
TotalMidtrans int `json:"total_midtrans"`
}
type DailyFeeRow struct {
Day time.Time `json:"day"`
CompanyID uint `json:"company_id"`
CompanyName string `json:"company_name"`
FeePercentage float64 `json:"fee_percentage"`
TotalTransactions int64 `json:"total_transactions"`
TotalRevenue int `json:"total_revenue"`
TotalFee int `json:"total_fee"`
}
func GetFeeReport(c *gin.Context) {
dateFrom := c.Query("date_from")
dateTo := c.Query("date_to")
companyIDStr := c.Query("company_id")
today := time.Now()
var dFrom, dTo time.Time
var err error
if dateFrom != "" {
dFrom, err = time.Parse("2006-01-02", dateFrom)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "Format date_from salah, gunakan YYYY-MM-DD"})
return
}
} else {
// Default 30 hari lalu
dFrom = today.AddDate(0, 0, -29)
}
if dateTo != "" {
dTo, err = time.Parse("2006-01-02", dateTo)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "Format date_to salah, gunakan YYYY-MM-DD"})
return
}
} else {
dTo = today
}
loc, _ := time.LoadLocation("Asia/Jakarta")
if loc == nil {
loc = time.Local
}
startDt := time.Date(dFrom.Year(), dFrom.Month(), dFrom.Day(), 0, 0, 0, 0, loc)
endDt := time.Date(dTo.Year(), dTo.Month(), dTo.Day(), 23, 59, 59, 0, loc)
// Helper local untuk membikin query fresh guna menghindari GORM method chaining state sharing bug (table name specified more than once)
getFreshFeeQuery := func() *gorm.DB {
q := config.DB.Table("orders").
Where("orders.payment_status = ? AND orders.created_at >= ? AND orders.created_at <= ?", "paid", startDt, endDt)
if companyIDStr != "" {
if cID, err := strconv.Atoi(companyIDStr); err == nil {
q = q.Where("orders.company_id = ?", cID)
}
}
return q
}
// 1. Summary keseluruhan
var summary FeeSummaryData
getFreshFeeQuery().Select(`
count(orders.id) as total_transactions,
coalesce(sum(orders.total_amount), 0) as total_revenue,
coalesce(sum(orders.platform_fee), 0) as total_fee,
coalesce(sum(case when orders.payment_method = 'cash' then orders.total_amount else 0 end), 0) as total_cash,
coalesce(sum(case when orders.payment_method = 'midtrans' then orders.total_amount else 0 end), 0) as total_midtrans,
coalesce(sum(case when orders.payment_method = 'cash' then orders.platform_fee else 0 end), 0) as fee_cash,
coalesce(sum(case when orders.payment_method = 'midtrans' then orders.platform_fee else 0 end), 0) as fee_midtrans
`).Scan(&summary)
// 2. Summary per perusahaan
var perCompany []CompanyFeeRow
getFreshFeeQuery().Select(`
orders.company_id,
companies.name as company_name,
companies.fee_percentage,
count(orders.id) as total_transactions,
coalesce(sum(orders.total_amount), 0) as total_revenue,
coalesce(sum(orders.platform_fee), 0) as total_fee,
coalesce(sum(case when orders.payment_method = 'cash' then orders.total_amount else 0 end), 0) as total_cash,
coalesce(sum(case when orders.payment_method = 'midtrans' then orders.total_amount else 0 end), 0) as total_midtrans
`).
Joins("join companies on companies.id = orders.company_id").
Group("orders.company_id, companies.name, companies.fee_percentage").
Order("total_fee desc").
Scan(&perCompany)
// 3. Summary per cabang
var perBranch []BranchFeeRow
getFreshFeeQuery().Select(`
orders.company_id,
companies.name as company_name,
orders.branch_id,
coalesce(branches.name, 'Tanpa Cabang') as branch_name,
count(orders.id) as total_transactions,
coalesce(sum(orders.total_amount), 0) as total_revenue,
coalesce(sum(orders.platform_fee), 0) as total_fee,
coalesce(sum(case when orders.payment_method = 'cash' then orders.total_amount else 0 end), 0) as total_cash,
coalesce(sum(case when orders.payment_method = 'midtrans' then orders.total_amount else 0 end), 0) as total_midtrans
`).
Joins("join companies on companies.id = orders.company_id").
Joins("left join branches on branches.id = orders.branch_id").
Group("orders.company_id, companies.name, orders.branch_id, branches.name").
Order("orders.company_id, total_revenue desc").
Scan(&perBranch)
// 4. Daily breakdown
var dailyBreakdown []DailyFeeRow
getFreshFeeQuery().Select(`
DATE(orders.created_at) as day,
orders.company_id,
companies.name as company_name,
companies.fee_percentage,
count(orders.id) as total_transactions,
coalesce(sum(orders.total_amount), 0) as total_revenue,
coalesce(sum(orders.platform_fee), 0) as total_fee
`).
Joins("join companies on companies.id = orders.company_id").
Group("DATE(orders.created_at), orders.company_id, companies.name, companies.fee_percentage").
Order("day desc").
Scan(&dailyBreakdown)
// Buat daily_breakdown response yang clean format tanggalnya
type DailyBreakdownResp struct {
Date string `json:"date"`
CompanyID uint `json:"company_id"`
CompanyName string `json:"company_name"`
FeePercentage float64 `json:"fee_percentage"`
TotalTransactions int64 `json:"total_transactions"`
TotalRevenue int `json:"total_revenue"`
TotalFee int `json:"total_fee"`
}
var dailyResp []DailyBreakdownResp
for _, row := range dailyBreakdown {
dailyResp = append(dailyResp, DailyBreakdownResp{
Date: row.Day.Format("2006-01-02"),
CompanyID: row.CompanyID,
CompanyName: row.CompanyName,
FeePercentage: row.FeePercentage,
TotalTransactions: row.TotalTransactions,
TotalRevenue: row.TotalRevenue,
TotalFee: row.TotalFee,
})
}
c.JSON(http.StatusOK, gin.H{
"period": gin.H{
"date_from": dFrom.Format("2006-01-02"),
"date_to": dTo.Format("2006-01-02"),
},
"summary": summary,
"per_company": perCompany,
"per_branch": perBranch,
"daily_breakdown": dailyResp,
})
}
func SetCompanyFee(c *gin.Context) {
companyIDStr := c.Param("company_id")
companyID, err := strconv.Atoi(companyIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "ID perusahaan tidak valid"})
return
}
feePctStr := c.Query("fee_percentage")
if feePctStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"detail": "Query parameter fee_percentage wajib diisi"})
return
}
feePct, err := strconv.ParseFloat(feePctStr, 64)
if err != nil || feePct < 0 || feePct > 100 {
c.JSON(http.StatusBadRequest, gin.H{"detail": "Fee percentage harus berupa angka antara 0 dan 100"})
return
}
var company models.Company
if err := config.DB.First(&company, companyID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "Perusahaan tidak ditemukan"})
return
}
company.FeePercentage = feePct
if err := config.DB.Save(&company).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyimpan fee perusahaan"})
return
}
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Fee perusahaan '%s' berhasil diatur ke %.2f%%", company.Name, company.FeePercentage),
"company_id": company.ID,
"company_name": company.Name,
"fee_percentage": company.FeePercentage,
})
}