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 验证证据链。
89 lines
4.2 KiB
Go
89 lines
4.2 KiB
Go
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})
|
|
}
|