Constraint: 切换 main 前必须保存当前七月分支全部项目进展,套餐生效提案仅属于 Iteration/7-11。 Rejected: 将七月套餐修复直接移植到 main | 两个分支的可靠投递架构不同。 Confidence: medium Scope-risk: broad Directive: 不得将本提交整体 cherry-pick 到 main;main 套餐热修必须基于其纯 Asynq 代码独立实施。 Tested: git diff --check;openspec validate fix-package-activation-starvation --strict。 Not-tested: 按用户要求未运行自动化测试;go build ./... 因当前审计改造中的 Enterprise 模型字面量和 role.recordFailure 参数类型错误未通过。
118 lines
4.5 KiB
Go
118 lines
4.5 KiB
Go
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)
|
|
}
|