package audit import ( "context" "strconv" "github.com/google/uuid" "gorm.io/gorm" shopapp "github.com/break/junhong_cmp_fiber/internal/application/shop" "github.com/break/junhong_cmp_fiber/pkg/constants" pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors" ) // WriteBusinessOwnerBatch 将店铺负责人批量交接写为批次根事件与逐店子事件。 // 成功批次写平台作用域根事件(承载批次汇总)+ 店铺作用域子事件; // 业务回滚后的失败或拒绝只写根事件,由调用方在独立短事务内提交。 func (w *Writer) WriteBusinessOwnerBatch(ctx context.Context, tx *gorm.DB, batch shopapp.BusinessOwnerBatchAudit) error { if w == nil || w.registry == nil || tx == nil { return pkgerrors.New(pkgerrors.CodeInvalidStatus, "店铺负责人批量交接审计 Writer 未正确配置") } if batch.BatchKey == "" || batch.OperatorID == 0 || batch.Total <= 0 { return pkgerrors.New(pkgerrors.CodeInvalidParam, "店铺负责人批量交接审计事实不完整") } root := AppendInput{ EventID: batch.BatchKey, ActionCode: constants.AuditActionShopBusinessOwnerBatchUpdated, Summary: "批量交接店铺负责人", ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess, BatchTotal: batch.Total, SuccessCount: len(batch.Changes), Actor: ActorInput{Kind: constants.AuditActorAccount, ID: strconv.FormatUint(uint64(batch.OperatorID), 10)}, Source: constants.AuditSourceAdminAPI, Metadata: map[string]any{ "operation": batch.Operation, "shop_count": batch.Total, }, Resources: []ResourceInput{{ Type: constants.AuditResourceShopBusinessOwnerBatch, Key: batch.BatchKey, DisplayName: "店铺负责人批量交接", Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleShopBusinessOwnerBatch, IdentitySnapshot: map[string]any{ "batch_key": batch.BatchKey, "operation": batch.Operation, "shop_count": batch.Total, }, }}, } if batch.Owner != nil { root.Metadata["business_owner_account_id"] = batch.Owner.ID resource := AccountResource(batch.Owner, constants.AuditResourceRelationReference, constants.AuditResourceRoleShopBusinessOwner) resource.SortOrder = 1 root.Resources = append(root.Resources, resource) } if batch.Result != "" { root.Result = batch.Result root.SuccessCount = 0 root.FailCount = batch.Total return w.Append(ctx, tx, root) } children := make([]AppendInput, 0, len(batch.Changes)) for _, change := range batch.Changes { if change.Shop == nil || change.Shop.ID == 0 { return pkgerrors.New(pkgerrors.CodeInvalidParam, "店铺负责人批量交接审计资源不完整") } shopResource := ShopResource(change.Shop, constants.AuditResourceRelationPrimary, constants.AuditResourceRoleShopTarget) shopResource.BeforeData = map[string]any{"business_owner_account_id": change.BeforeOwnerID} shopResource.AfterData = map[string]any{"business_owner_account_id": change.AfterOwnerID} shopResource.SubjectVisibility = constants.AuditSubjectResult shopResource.SubjectSummary = "店铺业务员归属已更新" resources := []ResourceInput{shopResource} if change.PreviousOwner != nil { resource := AccountResource(change.PreviousOwner, constants.AuditResourceRelationReference, constants.AuditResourceRoleShopPreviousBusinessOwner) resource.BeforeData = map[string]any{"assigned": true} resource.AfterData = map[string]any{"assigned": false} resource.SortOrder = 1 resources = append(resources, resource) } if change.Owner != nil { resource := AccountResource(change.Owner, constants.AuditResourceRelationReference, constants.AuditResourceRoleShopBusinessOwner) resource.BeforeData = map[string]any{"assigned": false} resource.AfterData = map[string]any{"assigned": true} resource.SortOrder = 2 resources = append(resources, resource) } children = append(children, AppendInput{ EventID: "evt_" + uuid.NewString(), ActionCode: constants.AuditActionShopBusinessOwnerUpdated, Summary: "批量更新店铺业务员归属", ScopeType: constants.AuditScopeShop, ScopeID: strconv.FormatUint(uint64(change.Shop.ID), 10), Result: constants.AuditResultSuccess, Resources: resources, }) } return w.AppendBatch(ctx, tx, BatchInput{Root: root, Children: children}) }