This commit is contained in:
@@ -49,11 +49,12 @@ type ApplicationSubmitResult struct {
|
||||
InstanceStatus int
|
||||
}
|
||||
|
||||
// ApplicationService 创建与重提核销申请。
|
||||
// ApplicationService 创建与重提核销申请,并恢复原审批提交事实。
|
||||
// 申请、审批尝试记录、审批实例与账单预占在同一事务完成;任一校验失败都不留下半成品事实。
|
||||
type ApplicationService struct {
|
||||
db *gorm.DB
|
||||
approval approvalapp.Port
|
||||
recovery approvalapp.RecoveryPort
|
||||
audit ApplicationAuditWriter
|
||||
}
|
||||
|
||||
@@ -62,6 +63,22 @@ func NewApplicationService(db *gorm.DB, approval approvalapp.Port, audit Applica
|
||||
return &ApplicationService{db: db, approval: approval, audit: audit}
|
||||
}
|
||||
|
||||
// SetRecoveryPort 注入通用审批原实例恢复接缝。
|
||||
func (s *ApplicationService) SetRecoveryPort(recovery approvalapp.RecoveryPort) {
|
||||
s.recovery = recovery
|
||||
}
|
||||
|
||||
// RecoveryResult 返回原审批恢复动作的安全结果。
|
||||
type RecoveryResult struct {
|
||||
ApplicationID uint
|
||||
ApprovalInstanceID uint
|
||||
Action string
|
||||
Result string
|
||||
SubmissionStatus int
|
||||
LastRecoveryAt *time.Time
|
||||
Recoverable bool
|
||||
}
|
||||
|
||||
// Create 为本人可见账单创建核销申请;超级管理员可为账单欠款人代办并必须填写代办原因。
|
||||
func (s *ApplicationService) Create(ctx context.Context, command SubmitApplicationCommand) (*ApplicationSubmitResult, error) {
|
||||
if err := s.ensureReady(); err != nil {
|
||||
@@ -89,6 +106,81 @@ func (s *ApplicationService) Resubmit(ctx context.Context, applicationID uint, c
|
||||
return s.submit(ctx, caller, applicationID, command)
|
||||
}
|
||||
|
||||
// RecoverApproval 恢复员工核销申请的原审批事实,不创建新尝试或审批实例。
|
||||
func (s *ApplicationService) RecoverApproval(ctx context.Context, applicationID uint) (*RecoveryResult, error) {
|
||||
if applicationID == 0 {
|
||||
return nil, errors.New(errors.CodeEmployeeCollectionApplicationNotFound)
|
||||
}
|
||||
actorID := middleware.GetUserIDFromContext(ctx)
|
||||
if actorID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
userType := middleware.GetUserTypeFromContext(ctx)
|
||||
if userType == constants.UserTypeAgent || userType == constants.UserTypeEnterprise {
|
||||
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
if s == nil || s.db == nil || s.recovery == nil || s.audit == nil {
|
||||
return nil, errors.New(errors.CodeServiceUnavailable, "核销审批恢复能力尚未配置")
|
||||
}
|
||||
var result RecoveryResult
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
application, err := lockApplication(ctx, tx, applicationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if application.ApplicantAccountID != actorID && userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
|
||||
return errors.New(errors.CodeEmployeeCollectionApplicationNotFound)
|
||||
}
|
||||
if application.Status != constants.EmployeeCollectionApplicationStatusPending || application.LatestAttemptID == 0 || application.LatestApprovalInstanceID == 0 {
|
||||
return errors.New(errors.CodeConflict, "终态申请不可恢复")
|
||||
}
|
||||
var attempt model.EmployeeCollectionApplicationAttempt
|
||||
if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND application_id = ?", application.LatestAttemptID, application.ID).First(&attempt).Error; err != nil {
|
||||
return errors.New(errors.CodeConflict, "核销审批尝试关联不一致")
|
||||
}
|
||||
if attempt.ApprovalInstanceID == nil || *attempt.ApprovalInstanceID != application.LatestApprovalInstanceID {
|
||||
return errors.New(errors.CodeConflict, "核销审批实例关联不一致")
|
||||
}
|
||||
var instance model.ApprovalInstance
|
||||
if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND business_type = ? AND business_id = ?", application.LatestApprovalInstanceID, constants.ApprovalBusinessTypeEmployeeCollection, attempt.ID).First(&instance).Error; err != nil {
|
||||
return errors.New(errors.CodeConflict, "审批实例业务关联不一致")
|
||||
}
|
||||
if instance.Status != constants.ApprovalStatusSubmitting && instance.Status != constants.ApprovalStatusPending && instance.Status != constants.ApprovalStatusSubmissionFailed && instance.Status != constants.ApprovalStatusSubmissionUnknown {
|
||||
return errors.New(errors.CodeConflict, "审批终态不允许恢复")
|
||||
}
|
||||
var wc model.WeComApprovalContext
|
||||
if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("approval_instance_id = ? AND business_type = ?", instance.ID, constants.ApprovalBusinessTypeEmployeeCollection).First(&wc).Error; err != nil {
|
||||
return errors.New(errors.CodeConflict, "企业微信审批上下文不存在")
|
||||
}
|
||||
beforeStatus := fmt.Sprintf("%d", wc.SubmissionStatus)
|
||||
action, recoveryErr := "active", error(nil)
|
||||
if wc.SPNo != "" {
|
||||
action = "sp_no"
|
||||
recoveryErr = s.recovery.EnqueueSubmittedSync(ctx, tx, instance.ID)
|
||||
} else if wc.SubmissionStatus == constants.WeComSubmissionStatusUnknown || instance.Status == constants.ApprovalStatusSubmissionUnknown {
|
||||
action = "unknown"
|
||||
recoveryErr = s.recovery.EnqueueUnknownConfirm(ctx, tx, instance.ID)
|
||||
} else if instance.Status == constants.ApprovalStatusSubmissionFailed && wc.SubmissionStatus == constants.WeComSubmissionStatusFailed {
|
||||
action = "replay"
|
||||
_, recoveryErr = s.recovery.RecoverSubmissionEvent(ctx, tx, instance.ID)
|
||||
} else if instance.Status != constants.ApprovalStatusSubmitting && instance.Status != constants.ApprovalStatusPending && wc.SubmissionStatus != constants.WeComSubmissionStatusReady && wc.SubmissionStatus != constants.WeComSubmissionStatusSending {
|
||||
return errors.New(errors.CodeConflict, "当前审批提交状态不允许恢复")
|
||||
}
|
||||
if recoveryErr != nil {
|
||||
return errors.Wrap(errors.CodeConflict, recoveryErr, "恢复审批提交失败")
|
||||
}
|
||||
if err := s.audit.WriteEmployeeCollectionRecovery(ctx, tx, application, &attempt, &instance, actorID, beforeStatus, action, "accepted"); err != nil {
|
||||
return err
|
||||
}
|
||||
result = RecoveryResult{ApplicationID: application.ID, ApprovalInstanceID: instance.ID, Action: action, Result: "accepted", SubmissionStatus: wc.SubmissionStatus, LastRecoveryAt: wc.LastRecoveryAt, Recoverable: true}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ensureReady 校验用例依赖完整,缺失时失败关闭,避免绕过企业微信终审。
|
||||
func (s *ApplicationService) ensureReady() error {
|
||||
if s == nil || s.db == nil || s.approval == nil || s.audit == nil {
|
||||
|
||||
@@ -34,4 +34,5 @@ type ApplicationAudit struct {
|
||||
// ApplicationAuditWriter 在员工代收款核销事务内追加统一 Audit Event。
|
||||
type ApplicationAuditWriter interface {
|
||||
WriteEmployeeCollectionApplication(ctx context.Context, tx *gorm.DB, change ApplicationAudit) error
|
||||
WriteEmployeeCollectionRecovery(ctx context.Context, tx *gorm.DB, application *model.EmployeeCollectionApplication, attempt *model.EmployeeCollectionApplicationAttempt, instance *model.ApprovalInstance, actorID uint, beforeStatus, action, result string) error
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
authHandler "github.com/break/junhong_cmp_fiber/internal/handler/auth"
|
||||
"github.com/break/junhong_cmp_fiber/internal/handler/callback"
|
||||
openapiHandler "github.com/break/junhong_cmp_fiber/internal/handler/openapi"
|
||||
approvalInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/approval"
|
||||
auditInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/carriercallback"
|
||||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
|
||||
@@ -359,10 +360,11 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
|
||||
WechatConfig: admin.NewWechatConfigHandler(svc.WechatConfig),
|
||||
PaymentMerchant: admin.NewPaymentMerchantHandler(merchantPaymentApp.NewManagementService(deps.DB, systemConfigAudit)),
|
||||
EmployeeCollection: func() *admin.EmployeeCollectionHandler {
|
||||
application := employeecollectionApp.NewApplicationService(deps.DB, svc.Approval, svc.AccessAudit)
|
||||
application.SetRecoveryPort(approvalInfra.NewRecoveryPort(deps.DB, deps.QueueClient))
|
||||
handler := admin.NewEmployeeCollectionHandler(
|
||||
employeecollectionApp.NewPaymentMethodService(deps.DB, svc.AccessAudit),
|
||||
employeecollectionApp.NewBillCloseService(deps.DB, svc.AccessAudit),
|
||||
employeecollectionApp.NewApplicationService(deps.DB, svc.Approval, svc.AccessAudit),
|
||||
employeecollectionApp.NewBillCloseService(deps.DB, svc.AccessAudit), application,
|
||||
)
|
||||
handler.SetPaymentMethodQuery(employeecollectionQuery.NewPaymentMethodQuery(deps.DB))
|
||||
handler.SetBillQuery(employeecollectionQuery.NewBillQuery(deps.DB))
|
||||
|
||||
@@ -35,6 +35,16 @@ func (h *DeviceHandler) List(c *fiber.Ctx) error {
|
||||
return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize)
|
||||
}
|
||||
|
||||
// ListDeviceTypes 查询设备类型选项。
|
||||
// GET /api/admin/devices/types
|
||||
func (h *DeviceHandler) ListDeviceTypes(c *fiber.Ctx) error {
|
||||
result, err := h.service.ListDeviceTypes(c.UserContext())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return response.Success(c, result)
|
||||
}
|
||||
|
||||
// BatchUpdateRealnamePolicy 批量更新设备实名认证策略。
|
||||
// POST /api/admin/devices/batch-update-realname-policy
|
||||
func (h *DeviceHandler) BatchUpdateRealnamePolicy(c *fiber.Ctx) error {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
employeecollectionapp "github.com/break/junhong_cmp_fiber/internal/application/employeecollection"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
employeecollectionquery "github.com/break/junhong_cmp_fiber/internal/query/employeecollection"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
@@ -268,6 +269,32 @@ func (h *EmployeeCollectionHandler) GetApplication(c *fiber.Ctx) error {
|
||||
return response.Success(c, result)
|
||||
}
|
||||
|
||||
// RecoverApplication 恢复核销申请的原审批提交事实。
|
||||
// POST /api/admin/employee-collection-applications/:id/recover-approval
|
||||
func (h *EmployeeCollectionHandler) RecoverApplication(c *fiber.Ctx) error {
|
||||
id, err := pathID(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := h.application.RecoverApproval(c.UserContext(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return response.Success(c, &dto.EmployeeCollectionApplicationRecoveryResponse{
|
||||
ApplicationID: result.ApplicationID, ApprovalInstanceID: result.ApprovalInstanceID,
|
||||
Action: result.Action, Result: result.Result, SubmissionStatus: result.SubmissionStatus,
|
||||
SubmissionStatusName: constants.GetWeComSubmissionStatusName(result.SubmissionStatus), Recoverable: result.Recoverable,
|
||||
LastRecoveryAt: formatRecoveryTime(result.LastRecoveryAt),
|
||||
})
|
||||
}
|
||||
|
||||
func formatRecoveryTime(value *time.Time) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return value.UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// bindApplicationRequest 绑定并转换创建或重提核销申请请求。
|
||||
// 付款时间按带时区的 RFC3339 解析;其余边界与业务校验由应用用例统一判断。
|
||||
func bindApplicationRequest(c *fiber.Ctx) (employeecollectionapp.SubmitApplicationCommand, error) {
|
||||
|
||||
@@ -87,7 +87,7 @@ func (r *RecoveryPort) RecoverSubmissionEvent(ctx context.Context, tx *gorm.DB,
|
||||
}
|
||||
|
||||
func recoverableBusinessType(value string) bool {
|
||||
return value == constants.ApprovalBusinessTypeRefund || value == constants.ApprovalBusinessTypeAgentDistribution
|
||||
return value == constants.ApprovalBusinessTypeRefund || value == constants.ApprovalBusinessTypeAgentDistribution || value == constants.ApprovalBusinessTypeEmployeeCollection
|
||||
}
|
||||
|
||||
var _ approvalapp.RecoveryPort = (*RecoveryPort)(nil)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
employeecollection "github.com/break/junhong_cmp_fiber/internal/application/employeecollection"
|
||||
employeecollectiondomain "github.com/break/junhong_cmp_fiber/internal/domain/employeecollection"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
@@ -83,3 +84,25 @@ func (w *Writer) WriteEmployeeCollectionApplication(ctx context.Context, tx *gor
|
||||
Resources: resources,
|
||||
})
|
||||
}
|
||||
|
||||
// WriteEmployeeCollectionRecovery 记录人工恢复原审批的脱敏审计事实。
|
||||
func (w *Writer) WriteEmployeeCollectionRecovery(ctx context.Context, tx *gorm.DB, application *model.EmployeeCollectionApplication, attempt *model.EmployeeCollectionApplicationAttempt, instance *model.ApprovalInstance, actorID uint, beforeStatus, action, result string) error {
|
||||
if application == nil || application.ID == 0 || attempt == nil || attempt.ID == 0 || instance == nil || instance.ID == 0 || actorID == 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "核销审批恢复审计资源不完整")
|
||||
}
|
||||
applicationID := strconv.FormatUint(uint64(application.ID), 10)
|
||||
attemptID := strconv.FormatUint(uint64(attempt.ID), 10)
|
||||
instanceID := strconv.FormatUint(uint64(instance.ID), 10)
|
||||
return w.Append(ctx, tx, AppendInput{
|
||||
EventID: "employee_collection:application:" + applicationID + ":recover:" + action,
|
||||
ActionCode: constants.AuditActionApprovalSubmissionRecovered, Summary: "人工恢复员工核销原审批提交",
|
||||
Actor: ActorInput{Kind: constants.AuditActorAccount, ID: strconv.FormatUint(uint64(actorID), 10)}, Source: constants.AuditSourceAdminAPI,
|
||||
ScopeType: constants.AuditScopePlatform, Result: result,
|
||||
Metadata: map[string]any{"before_submission_status": beforeStatus, "action": action, "result": result},
|
||||
Resources: []ResourceInput{
|
||||
{Type: constants.AuditResourceEmployeeCollectionApplication, ID: &applicationID, Key: applicationID, DisplayName: "核销申请 " + applicationID, Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleCollectionApplication, IdentitySnapshot: map[string]any{"id": application.ID, "applicant_account_id": application.ApplicantAccountID}},
|
||||
{Type: constants.AuditResourceEmployeeCollectionAttempt, ID: &attemptID, Key: attemptID, DisplayName: "审批尝试 " + attemptID, Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleCollectionAttempt, IdentitySnapshot: map[string]any{"id": attempt.ID, "application_id": attempt.ApplicationID}},
|
||||
{Type: constants.AuditResourceApprovalInstance, ID: &instanceID, Key: instanceID, DisplayName: "审批实例 " + instanceID, Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleApprovalTarget, IdentitySnapshot: map[string]any{"id": instance.ID, "business_type": instance.BusinessType, "business_id": instance.BusinessID}},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ type ListDeviceResponse struct {
|
||||
PageSize int `json:"size" description:"每页数量"`
|
||||
}
|
||||
|
||||
// DeviceTypeOptionsResponse 设备类型选项响应。
|
||||
// 响应数据为去重、去除首尾空白并按升序排列的设备类型字符串数组。
|
||||
type DeviceTypeOptionsResponse []string
|
||||
|
||||
type GetDeviceRequest struct {
|
||||
ID uint `path:"id" description:"设备ID" required:"true"`
|
||||
}
|
||||
|
||||
@@ -293,6 +293,12 @@ type EmployeeCollectionApplicationResponse struct {
|
||||
StatusName string `json:"status_name" description:"申请状态中文名称"`
|
||||
LatestAttemptID uint `json:"latest_attempt_id" description:"最新审批尝试记录ID,仅用于展示"`
|
||||
LatestApprovalInstanceID uint `json:"latest_approval_instance_id" description:"最新通用审批实例ID,仅用于展示"`
|
||||
ApprovalSubmissionStatus int `json:"approval_submission_status" description:"审批提交状态"`
|
||||
ApprovalSubmissionName string `json:"approval_submission_name" description:"审批提交状态中文名称"`
|
||||
ApprovalFailureSummary string `json:"approval_failure_summary,omitempty" description:"脱敏审批失败摘要"`
|
||||
Recoverable bool `json:"recoverable" description:"是否允许恢复原审批"`
|
||||
RecoveryResult string `json:"recovery_result,omitempty" description:"最近一次恢复动作结果"`
|
||||
LastRecoveryAt string `json:"last_recovery_at,omitempty" description:"最近恢复时间"`
|
||||
DecidedAt *time.Time `json:"decided_at,omitempty" description:"审批终态到达时间"`
|
||||
TerminalReason string `json:"terminal_reason" description:"异常终态说明,如企业微信通过后撤销"`
|
||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||
@@ -341,3 +347,15 @@ type EmployeeCollectionApplicationDetailResponse struct {
|
||||
Allocations []*EmployeeCollectionApplicationAllocationResponse `json:"allocations" description:"该申请当前的账单分摊"`
|
||||
Attempts []*EmployeeCollectionBillAttemptResponse `json:"attempts" description:"全部审批尝试记录,按提交顺序排列,历史材料不被覆盖"`
|
||||
}
|
||||
|
||||
// EmployeeCollectionApplicationRecoveryResponse 返回人工恢复原审批结果。
|
||||
type EmployeeCollectionApplicationRecoveryResponse struct {
|
||||
ApplicationID uint `json:"application_id" description:"核销申请ID"`
|
||||
ApprovalInstanceID uint `json:"approval_instance_id" description:"原审批实例ID"`
|
||||
Action string `json:"action" description:"恢复动作"`
|
||||
Result string `json:"result" description:"恢复结果"`
|
||||
SubmissionStatus int `json:"submission_status" description:"审批提交状态"`
|
||||
SubmissionStatusName string `json:"submission_status_name" description:"审批提交状态中文名称"`
|
||||
Recoverable bool `json:"recoverable" description:"是否仍可恢复"`
|
||||
LastRecoveryAt string `json:"last_recovery_at,omitempty" description:"最近恢复时间"`
|
||||
}
|
||||
|
||||
@@ -6,12 +6,31 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"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/openapi"
|
||||
)
|
||||
|
||||
func registerDeviceRoutes(router fiber.Router, handler *admin.DeviceHandler, importHandler *admin.DeviceImportHandler, doc *openapi.Generator, basePath string) {
|
||||
devices := router.Group("/devices")
|
||||
groupPath := basePath + "/devices"
|
||||
platformDevices := router.Group("/devices", func(c *fiber.Ctx) error {
|
||||
userType := middleware.GetUserTypeFromContext(c.UserContext())
|
||||
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
|
||||
return errors.New(errors.CodeForbidden, constants.PlatformManagementForbiddenMessage)
|
||||
}
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
Register(platformDevices, doc, groupPath, "GET", "/types", handler.ListDeviceTypes, RouteSpec{
|
||||
Summary: "查询设备类型选项",
|
||||
Description: "仅超级管理员与平台账号可访问。返回未删除设备中去重、去除首尾空白且按升序排列的非空设备类型字符串。",
|
||||
Tags: []string{"设备管理"},
|
||||
Input: nil,
|
||||
Output: new(dto.DeviceTypeOptionsResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(devices, doc, groupPath, "GET", "", handler.List, RouteSpec{
|
||||
Summary: "设备列表",
|
||||
|
||||
@@ -155,4 +155,7 @@ func registerEmployeeCollectionApplicationRoutes(router fiber.Router, handler *a
|
||||
Output: new(dto.EmployeeCollectionApplicationSubmitResponse),
|
||||
Auth: true,
|
||||
})
|
||||
Register(group, doc, applicationPath, "POST", "/:id/recover-approval", handler.RecoverApplication, RouteSpec{
|
||||
Summary: "恢复核销原审批", Description: "复用最新审批尝试与原审批实例,按企业微信提交事实恢复或查询确认,不创建新审批。", Tags: []string{"员工代收款"}, Input: new(dto.IDReq), Output: new(dto.EmployeeCollectionApplicationRecoveryResponse), Auth: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -324,6 +324,18 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListDeviceTypes 查询平台管理端可用的设备类型选项。
|
||||
func (s *Service) ListDeviceTypes(ctx context.Context) ([]string, error) {
|
||||
types, err := s.deviceStore.ListDeviceTypes(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询设备类型选项失败")
|
||||
}
|
||||
if types == nil {
|
||||
types = make([]string, 0)
|
||||
}
|
||||
return types, nil
|
||||
}
|
||||
|
||||
func normalizeShopIDs(ids []uint) ([]uint, bool) {
|
||||
if len(ids) == 0 {
|
||||
return nil, false
|
||||
|
||||
@@ -142,6 +142,23 @@ func (s *DeviceStore) List(ctx context.Context, opts *store.QueryOptions, filter
|
||||
return devices, total, nil
|
||||
}
|
||||
|
||||
// ListDeviceTypes 查询未删除设备的去重设备类型。
|
||||
func (s *DeviceStore) ListDeviceTypes(ctx context.Context) ([]string, error) {
|
||||
types := make([]string, 0)
|
||||
trimmedType := "TRIM(device_type)"
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&model.Device{}).
|
||||
Where("NULLIF("+trimmedType+", '') IS NOT NULL").
|
||||
Select(trimmedType).
|
||||
Distinct().
|
||||
Order(trimmedType+" ASC").
|
||||
Pluck(trimmedType, &types).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types, nil
|
||||
}
|
||||
|
||||
func (s *DeviceStore) applyDeviceFilters(ctx context.Context, query *gorm.DB, filters map[string]any) *gorm.DB {
|
||||
if virtualNo, ok := filters["virtual_no"].(string); ok && virtualNo != "" {
|
||||
query = query.Where("virtual_no LIKE ?", "%"+virtualNo+"%")
|
||||
|
||||
Reference in New Issue
Block a user