package device import ( "context" "strconv" "github.com/google/uuid" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/infrastructure/audit" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/auditcontext" "github.com/break/junhong_cmp_fiber/pkg/constants" ) func (s *Service) appendCSVBatchAllocationAudit( ctx context.Context, tx *gorm.DB, devices []*model.Device, succeededIDs []uint, failedItems []dto.AllocationDeviceFailedItem, targetShopID uint, ) error { linkage := auditcontext.From(ctx) if s.auditWriter == nil || linkage.ActorKind != constants.AuditActorSystemTask || linkage.ActorID != constants.TaskTypeDeviceImport || linkage.Source != constants.AuditSourceWorker || linkage.CorrelationID == "" { return nil } devicesByID := make(map[uint]*model.Device, len(devices)) for _, device := range devices { if device != nil { devicesByID[device.ID] = device } } rootEventID := stableBatchEventID("root", linkage.CorrelationID) children := make([]audit.AppendInput, 0, len(succeededIDs)+len(failedItems)) for _, deviceID := range succeededIDs { if device := devicesByID[deviceID]; device != nil { children = append(children, deviceBatchChild(device, rootEventID, linkage.CorrelationID, targetShopID, true, "")) } } for _, item := range failedItems { if device := devicesByID[item.DeviceID]; device != nil { children = append(children, deviceBatchChild(device, rootEventID, linkage.CorrelationID, targetShopID, false, item.Reason)) } } result := constants.AuditResultSuccess if len(succeededIDs) > 0 && len(failedItems) > 0 { result = constants.AuditResultPartial } return s.auditWriter.AppendBatch(ctx, tx, audit.BatchInput{ Root: audit.AppendInput{ EventID: rootEventID, ActionCode: constants.AuditActionDeviceBatchAllocationCompleted, Summary: "设备CSV批量分配完成", Result: result, CorrelationID: linkage.CorrelationID, BatchTotal: len(succeededIDs) + len(failedItems), SuccessCount: len(succeededIDs), FailCount: len(failedItems), Metadata: map[string]any{"operation_type": constants.DeviceImportOperationAssignShop, "target_shop_id": targetShopID}, Resources: []audit.ResourceInput{{ Type: constants.AuditResourceDeviceBatchTask, Key: linkage.CorrelationID, DisplayName: linkage.CorrelationID, Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleBatchTask, IdentitySnapshot: map[string]any{"task_no": linkage.CorrelationID, "operation_type": constants.DeviceImportOperationAssignShop}, SubjectVisibility: constants.AuditSubjectInternalOnly, }}, }, Children: children, }) } func deviceBatchChild( device *model.Device, parentEventID string, correlationID string, targetShopID uint, succeeded bool, reason string, ) audit.AppendInput { resourceID := strconv.FormatUint(uint64(device.ID), 10) before := map[string]any{"shop_id": device.ShopID, "status": device.Status} after := before result := constants.AuditResultFailed summary := "设备批量分配失败" if succeeded { after = map[string]any{"shop_id": targetShopID, "status": constants.DeviceStatusDistributed} result = constants.AuditResultSuccess summary = "设备批量分配成功" } return audit.AppendInput{ EventID: stableBatchEventID("device", correlationID+":"+resourceID), ActionCode: constants.AuditActionDeviceBatchAllocationItem, Summary: summary, Result: result, ErrorSummary: reason, CorrelationID: correlationID, ParentEventID: parentEventID, Resources: []audit.ResourceInput{{ Type: constants.AuditResourceDevice, ID: &resourceID, Key: deviceAuditKey(device), DisplayName: deviceAuditKey(device), Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleBatchItem, IdentitySnapshot: map[string]any{ "id": device.ID, "virtual_no": device.VirtualNo, "imei": device.IMEI, "sn": device.SN, "shop_id": device.ShopID, "series_id": device.SeriesID, "generation": device.Generation, }, BeforeData: before, AfterData: after, SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: summary, }}, } } func stableBatchEventID(kind, key string) string { return "evt_" + uuid.NewSHA1(uuid.NameSpaceOID, []byte("device-batch:"+kind+":"+key)).String() } func deviceAuditKey(device *model.Device) string { for _, value := range []string{device.VirtualNo, device.IMEI, device.SN} { if value != "" { return value } } return strconv.FormatUint(uint64(device.ID), 10) }