feat: 实现账号与佣金管理模块
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m35s

新增功能:
- 店铺佣金查询:店铺佣金统计、店铺佣金记录列表、店铺提现记录
- 佣金提现审批:提现申请列表、审批通过、审批拒绝
- 提现配置管理:配置列表、新增配置、获取当前生效配置
- 企业管理:企业列表、创建、更新、删除、获取详情
- 企业卡授权:授权列表、批量授权、批量取消授权、统计
- 客户账号管理:账号列表、创建、更新状态、重置密码
- 我的佣金:佣金统计、佣金记录、提现申请、提现记录

数据库变更:
- 扩展 tb_commission_withdrawal_request 新增提现单号等字段
- 扩展 tb_account 新增 is_primary 字段
- 扩展 tb_commission_record 新增 shop_id、balance_after
- 扩展 tb_commission_withdrawal_setting 新增每日提现次数限制
- 扩展 tb_iot_card、tb_device 新增 shop_id 冗余字段
- 新建 tb_enterprise_card_authorization 企业卡授权表
- 新建 tb_asset_allocation_record 资产分配记录表
- 数据迁移:owner_type 枚举值 agent 统一为 shop

测试:
- 新增 7 个单元测试文件覆盖各服务
- 修复集成测试 Redis 依赖问题
This commit is contained in:
2026-01-21 18:20:44 +08:00
parent 1489abe668
commit 91c9bbfeb8
89 changed files with 11958 additions and 159 deletions

View File

@@ -0,0 +1,72 @@
package admin
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
commissionWithdrawalService "github.com/break/junhong_cmp_fiber/internal/service/commission_withdrawal"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type CommissionWithdrawalHandler struct {
service *commissionWithdrawalService.Service
}
func NewCommissionWithdrawalHandler(service *commissionWithdrawalService.Service) *CommissionWithdrawalHandler {
return &CommissionWithdrawalHandler{service: service}
}
func (h *CommissionWithdrawalHandler) ListWithdrawalRequests(c *fiber.Ctx) error {
var req model.WithdrawalRequestListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.ListWithdrawalRequests(c.UserContext(), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}
func (h *CommissionWithdrawalHandler) ApproveWithdrawal(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的提现申请ID")
}
var req model.ApproveWithdrawalReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.Approve(c.UserContext(), uint(id), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *CommissionWithdrawalHandler) RejectWithdrawal(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的提现申请ID")
}
var req model.RejectWithdrawalReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.Reject(c.UserContext(), uint(id), &req)
if err != nil {
return err
}
return response.Success(c, result)
}

View File

@@ -0,0 +1,55 @@
package admin
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
commissionWithdrawalSettingService "github.com/break/junhong_cmp_fiber/internal/service/commission_withdrawal_setting"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type CommissionWithdrawalSettingHandler struct {
service *commissionWithdrawalSettingService.Service
}
func NewCommissionWithdrawalSettingHandler(service *commissionWithdrawalSettingService.Service) *CommissionWithdrawalSettingHandler {
return &CommissionWithdrawalSettingHandler{service: service}
}
func (h *CommissionWithdrawalSettingHandler) Create(c *fiber.Ctx) error {
var req model.CreateWithdrawalSettingReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.Create(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *CommissionWithdrawalSettingHandler) List(c *fiber.Ctx) error {
var req model.WithdrawalSettingListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.List(c.UserContext(), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}
func (h *CommissionWithdrawalSettingHandler) GetCurrent(c *fiber.Ctx) error {
result, err := h.service.GetCurrent(c.UserContext())
if err != nil {
return err
}
return response.Success(c, result)
}

View File

@@ -0,0 +1,106 @@
package admin
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
customerAccountService "github.com/break/junhong_cmp_fiber/internal/service/customer_account"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type CustomerAccountHandler struct {
service *customerAccountService.Service
}
func NewCustomerAccountHandler(service *customerAccountService.Service) *CustomerAccountHandler {
return &CustomerAccountHandler{service: service}
}
func (h *CustomerAccountHandler) List(c *fiber.Ctx) error {
var req model.CustomerAccountListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.List(c.UserContext(), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}
func (h *CustomerAccountHandler) Create(c *fiber.Ctx) error {
var req model.CreateCustomerAccountReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.Create(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *CustomerAccountHandler) Update(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的账号ID")
}
var req model.UpdateCustomerAccountReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.Update(c.UserContext(), uint(id), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *CustomerAccountHandler) UpdatePassword(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的账号ID")
}
var req model.UpdateCustomerAccountPasswordReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.service.UpdatePassword(c.UserContext(), uint(id), req.Password); err != nil {
return err
}
return response.Success(c, nil)
}
func (h *CustomerAccountHandler) UpdateStatus(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的账号ID")
}
var req model.UpdateCustomerAccountStatusReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.service.UpdateStatus(c.UserContext(), uint(id), req.Status); err != nil {
return err
}
return response.Success(c, nil)
}

View File

@@ -0,0 +1,106 @@
package admin
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
enterpriseService "github.com/break/junhong_cmp_fiber/internal/service/enterprise"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type EnterpriseHandler struct {
service *enterpriseService.Service
}
func NewEnterpriseHandler(service *enterpriseService.Service) *EnterpriseHandler {
return &EnterpriseHandler{service: service}
}
func (h *EnterpriseHandler) Create(c *fiber.Ctx) error {
var req model.CreateEnterpriseReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.Create(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *EnterpriseHandler) List(c *fiber.Ctx) error {
var req model.EnterpriseListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.List(c.UserContext(), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}
func (h *EnterpriseHandler) Update(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
var req model.UpdateEnterpriseReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.Update(c.UserContext(), uint(id), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *EnterpriseHandler) UpdateStatus(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
var req model.UpdateEnterpriseStatusReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.service.UpdateStatus(c.UserContext(), uint(id), req.Status); err != nil {
return err
}
return response.Success(c, nil)
}
func (h *EnterpriseHandler) UpdatePassword(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
var req model.UpdateEnterprisePasswordReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.service.UpdatePassword(c.UserContext(), uint(id), req.Password); err != nil {
return err
}
return response.Success(c, nil)
}

View File

@@ -0,0 +1,140 @@
package admin
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
enterpriseCardService "github.com/break/junhong_cmp_fiber/internal/service/enterprise_card"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type EnterpriseCardHandler struct {
service *enterpriseCardService.Service
}
func NewEnterpriseCardHandler(service *enterpriseCardService.Service) *EnterpriseCardHandler {
return &EnterpriseCardHandler{service: service}
}
func (h *EnterpriseCardHandler) AllocateCardsPreview(c *fiber.Ctx) error {
idStr := c.Params("id")
enterpriseID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
var req model.AllocateCardsPreviewReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.AllocateCardsPreview(c.UserContext(), uint(enterpriseID), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *EnterpriseCardHandler) AllocateCards(c *fiber.Ctx) error {
idStr := c.Params("id")
enterpriseID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
var req model.AllocateCardsReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.AllocateCards(c.UserContext(), uint(enterpriseID), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *EnterpriseCardHandler) RecallCards(c *fiber.Ctx) error {
idStr := c.Params("id")
enterpriseID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
var req model.RecallCardsReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.RecallCards(c.UserContext(), uint(enterpriseID), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *EnterpriseCardHandler) ListCards(c *fiber.Ctx) error {
idStr := c.Params("id")
enterpriseID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
var req model.EnterpriseCardListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.ListCards(c.UserContext(), uint(enterpriseID), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}
func (h *EnterpriseCardHandler) SuspendCard(c *fiber.Ctx) error {
enterpriseIDStr := c.Params("id")
enterpriseID, err := strconv.ParseUint(enterpriseIDStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
cardIDStr := c.Params("card_id")
cardID, err := strconv.ParseUint(cardIDStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的卡ID")
}
if err := h.service.SuspendCard(c.UserContext(), uint(enterpriseID), uint(cardID)); err != nil {
return err
}
return response.Success(c, nil)
}
func (h *EnterpriseCardHandler) ResumeCard(c *fiber.Ctx) error {
enterpriseIDStr := c.Params("id")
enterpriseID, err := strconv.ParseUint(enterpriseIDStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的企业ID")
}
cardIDStr := c.Params("card_id")
cardID, err := strconv.ParseUint(cardIDStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的卡ID")
}
if err := h.service.ResumeCard(c.UserContext(), uint(enterpriseID), uint(cardID)); err != nil {
return err
}
return response.Success(c, nil)
}

View File

@@ -0,0 +1,69 @@
package admin
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
myCommissionService "github.com/break/junhong_cmp_fiber/internal/service/my_commission"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type MyCommissionHandler struct {
service *myCommissionService.Service
}
func NewMyCommissionHandler(service *myCommissionService.Service) *MyCommissionHandler {
return &MyCommissionHandler{service: service}
}
func (h *MyCommissionHandler) GetSummary(c *fiber.Ctx) error {
result, err := h.service.GetCommissionSummary(c.UserContext())
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *MyCommissionHandler) CreateWithdrawal(c *fiber.Ctx) error {
var req model.CreateMyWithdrawalReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.CreateWithdrawalRequest(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *MyCommissionHandler) ListWithdrawals(c *fiber.Ctx) error {
var req model.MyWithdrawalListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.ListMyWithdrawalRequests(c.UserContext(), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}
func (h *MyCommissionHandler) ListRecords(c *fiber.Ctx) error {
var req model.MyCommissionRecordListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.ListMyCommissionRecords(c.UserContext(), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}

View File

@@ -0,0 +1,72 @@
package admin
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
shopCommissionService "github.com/break/junhong_cmp_fiber/internal/service/shop_commission"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type ShopCommissionHandler struct {
service *shopCommissionService.Service
}
func NewShopCommissionHandler(service *shopCommissionService.Service) *ShopCommissionHandler {
return &ShopCommissionHandler{service: service}
}
func (h *ShopCommissionHandler) ListCommissionSummary(c *fiber.Ctx) error {
var req model.ShopCommissionSummaryListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.ListShopCommissionSummary(c.UserContext(), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}
func (h *ShopCommissionHandler) ListWithdrawalRequests(c *fiber.Ctx) error {
shopID, err := strconv.ParseUint(c.Params("shop_id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的店铺 ID")
}
var req model.ShopWithdrawalRequestListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.ListShopWithdrawalRequests(c.UserContext(), uint(shopID), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}
func (h *ShopCommissionHandler) ListCommissionRecords(c *fiber.Ctx) error {
shopID, err := strconv.ParseUint(c.Params("shop_id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的店铺 ID")
}
var req model.ShopCommissionRecordListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.ListShopCommissionRecords(c.UserContext(), uint(shopID), &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}