Files
yunzerwebsiteallinone/go/controllers/platform_commission.go
T
2026-09-15 12:46:29 +08:00

428 lines
13 KiB
Go

package controllers
import (
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
"server/services/payment"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// PlatformCommissionController 平台端推广佣金(台账 / 统计 / 规则维护)
type PlatformCommissionController struct {
beego.Controller
}
func (c *PlatformCommissionController) platformClaims() (*jwtutil.Claims, error) {
return paymentPlatformClaims(&c.Controller)
}
func (c *PlatformCommissionController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func (c *PlatformCommissionController) ok(data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
// commissionRuleDTO 规则出参。
// 比例值统一按「百分比」返回:库里存百分比×100(500 = 5%),接口返回 5,便于前端直接编辑。
func commissionRuleDTO(row *models.PlatformCommissionRule) map[string]interface{} {
dto := map[string]interface{}{
"id": row.ID, "rule_name": row.RuleName, "calc_type": row.CalcType,
"order_type": row.OrderType, "status": row.Status, "remark": row.Remark,
"create_user_id": row.CreateUserID, "create_user_name": row.CreateUserName,
"create_time": row.CreateTime, "update_time": row.UpdateTime,
"steps": []map[string]interface{}{},
}
if row.CalcType == models.CommissionCalcFixed {
dto["value"] = row.Value // 固定金额:分
} else {
dto["value"] = float64(row.Value) / 100 // 比例:5% -> 5
}
if row.CalcType == models.CommissionCalcTiered && row.TierJSON != nil && *row.TierJSON != "" {
var tiers []models.CommissionTier
if err := json.Unmarshal([]byte(*row.TierJSON), &tiers); err == nil {
steps := make([]map[string]interface{}, 0, len(tiers))
for _, t := range tiers {
var maxV interface{}
if t.Max != nil {
maxV = *t.Max
}
steps = append(steps, map[string]interface{}{
"min": t.Min, "max": maxV, "rate": float64(t.Rate) / 100,
})
}
dto["steps"] = steps
}
}
return dto
}
// commissionOrderDTO 佣金单出参
func commissionOrderDTO(row *models.PlatformCommissionOrder) map[string]interface{} {
return map[string]interface{}{
"id": row.ID, "commission_no": row.CommissionNo,
"pay_no": row.PayNo, "out_trade_no": row.OutTradeNo,
"rule_id": row.RuleID, "rule_name": row.RuleName, "calc_type": row.CalcType,
"promoter_id": row.PromoterID, "promoter_name": row.PromoterName, "promoter_type": row.PromoterType,
"base_amount": row.BaseAmount, "amount": row.Amount,
"status": row.Status, "pay_method": row.PayMethod, "pay_voucher": row.PayVoucher,
"paid_at": row.PaidAt, "paid_user_id": row.PaidUserID, "paid_user_name": row.PaidUserName,
"remark": row.Remark, "create_time": row.CreateTime, "update_time": row.UpdateTime,
}
}
// List GET /platform/commission/list 佣金台账
func (c *PlatformCommissionController) List() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := strconv.Atoi(c.GetString("page", "1"))
pageSize, _ := strconv.Atoi(c.GetString("pageSize", "10"))
rows, total, err := payment.ListCommissions(payment.CommissionListInput{
Promoter: strings.TrimSpace(c.GetString("promoter")),
Status: strings.TrimSpace(c.GetString("status")),
StartTime: strings.TrimSpace(c.GetString("start_time")),
EndTime: strings.TrimSpace(c.GetString("end_time")),
Page: page,
PageSize: pageSize,
})
if err != nil {
c.jsonErr(500, 500, "查询失败: "+err.Error())
return
}
if rows == nil {
rows = []models.PlatformCommissionOrder{}
}
list := make([]map[string]interface{}, 0, len(rows))
for i := range rows {
list = append(list, commissionOrderDTO(&rows[i]))
}
c.ok(map[string]interface{}{"list": list, "total": total, "page": page, "pageSize": pageSize})
}
// Stats GET /platform/commission/stats 台账统计
func (c *PlatformCommissionController) Stats() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
stats, err := payment.GetCommissionStats()
if err != nil {
c.jsonErr(500, 500, "统计失败: "+err.Error())
return
}
c.ok(stats)
}
// MarkPaid POST /platform/commission/:id/pay 标记已付(发放登记)
func (c *PlatformCommissionController) MarkPaid() {
claims, err := c.platformClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
body, _ := io.ReadAll(c.Ctx.Request.Body)
var p struct {
PayMethod string `json:"pay_method"`
PayVoucher string `json:"pay_voucher"`
Remark string `json:"remark"`
}
if err := json.Unmarshal(body, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if strings.TrimSpace(p.PayMethod) == "" {
c.jsonErr(400, 400, "请选择发放方式")
return
}
row, err := payment.MarkCommissionPaid(context.Background(), payment.MarkCommissionPaidInput{
ID: id, PayMethod: strings.TrimSpace(p.PayMethod), PayVoucher: strings.TrimSpace(p.PayVoucher),
Remark: strings.TrimSpace(p.Remark),
OperatorID: fmt.Sprintf("%d", claims.UserID), OperatorName: claims.Username,
})
if err != nil {
c.jsonErr(400, 400, err.Error())
return
}
c.ok(commissionOrderDTO(row))
}
/* ---------------- 佣金规则 ---------------- */
type commissionRulePayload struct {
RuleName string `json:"rule_name"`
CalcType string `json:"calc_type"`
Value *float64 `json:"value"`
Steps []struct {
Min *float64 `json:"min"`
Max *float64 `json:"max"`
Rate *float64 `json:"rate"`
} `json:"steps"`
OrderType string `json:"order_type"`
Status *int8 `json:"status"`
Remark string `json:"remark"`
}
// buildTierJSON 组装阶梯配置(金额分;rate 存百分比×100)
func buildTierJSON(steps []struct {
Min *float64 `json:"min"`
Max *float64 `json:"max"`
Rate *float64 `json:"rate"`
}) (string, error) {
if len(steps) == 0 {
return "", fmt.Errorf("阶梯规则至少需要一档")
}
tiers := make([]models.CommissionTier, 0, len(steps))
for i, s := range steps {
if s.Min == nil || s.Rate == nil {
return "", fmt.Errorf("第 %d 档的起始金额与比例必填", i+1)
}
tier := models.CommissionTier{
Min: int64(*s.Min + 0.5),
Rate: int64(*s.Rate*100 + 0.5),
}
if s.Max != nil && *s.Max > 0 {
mx := int64(*s.Max + 0.5)
tier.Max = &mx
if mx <= tier.Min {
return "", fmt.Errorf("第 %d 档结束金额需大于起始金额", i+1)
}
}
if tier.Rate <= 0 {
return "", fmt.Errorf("第 %d 档比例需大于 0", i+1)
}
tiers = append(tiers, tier)
}
b, err := json.Marshal(tiers)
return string(b), err
}
// RulesList GET /platform/commission/rules 规则列表
func (c *PlatformCommissionController) RulesList() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := strconv.Atoi(c.GetString("page", "1"))
pageSize, _ := strconv.Atoi(c.GetString("pageSize", "20"))
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 200 {
pageSize = 20
}
qs := models.Orm.QueryTable(new(models.PlatformCommissionRule)).Filter("delete_time__isnull", true)
if kw := strings.TrimSpace(c.GetString("keyword")); kw != "" {
qs = qs.Filter("rule_name__icontains", kw)
}
total, err := qs.Count()
if err != nil {
c.jsonErr(500, 500, "查询失败: "+err.Error())
return
}
var rows []models.PlatformCommissionRule
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
if err != nil && err != orm.ErrNoRows {
c.jsonErr(500, 500, "查询失败: "+err.Error())
return
}
if rows == nil {
rows = []models.PlatformCommissionRule{}
}
list := make([]map[string]interface{}, 0, len(rows))
for i := range rows {
list = append(list, commissionRuleDTO(&rows[i]))
}
c.ok(map[string]interface{}{"list": list, "total": total, "page": page, "pageSize": pageSize})
}
// RuleCreate POST /platform/commission/rules 新增规则
func (c *PlatformCommissionController) RuleCreate() {
claims, err := c.platformClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
body, _ := io.ReadAll(c.Ctx.Request.Body)
var p commissionRulePayload
if err := json.Unmarshal(body, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if strings.TrimSpace(p.RuleName) == "" {
c.jsonErr(400, 400, "规则名称不能为空")
return
}
row := &models.PlatformCommissionRule{
RuleName: strings.TrimSpace(p.RuleName),
CalcType: strings.TrimSpace(p.CalcType),
OrderType: strings.TrimSpace(p.OrderType),
Status: 1,
Remark: strings.TrimSpace(p.Remark),
CreateUserID: fmt.Sprintf("%d", claims.UserID),
CreateUserName: claims.Username,
}
if p.Status != nil {
row.Status = *p.Status
}
if err := applyCommissionRuleValues(row, &p); err != nil {
c.jsonErr(400, 400, err.Error())
return
}
if _, err := models.Orm.Insert(row); err != nil {
c.jsonErr(500, 500, "创建失败: "+err.Error())
return
}
c.ok(commissionRuleDTO(row))
}
// RuleUpdate POST /platform/commission/rules/:id 更新规则
func (c *PlatformCommissionController) RuleUpdate() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
row := &models.PlatformCommissionRule{}
if err := models.Orm.QueryTable(new(models.PlatformCommissionRule)).
Filter("id", id).Filter("delete_time__isnull", true).One(row); err != nil {
c.jsonErr(404, 404, "规则不存在")
return
}
body, _ := io.ReadAll(c.Ctx.Request.Body)
var p commissionRulePayload
if err := json.Unmarshal(body, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if name := strings.TrimSpace(p.RuleName); name != "" {
row.RuleName = name
}
if calc := strings.TrimSpace(p.CalcType); calc != "" {
row.CalcType = calc
}
if ot := strings.TrimSpace(p.OrderType); ot != "" {
row.OrderType = ot
}
row.Remark = strings.TrimSpace(p.Remark)
if p.Status != nil {
row.Status = *p.Status
}
if err := applyCommissionRuleValues(row, &p); err != nil {
c.jsonErr(400, 400, err.Error())
return
}
if _, err := models.Orm.Update(row); err != nil {
c.jsonErr(500, 500, "更新失败: "+err.Error())
return
}
c.ok(commissionRuleDTO(row))
}
// applyCommissionRuleValues 按计算方式写入 value / tier_json
func applyCommissionRuleValues(row *models.PlatformCommissionRule, p *commissionRulePayload) error {
switch row.CalcType {
case models.CommissionCalcPercent:
if p.Value == nil || *p.Value <= 0 {
return fmt.Errorf("请输入大于 0 的比例值")
}
row.Value = int64(*p.Value*100 + 0.5)
row.TierJSON = nil
case models.CommissionCalcFixed:
if p.Value == nil || *p.Value <= 0 {
return fmt.Errorf("请输入大于 0 的固定金额(分)")
}
row.Value = int64(*p.Value + 0.5)
row.TierJSON = nil
case models.CommissionCalcTiered:
tierJSON, err := buildTierJSON(p.Steps)
if err != nil {
return err
}
row.Value = 0
row.TierJSON = &tierJSON
default:
return fmt.Errorf("计算方式不合法")
}
switch row.OrderType {
case models.CommissionOrderTypeAll, models.CommissionOrderTypePlatformUsage,
models.CommissionOrderTypeModuleShop, models.CommissionOrderTypeServiceFee:
default:
return fmt.Errorf("适用订单类型不合法")
}
return nil
}
// RuleDelete DELETE /platform/commission/rules/:id 删除规则(软删除;历史佣金单不受影响)
func (c *PlatformCommissionController) RuleDelete() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
now := time.Now()
if _, err := models.Orm.QueryTable(new(models.PlatformCommissionRule)).
Filter("id", id).
Update(orm.Params{"delete_time": now, "update_time": now}); err != nil {
c.jsonErr(500, 500, "删除失败: "+err.Error())
return
}
c.ok(nil)
}
// RuleToggle POST /platform/commission/rules/:id/status 启用/停用
func (c *PlatformCommissionController) RuleToggle() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
body, _ := io.ReadAll(c.Ctx.Request.Body)
var p struct {
Status *int8 `json:"status"`
}
if err := json.Unmarshal(body, &p); err != nil || p.Status == nil {
c.jsonErr(400, 400, "参数错误")
return
}
if _, err := models.Orm.QueryTable(new(models.PlatformCommissionRule)).
Filter("id", id).
Update(orm.Params{"status": *p.Status, "update_time": time.Now()}); err != nil {
c.jsonErr(500, 500, "操作失败: "+err.Error())
return
}
c.ok(map[string]interface{}{"status": *p.Status})
}