feat: 实现单卡资产分配与回收功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m45s

- 新增单卡分配/回收 API(支持 ICCID 列表、号段范围、筛选条件三种选卡方式)
- 新增资产分配记录查询 API(支持多条件筛选和分页)
- 新增 AssetAllocationRecord 模型、Store、Service、Handler 完整实现
- 扩展 IotCardStore 新增批量更新、号段查询、筛选查询等方法
- 修复 GORM Callback 处理 slice 类型(BatchCreate)的问题
- 新增完整的单元测试和集成测试
- 同步 OpenSpec 规范并归档 change
This commit is contained in:
2026-01-24 15:46:15 +08:00
parent a924e63e68
commit 194078674a
33 changed files with 2785 additions and 92 deletions

View File

@@ -0,0 +1,58 @@
package admin
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
assetAllocationRecordService "github.com/break/junhong_cmp_fiber/internal/service/asset_allocation_record"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type AssetAllocationRecordHandler struct {
service *assetAllocationRecordService.Service
}
func NewAssetAllocationRecordHandler(service *assetAllocationRecordService.Service) *AssetAllocationRecordHandler {
return &AssetAllocationRecordHandler{service: service}
}
func (h *AssetAllocationRecordHandler) List(c *fiber.Ctx) error {
var req dto.ListAssetAllocationRecordRequest
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
ctx := c.UserContext()
userType := middleware.GetUserTypeFromContext(ctx)
var userShopID *uint
if userType == constants.UserTypeAgent {
shopID := middleware.GetShopIDFromContext(ctx)
if shopID > 0 {
userShopID = &shopID
}
}
result, err := h.service.List(ctx, &req, userShopID)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize)
}
func (h *AssetAllocationRecordHandler) GetByID(c *fiber.Ctx) error {
var req dto.GetAssetAllocationRecordRequest
if err := c.ParamsParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.GetByID(c.UserContext(), req.ID)
if err != nil {
return err
}
return response.Success(c, result)
}

View File

@@ -5,7 +5,9 @@ import (
"github.com/break/junhong_cmp_fiber/internal/model/dto"
iotCardService "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
@@ -30,3 +32,55 @@ func (h *IotCardHandler) ListStandalone(c *fiber.Ctx) error {
return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize)
}
func (h *IotCardHandler) AllocateCards(c *fiber.Ctx) error {
var req dto.AllocateStandaloneCardsRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
ctx := c.UserContext()
operatorID := middleware.GetUserIDFromContext(ctx)
userType := middleware.GetUserTypeFromContext(ctx)
var operatorShopID *uint
if userType == constants.UserTypeAgent {
shopID := middleware.GetShopIDFromContext(ctx)
if shopID > 0 {
operatorShopID = &shopID
}
}
result, err := h.service.AllocateCards(ctx, &req, operatorID, operatorShopID)
if err != nil {
return err
}
return response.Success(c, result)
}
func (h *IotCardHandler) RecallCards(c *fiber.Ctx) error {
var req dto.RecallStandaloneCardsRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
ctx := c.UserContext()
operatorID := middleware.GetUserIDFromContext(ctx)
userType := middleware.GetUserTypeFromContext(ctx)
var operatorShopID *uint
if userType == constants.UserTypeAgent {
shopID := middleware.GetShopIDFromContext(ctx)
if shopID > 0 {
operatorShopID = &shopID
}
}
result, err := h.service.RecallCards(ctx, &req, operatorID, operatorShopID)
if err != nil {
return err
}
return response.Success(c, result)
}