feat(业务用户组): AUG26-003 业务用户组与店铺负责人分组导入
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 1h43m42s
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 1h43m42s
- 迁移 000221:新增 tb_business_user_group、tb_business_user_group_member、tb_shop_business_owner_import_task,成员一账号一行由部分唯一索引保证,店铺所属组按当前负责人实时推导,不回填历史分组。 - 用户组 CRUD、成员改组/清空归属、店铺批量交接(原子失败不部分写入)。 - 店铺负责人 CSV 导入任务:逐行独立事务、逐行明细、任务级与行级失败分离。 - 读侧推导与筛选:未分组、业务线、停用组可筛出并带停用标记。 - 补齐操作审计动作与资源、openapi 清单、发布门禁巡检表清单。 - 归档 add-shop-salesperson-groups 变更并同步 openspec/specs/business-user-group,补齐 AUG26-003 验证证据链。
This commit is contained in:
88
internal/infrastructure/audit/business_user_group.go
Normal file
88
internal/infrastructure/audit/business_user_group.go
Normal file
@@ -0,0 +1,88 @@
|
||||
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})
|
||||
}
|
||||
@@ -208,6 +208,16 @@ func NewRegistry() *Registry {
|
||||
orderPackageInvalidateTaskCreated := taskAction(constants.AuditActionOrderPackageInvalidateTaskCreated, "创建订单套餐批量失效任务", constants.AuditResourceOrderPackageInvalidateTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
|
||||
orderPackageInvalidateTaskCompleted := taskAction(constants.AuditActionOrderPackageInvalidateTaskCompleted, "完成订单套餐批量失效任务", constants.AuditResourceOrderPackageInvalidateTask, constants.AuditActorSystemTask, constants.AuditSourceWorker)
|
||||
orderPackageInvalidateItem := taskAction(constants.AuditActionOrderPackageInvalidateItem, "失效订单套餐权益", constants.AuditResourceOrder, constants.AuditActorSystemTask, constants.AuditSourceWorker)
|
||||
businessUserGroupCreated := businessUserGroupAction(constants.AuditActionBusinessUserGroupCreated, "创建业务用户组", constants.AuditRiskNormal, constants.AuditResourceBusinessUserGroup)
|
||||
businessUserGroupUpdated := businessUserGroupAction(constants.AuditActionBusinessUserGroupUpdated, "更新业务用户组", constants.AuditRiskNormal, constants.AuditResourceBusinessUserGroup)
|
||||
businessUserGroupEnabled := businessUserGroupAction(constants.AuditActionBusinessUserGroupEnabled, "启用业务用户组", constants.AuditRiskNormal, constants.AuditResourceBusinessUserGroup)
|
||||
businessUserGroupDisabled := businessUserGroupAction(constants.AuditActionBusinessUserGroupDisabled, "停用业务用户组", constants.AuditRiskNormal, constants.AuditResourceBusinessUserGroup)
|
||||
businessUserGroupDeleted := businessUserGroupAction(constants.AuditActionBusinessUserGroupDeleted, "删除业务用户组", constants.AuditRiskHigh, constants.AuditResourceBusinessUserGroup)
|
||||
businessUserGroupMembersUpdated := businessUserGroupAction(constants.AuditActionBusinessUserGroupMembersUpdated, "批量维护业务用户组成员", constants.AuditRiskNormal, constants.AuditResourceAccount)
|
||||
shopBusinessOwnerBatchUpdated := batchRootAction(constants.AuditActionShopBusinessOwnerBatchUpdated, "批量交接店铺负责人", constants.AuditResourceShopBusinessOwnerBatch)
|
||||
shopBusinessOwnerImported := taskAction(constants.AuditActionShopBusinessOwnerImported, "导入变更店铺负责人", constants.AuditResourceShop, constants.AuditActorSystemTask, constants.AuditSourceWorker)
|
||||
shopBusinessOwnerImportTaskCreated := taskAction(constants.AuditActionShopBusinessOwnerImportTaskCreated, "创建店铺负责人导入任务", constants.AuditResourceShopBusinessOwnerImportTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
|
||||
shopBusinessOwnerImportTaskCompleted := taskAction(constants.AuditActionShopBusinessOwnerImportTaskCompleted, "完成店铺负责人导入任务", constants.AuditResourceShopBusinessOwnerImportTask, constants.AuditActorSystemTask, constants.AuditSourceWorker)
|
||||
exportTaskCreated := taskAction(constants.AuditActionExportTaskCreated, "创建业务导出任务", constants.AuditResourceExportTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
|
||||
exportTaskCancelled := taskAction(constants.AuditActionExportTaskCancelled, "取消业务导出任务", constants.AuditResourceExportTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
|
||||
notificationDelivered := notificationAction(constants.AuditActionNotificationDelivered, "生成站内通知", constants.AuditResourceNotification, constants.AuditActorSystemTask, constants.AuditSourceWorker)
|
||||
@@ -554,6 +564,16 @@ func NewRegistry() *Registry {
|
||||
constants.AuditActionOrderPackageInvalidateTaskCreated: orderPackageInvalidateTaskCreated,
|
||||
constants.AuditActionOrderPackageInvalidateTaskCompleted: orderPackageInvalidateTaskCompleted,
|
||||
constants.AuditActionOrderPackageInvalidateItem: orderPackageInvalidateItem,
|
||||
constants.AuditActionBusinessUserGroupCreated: businessUserGroupCreated,
|
||||
constants.AuditActionBusinessUserGroupUpdated: businessUserGroupUpdated,
|
||||
constants.AuditActionBusinessUserGroupEnabled: businessUserGroupEnabled,
|
||||
constants.AuditActionBusinessUserGroupDisabled: businessUserGroupDisabled,
|
||||
constants.AuditActionBusinessUserGroupDeleted: businessUserGroupDeleted,
|
||||
constants.AuditActionBusinessUserGroupMembersUpdated: businessUserGroupMembersUpdated,
|
||||
constants.AuditActionShopBusinessOwnerBatchUpdated: shopBusinessOwnerBatchUpdated,
|
||||
constants.AuditActionShopBusinessOwnerImported: shopBusinessOwnerImported,
|
||||
constants.AuditActionShopBusinessOwnerImportTaskCreated: shopBusinessOwnerImportTaskCreated,
|
||||
constants.AuditActionShopBusinessOwnerImportTaskCompleted: shopBusinessOwnerImportTaskCompleted,
|
||||
constants.AuditActionExportTaskCreated: exportTaskCreated,
|
||||
constants.AuditActionExportTaskCancelled: exportTaskCancelled,
|
||||
constants.AuditActionNotificationDelivered: notificationDelivered,
|
||||
@@ -760,6 +780,18 @@ func NewRegistry() *Registry {
|
||||
Type: constants.AuditResourceOrderPackageInvalidateTask, Name: "订单套餐批量失效任务",
|
||||
IdentityFields: []string{"id", "task_no", "file_name"},
|
||||
},
|
||||
constants.AuditResourceShopBusinessOwnerImportTask: {
|
||||
Type: constants.AuditResourceShopBusinessOwnerImportTask, Name: "店铺负责人导入任务",
|
||||
IdentityFields: []string{"id", "task_no", "file_name"},
|
||||
},
|
||||
constants.AuditResourceBusinessUserGroup: {
|
||||
Type: constants.AuditResourceBusinessUserGroup, Name: "业务用户组",
|
||||
IdentityFields: []string{"id", "code", "name", "business_line", "status"},
|
||||
},
|
||||
constants.AuditResourceShopBusinessOwnerBatch: {
|
||||
Type: constants.AuditResourceShopBusinessOwnerBatch, Name: "店铺负责人批量交接批次",
|
||||
IdentityFields: []string{"batch_key", "operation", "shop_count"},
|
||||
},
|
||||
constants.AuditResourceExportTask: {
|
||||
Type: constants.AuditResourceExportTask, Name: "业务导出任务",
|
||||
IdentityFields: []string{"id", "task_no", "scene", "format", "creator_user_id", "creator_user_type", "creator_shop_id", "creator_enterprise_id", "scope_shop_ids"},
|
||||
@@ -1303,6 +1335,28 @@ func taskAction(code, name, primaryResource, actor, source string) ActionDefinit
|
||||
}
|
||||
}
|
||||
|
||||
// businessUserGroupAction 定义业务用户组维护动作;组只承载业务分类,主体不可见事件细节。
|
||||
func businessUserGroupAction(code, name, risk, primaryResource string) ActionDefinition {
|
||||
return ActionDefinition{
|
||||
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: risk,
|
||||
PrimaryResource: primaryResource, AllowedActor: constants.AuditActorAccount,
|
||||
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
|
||||
DefaultVisibility: constants.AuditSubjectInternalOnly,
|
||||
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
|
||||
}
|
||||
}
|
||||
|
||||
// batchRootAction 定义同步后台批次根动作;子事件自带店铺或资源作用域。
|
||||
func batchRootAction(code, name, primaryResource string) ActionDefinition {
|
||||
return ActionDefinition{
|
||||
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskNormal,
|
||||
PrimaryResource: primaryResource, AllowedActor: constants.AuditActorAccount,
|
||||
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
|
||||
DefaultVisibility: constants.AuditSubjectInternalOnly,
|
||||
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
|
||||
}
|
||||
}
|
||||
|
||||
func notificationAction(code, name, primaryResource, actor, source string) ActionDefinition {
|
||||
return ActionDefinition{
|
||||
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskLow,
|
||||
|
||||
Reference in New Issue
Block a user