package wecom import ( "context" "strings" "time" "github.com/bytedance/sonic" "github.com/hibiken/asynq" approvalapp "github.com/break/junhong_cmp_fiber/internal/application/approval" "github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" ) // ApprovalDetailTaskHandler 拉取企微审批详情并翻译为现有标准决策。 type ApprovalDetailTaskHandler struct { details *ApprovalDetailClient contexts *ApprovalContextRepository decisions *approvalapp.SyncDecisionService integration TokenIntegrationLog } // NewApprovalDetailTaskHandler 创建企业微信审批详情同步任务 Handler。 func NewApprovalDetailTaskHandler(details *ApprovalDetailClient, contexts *ApprovalContextRepository, decisions *approvalapp.SyncDecisionService, integration TokenIntegrationLog) *ApprovalDetailTaskHandler { return &ApprovalDetailTaskHandler{details: details, contexts: contexts, decisions: decisions, integration: integration} } // Handle 处理回调或后续轮询提交的详情同步任务。 func (h *ApprovalDetailTaskHandler) Handle(ctx context.Context, task *asynq.Task) error { if h == nil || h.details == nil || h.contexts == nil || h.decisions == nil || h.integration == nil { return errors.New(errors.CodeServiceUnavailable, "企业微信审批详情同步任务未配置") } var payload ApprovalDetailSyncTask if err := sonic.Unmarshal(task.Payload(), &payload); err != nil || payload.ApplicationID == 0 || strings.TrimSpace(payload.SPNo) == "" { return errors.New(errors.CodeInvalidParam, "企业微信审批详情同步任务载荷无效") } if !validApprovalSyncSource(payload.Source) { return errors.New(errors.CodeInvalidParam, "企业微信审批详情同步来源无效") } detail, err := h.details.Get(ctx, payload.ApplicationID, payload.SPNo) if err != nil { return err } record, err := h.contexts.FindBySPNo(ctx, payload.ApplicationID, payload.SPNo) if err != nil { return err } if record == nil { record, err = h.recoverUnknownCallback(ctx, payload, detail) if err != nil { return err } if record == nil { return h.completeIgnoredCallback(ctx, payload) } } if err := h.contexts.SaveLatestDetail(ctx, record.Instance.ID, detail.SPStatus, detail.Snapshot); err != nil { return err } decisions := weComDecisions(detail.SPStatus) for _, decision := range decisions { if _, err := h.decisions.Execute(ctx, approvalapp.SyncDecisionCommand{ InstanceID: record.Instance.ID, Decision: decision, DecisionSnapshot: detail.Snapshot, Source: payload.Source, }); err != nil { return err } } if strings.TrimSpace(payload.IntegrationID) != "" { _, err = h.integration.Complete(ctx, payload.IntegrationID, integrationlog.Completion{ Result: constants.IntegrationResultCompleted, ProviderCode: "processed", ProviderMessage: "企业微信审批回调已完成权威详情同步", ResponseSummary: map[string]any{"sp_no": payload.SPNo, "sp_status": detail.SPStatus, "decisions": decisions}, DurationMS: 0, StateChanged: len(decisions) > 0, }) } return err } func (h *ApprovalDetailTaskHandler) recoverUnknownCallback(ctx context.Context, payload ApprovalDetailSyncTask, detail ApprovalDetail) (*ApprovalSubmissionRecord, error) { if payload.Source != constants.ApprovalSyncSourceCallback { return nil, nil } fingerprint := approvalDetailFingerprint(detail.Snapshot) candidate, err := h.contexts.FindUniqueUnknownByFingerprint( ctx, payload.ApplicationID, fingerprint.TemplateID, fingerprint.CreatorUserID, fingerprint.SubmittedAt, ) if err != nil || candidate == nil { return nil, err } recovered, err := h.contexts.RecoverSubmitted(ctx, candidate.InstanceID, payload.SPNo) if err != nil || !recovered { return nil, err } return h.contexts.Get(ctx, candidate.InstanceID) } func (h *ApprovalDetailTaskHandler) completeIgnoredCallback(ctx context.Context, payload ApprovalDetailSyncTask) error { if strings.TrimSpace(payload.IntegrationID) == "" { return nil } _, err := h.integration.Complete(ctx, payload.IntegrationID, integrationlog.Completion{ Result: constants.IntegrationResultIgnored, ProviderCode: "unrelated_approval", ProviderMessage: "企业微信审批单未关联本系统业务,已忽略", ResponseSummary: map[string]any{"sp_no": payload.SPNo, "ignored": true}, }) return err } type approvalFingerprint struct { TemplateID string CreatorUserID string SubmittedAt time.Time } func approvalDetailFingerprint(snapshot []byte) approvalFingerprint { var value struct { TemplateID string `json:"template_id"` ApplyTime int64 `json:"apply_time"` Applyer struct { UserID string `json:"userid"` } `json:"applyer"` } if sonic.Unmarshal(snapshot, &value) != nil || value.ApplyTime <= 0 { return approvalFingerprint{} } return approvalFingerprint{ TemplateID: strings.TrimSpace(value.TemplateID), CreatorUserID: strings.TrimSpace(value.Applyer.UserID), SubmittedAt: time.Unix(value.ApplyTime, 0).UTC(), } } func validApprovalSyncSource(source string) bool { switch source { case constants.ApprovalSyncSourceCallback, constants.ApprovalSyncSourcePolling, constants.ApprovalSyncSourceManual: return true default: return false } } // weComDecisions 只翻译现有审批核心支持的企微标准终态;通过后撤销按领域允许顺序补齐两个事实。 func weComDecisions(status int) []string { switch status { case 2: return []string{constants.ApprovalDecisionApproved} case 3: return []string{constants.ApprovalDecisionRejected} case 4: return []string{constants.ApprovalDecisionCancelled} case 6: return []string{constants.ApprovalDecisionApproved, constants.ApprovalDecisionRevokedAfterApproved} case 7: return []string{constants.ApprovalDecisionDeleted} default: return nil } }