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}) }