feat(业务用户组): AUG26-003 业务用户组与店铺负责人分组导入
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:
2026-09-14 16:51:44 +08:00
parent 957a235585
commit c7f9e005af
56 changed files with 4166 additions and 344 deletions

View File

@@ -0,0 +1,40 @@
package model
import (
"gorm.io/gorm"
)
// BusinessUserGroup 业务用户组:平台用户的业务分类。
// 只用于店铺负责人的分组推导、筛选与批量维护,不进入登录、角色权限或数据范围判定;
// 不设上级组、层级与组管理员,所属组始终由店铺当前负责人实时推导。
type BusinessUserGroup struct {
gorm.Model
BaseModel `gorm:"embedded"`
// Code 是创建时必填的稳定编码,未删除组内唯一,创建后不可修改。
Code string `gorm:"column:code;type:varchar(64);not null;uniqueIndex:uk_business_user_group_code,where:deleted_at IS NULL;comment:创建时必填的稳定编码,创建后不可修改" json:"code"`
Name string `gorm:"column:name;type:varchar(100);not null;comment:用户组名称" json:"name"`
// BusinessLine 是三枚举单值,空字符串表示未设置业务线。
BusinessLine string `gorm:"column:business_line;type:varchar(20);not null;default:'';comment:所属业务线单值 standard/smart/other空值未设置" json:"business_line"`
SortOrder int64 `gorm:"column:sort_order;type:bigint;not null;default:0;comment:排序值,非负整数" json:"sort_order"`
Status int `gorm:"column:status;type:smallint;not null;default:1;comment:状态 0=禁用 1=启用" json:"status"`
Remark string `gorm:"column:remark;type:varchar(500);not null;default:'';comment:备注" json:"remark"`
}
// TableName 指定业务用户组表名。
func (BusinessUserGroup) TableName() string {
return "tb_business_user_group"
}
// BusinessUserGroupMember 平台用户与业务用户组的唯一归属关系。
// 一个账号至多一条未删除记录;改组直接更新组 ID账号软删后关系保留并继续参与店铺推导。
type BusinessUserGroupMember struct {
gorm.Model
BaseModel `gorm:"embedded"`
BusinessUserGroupID uint `gorm:"column:business_user_group_id;not null;index:idx_business_user_group_member_group,where:deleted_at IS NULL;comment:所属业务用户组ID" json:"business_user_group_id"`
AccountID uint `gorm:"column:account_id;not null;uniqueIndex:uk_business_user_group_member_account,where:deleted_at IS NULL;comment:平台用户账号ID" json:"account_id"`
}
// TableName 指定平台用户分组关系表名。
func (BusinessUserGroupMember) TableName() string {
return "tb_business_user_group_member"
}

View File

@@ -0,0 +1,196 @@
package dto
import "github.com/bytedance/sonic"
// 业务用户组 DTO。
// 枚举说明enabled 与 business_line 取值必须与 pkg/constants 的
// StatusDisabled/StatusEnabled 与 BusinessLineStandard/BusinessLineSmart/BusinessLineOther 保持一致。
// CreateBusinessUserGroupRequest 创建业务用户组请求。
type CreateBusinessUserGroupRequest struct {
Code string `json:"code" validate:"required,min=1,max=64" required:"true" minLength:"1" maxLength:"64" description:"业务用户组稳定编码1-64 字符,未删除组内唯一,创建后不可修改"`
Name string `json:"name" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"业务用户组名称1-100 字符"`
BusinessLine string `json:"business_line" validate:"omitempty,oneof=standard smart other" enum:"standard,smart,other" description:"所属业务线 (standard:标品, smart:智能产品, other:其他),留空表示不设置"`
Sort *int64 `json:"sort" validate:"omitempty,min=0" minimum:"0" description:"排序值,非负整数,默认 0"`
Enabled *bool `json:"enabled" description:"是否启用,默认启用;停用后不得新增成员,也不得作为批量目标"`
Remark string `json:"remark" validate:"omitempty,max=500" maxLength:"500" description:"备注,最多 500 字符"`
}
// UpdateBusinessUserGroupRequest 更新业务用户组请求。
// 仅传入的字段被修改;稳定编码永不允许修改,本请求不提供该字段。
// business_line 传显式空字符串表示清空业务线。
type UpdateBusinessUserGroupRequest struct {
Name *string `json:"name" validate:"omitempty,min=1,max=100" minLength:"1" maxLength:"100" description:"业务用户组名称1-100 字符"`
BusinessLine *string `json:"business_line" enum:"standard,smart,other" description:"所属业务线 (standard:标品, smart:智能产品, other:其他);字段缺失不修改,传空字符串清空;取值白名单在服务层校验"`
Sort *int64 `json:"sort" validate:"omitempty,min=0" minimum:"0" description:"排序值,非负整数"`
Enabled *bool `json:"enabled" description:"是否启用;停用后不得新增成员,已有成员关系保留并显示已停用"`
Remark *string `json:"remark" validate:"omitempty,max=500" maxLength:"500" description:"备注,最多 500 字符"`
}
// BusinessUserGroupListRequest 查询业务用户组列表请求。
type BusinessUserGroupListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码,默认 1"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量,默认 20最大 100"`
Enabled *bool `json:"enabled" query:"enabled" description:"按启用状态过滤;不传返回全部"`
Keyword string `json:"keyword" query:"keyword" validate:"omitempty,max=100" maxLength:"100" description:"按稳定编码或名称模糊搜索,最多 100 字符"`
BusinessLine *string `json:"business_line" query:"business_line" validate:"omitempty,oneof=standard smart other" enum:"standard,smart,other" description:"按所属业务线过滤 (standard:标品, smart:智能产品, other:其他)"`
}
// BusinessUserGroupResponse 业务用户组响应。
type BusinessUserGroupResponse struct {
ID uint `json:"id" description:"业务用户组ID"`
Code string `json:"code" description:"业务用户组稳定编码,创建后不可修改"`
Name string `json:"name" description:"业务用户组名称"`
BusinessLine string `json:"business_line" description:"所属业务线 (standard:标品, smart:智能产品, other:其他),空字符串表示未设置"`
BusinessLineName string `json:"business_line_name" description:"所属业务线中文名称,未设置时为空字符串"`
Sort int64 `json:"sort" description:"排序值"`
Enabled bool `json:"enabled" description:"是否启用;停用后不得新增成员,也不得作为批量目标"`
Remark string `json:"remark" description:"备注"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
// BusinessUserGroupPageResult 业务用户组分页响应。
type BusinessUserGroupPageResult struct {
Items []*BusinessUserGroupResponse `json:"items" description:"业务用户组列表"`
Total int64 `json:"total" description:"总记录数"`
Page int `json:"page" description:"当前页码"`
Size int `json:"size" description:"每页数量"`
}
// SetBusinessUserGroupMembersRequest 批量设置平台用户业务用户组归属请求。
// 设置直接替换每个账号的原归属;任一账号不是启用平台用户或目标组未启用时整批不修改。
type SetBusinessUserGroupMembersRequest struct {
AccountIDs []uint `json:"account_ids" validate:"required,min=1,dive,min=1" required:"true" description:"平台用户账号ID列表至少一个每个账号必须是启用平台用户"`
}
// ClearBusinessUserGroupMembersRequest 批量清空平台用户业务用户组归属请求。
type ClearBusinessUserGroupMembersRequest struct {
AccountIDs []uint `json:"account_ids" validate:"required,min=1,dive,min=1" required:"true" description:"平台用户账号ID列表至少一个每个账号必须是启用平台用户"`
}
// BusinessUserGroupMemberRequest 业务用户组成员维护路径参数。
type BusinessUserGroupMemberRequest struct {
ID uint `path:"id" required:"true" description:"业务用户组ID"`
}
// BusinessUserGroupDeleteRequest 业务用户组删除二次确认请求。
type BusinessUserGroupDeleteRequest struct {
Confirm bool `json:"confirm" validate:"required" required:"true" description:"确认删除,必须为 true仅无成员的用户组可删除有成员时只能停用或先移走成员"`
}
// BusinessUserGroupUpdateParams 更新业务用户组参数(路径参数 + 请求体,用于文档生成)。
type BusinessUserGroupUpdateParams struct {
IDReq
UpdateBusinessUserGroupRequest
}
// BusinessUserGroupDeleteParams 删除业务用户组参数(路径参数 + 二次确认,用于文档生成)。
type BusinessUserGroupDeleteParams struct {
IDReq
BusinessUserGroupDeleteRequest
}
// ClearBusinessUserGroupMembersParams 清空平台用户分组归属请求(用于 DELETE 显式请求体)。
type ClearBusinessUserGroupMembersParams struct {
ClearBusinessUserGroupMembersRequest
}
// BusinessUserGroupMembersResult 成员维护结果。
type BusinessUserGroupMembersResult struct {
GroupID uint `json:"group_id" description:"目标业务用户组ID清空操作为 0"`
AccountIDs []uint `json:"account_ids" description:"本次成功维护归属的平台用户账号ID列表"`
}
// BatchUpdateShopBusinessOwnerRequest 勾选店铺批量设置或清空负责人请求。
// business_owner_account_id 缺失时请求非法;显式 null 表示清空负责人。
type BatchUpdateShopBusinessOwnerRequest struct {
ShopIDs []uint `json:"shop_ids" validate:"required,min=1,max=500,dive,min=1" required:"true" minItems:"1" maxItems:"500" description:"店铺ID列表至少一个且去重最多 500 个"`
BusinessOwnerAccountID *uint `json:"business_owner_account_id" nullable:"true" description:"目标平台业务员账号ID显式 null 表示清空负责人"`
BusinessOwnerAccountIDSet bool `json:"-"`
}
// UnmarshalJSON 解析批量交接请求并保留负责人字段「缺失」和「显式 null」的差异。
func (r *BatchUpdateShopBusinessOwnerRequest) UnmarshalJSON(data []byte) error {
type plain BatchUpdateShopBusinessOwnerRequest
var decoded plain
if err := sonic.Unmarshal(data, &decoded); err != nil {
return err
}
var fields map[string]any
if err := sonic.Unmarshal(data, &fields); err != nil {
return err
}
decoded.BusinessOwnerAccountIDSet = false
if _, exists := fields["business_owner_account_id"]; exists {
decoded.BusinessOwnerAccountIDSet = true
}
*r = BatchUpdateShopBusinessOwnerRequest(decoded)
return nil
}
// BatchUpdateShopBusinessOwnerResult 勾选店铺批量交接结果。
type BatchUpdateShopBusinessOwnerResult struct {
BatchKey string `json:"batch_key" description:"批次标识,可用于审计关联时间线"`
ShopCount int `json:"shop_count" description:"本次成功变更的店铺数量"`
Cleared bool `json:"cleared" description:"本次是否为清空负责人操作"`
BusinessOwnerAccountID *uint `json:"business_owner_account_id" description:"目标平台业务员账号ID清空时为 null"`
}
// CreateShopBusinessOwnerImportRequest 创建店铺负责人 CSV 导入任务请求。
type CreateShopBusinessOwnerImportRequest struct {
FileKey string `json:"file_key" validate:"required,min=1,max=500" required:"true" minLength:"1" maxLength:"500" description:"CSV 对象存储 Key必须以 shop-imports/ 开头且扩展名为 .csv通过 POST /api/admin/storage/upload-url 获取)"`
}
// ListShopBusinessOwnerImportRequest 查询店铺负责人导入任务列表请求。
type ListShopBusinessOwnerImportRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码,默认 1"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量,默认 20最大 100"`
Status *int `json:"status" query:"status" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:失败)"`
}
// GetShopBusinessOwnerImportRequest 店铺负责人导入任务详情路径参数。
type GetShopBusinessOwnerImportRequest struct {
ID uint `path:"id" required:"true" description:"导入任务ID"`
}
// ShopBusinessOwnerImportTaskResponse 店铺负责人导入任务响应。
type ShopBusinessOwnerImportTaskResponse struct {
ID uint `json:"id" description:"导入任务ID"`
TaskNo string `json:"task_no" description:"导入任务编号"`
FileName string `json:"file_name" description:"上传的源 CSV 文件名"`
Status int `json:"status" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:失败)"`
StatusName string `json:"status_name" description:"任务状态中文名称"`
TotalCount int `json:"total_count" description:"数据行总数;任务级失败时为 0"`
SuccessCount int `json:"success_count" description:"成功行数"`
FailCount int `json:"fail_count" description:"失败行数"`
ErrorMessage string `json:"error_message" description:"任务级失败原因;与行级失败原因分开记录"`
CreatorName string `json:"creator_name" description:"任务创建人名称快照"`
CreatedAt string `json:"created_at" description:"创建时间"`
StartedAt string `json:"started_at" description:"开始处理时间"`
CompletedAt string `json:"completed_at" description:"完成时间"`
}
// ShopBusinessOwnerImportTaskPageResult 店铺负责人导入任务分页响应。
type ShopBusinessOwnerImportTaskPageResult struct {
Items []*ShopBusinessOwnerImportTaskResponse `json:"items" description:"导入任务列表"`
Total int64 `json:"total" description:"总记录数"`
Page int `json:"page" description:"当前页码"`
Size int `json:"size" description:"每页数量"`
}
// ShopBusinessOwnerImportItemResponse 店铺负责人导入逐行结果。
type ShopBusinessOwnerImportItemResponse struct {
Line int `json:"line" description:"行号,自数据首行起计(表头不计入)"`
ShopCode string `json:"shop_code" description:"店铺编码"`
OperationType string `json:"operation_type" description:"操作类型 (换绑/清空)"`
Status int `json:"status" description:"行状态 (3:成功, 4:失败)"`
StatusName string `json:"status_name" description:"行状态中文名称"`
Reason string `json:"reason" description:"失败原因;成功行为空"`
}
// ShopBusinessOwnerImportTaskDetailResponse 店铺负责人导入任务详情响应。
type ShopBusinessOwnerImportTaskDetailResponse struct {
ShopBusinessOwnerImportTaskResponse
Items []ShopBusinessOwnerImportItemResponse `json:"items" description:"逐行结果明细;任务级失败时为空数组"`
}

View File

@@ -3,15 +3,18 @@ package dto
import "github.com/bytedance/sonic"
type ShopListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
ShopName string `json:"shop_name" query:"shop_name" validate:"omitempty,max=100" maxLength:"100" description:"店铺名称模糊查询"`
ShopCode string `json:"shop_code" query:"shop_code" validate:"omitempty,max=50" maxLength:"50" description:"店铺编号精确查询"`
ContactPhone string `json:"contact_phone" query:"contact_phone" validate:"omitempty,len=11,numeric,ascii" minLength:"11" maxLength:"11" pattern:"^[0-9]{11}$" description:"联系电话精确查询11位 ASCII 数字;空值不启用筛选;与其他条件按 AND 组合)"`
BusinessOwnerAccountID *uint `json:"business_owner_account_id" query:"business_owner_account_id" validate:"omitempty,min=1" minimum:"1" description:"平台业务员账号ID精确筛选"`
ParentID *uint `json:"parent_id" query:"parent_id" validate:"omitempty,min=1" minimum:"1" description:"上级店铺ID"`
Level *int `json:"level" query:"level" validate:"omitempty,min=1,max=7" minimum:"1" maximum:"7" description:"店铺层级 (1-7级)"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=0 1" description:"状态 (0:禁用, 1:启用)"`
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
ShopName string `json:"shop_name" query:"shop_name" validate:"omitempty,max=100" maxLength:"100" description:"店铺名称模糊查询"`
ShopCode string `json:"shop_code" query:"shop_code" validate:"omitempty,max=50" maxLength:"50" description:"店铺编号精确查询"`
ContactPhone string `json:"contact_phone" query:"contact_phone" validate:"omitempty,len=11,numeric,ascii" minLength:"11" maxLength:"11" pattern:"^[0-9]{11}$" description:"联系电话精确查询11位 ASCII 数字;空值不启用筛选;与其他条件按 AND 组合)"`
BusinessOwnerAccountID *uint `json:"business_owner_account_id" query:"business_owner_account_id" validate:"omitempty,min=1" minimum:"1" description:"平台业务员账号ID精确筛选"`
ParentID *uint `json:"parent_id" query:"parent_id" validate:"omitempty,min=1" minimum:"1" description:"上级店铺ID"`
Level *int `json:"level" query:"level" validate:"omitempty,min=1,max=7" minimum:"1" maximum:"7" description:"店铺层级 (1-7级)"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=0 1" description:"状态 (0:禁用, 1:启用)"`
BusinessUserGroupID *uint `json:"business_user_group_id" query:"business_user_group_id" validate:"omitempty,min=1" minimum:"1" description:"按业务用户组筛选,只匹配店铺当前负责人所属组;停用组同样可被筛出并携带已停用标记"`
BusinessLine *string `json:"business_line" query:"business_line" validate:"omitempty,oneof=standard smart other" enum:"standard,smart,other" description:"按负责人所属业务用户组的业务线筛选 (standard:标品, smart:智能产品, other:其他)"`
Ungrouped *bool `json:"ungrouped" query:"ungrouped" description:"按未分组筛选true 只返回无负责人或负责人无成员关系的店铺;负责人属于停用组的店铺不计入未分组"`
}
type CreateShopRequest struct {
@@ -98,18 +101,24 @@ type ShopResponse struct {
BusinessOwnerUsername string `json:"business_owner_username" description:"平台业务员账号名"`
BusinessOwnerPhoneSummary string `json:"business_owner_phone_summary" description:"平台业务员手机号摘要(前三后四)"`
BusinessOwnerAvailable bool `json:"business_owner_available" description:"平台业务员当前是否可用于通知接收"`
Level int `json:"level" description:"店铺层级 (1-7级)"`
ContactName string `json:"contact_name" description:"联系人姓名"`
ContactPhone string `json:"contact_phone" description:"联系人电话"`
Province string `json:"province" description:"省份"`
City string `json:"city" description:"城市"`
District string `json:"district" description:"区县"`
Address string `json:"address" description:"详细地址"`
Status int `json:"status" description:"状态 (0:禁用, 1:启用)"`
StatusName string `json:"status_name" description:"状态名称(中文)"`
ClientLoginDisabled bool `json:"client_login_disabled" description:"是否禁止该店铺资产发起新的 C 端登录"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
// 店铺负责人业务用户组由当前负责人实时推导,店铺不保存组字段。
BusinessUserGroupID *uint `json:"business_user_group_id" description:"负责人当前所属业务用户组IDnull 表示未分组"`
BusinessUserGroupCode string `json:"business_user_group_code" description:"业务用户组稳定编码"`
BusinessUserGroupName string `json:"business_user_group_name" description:"业务用户组名称"`
BusinessUserGroupEnabled bool `json:"business_user_group_enabled" description:"业务用户组是否启用false 且组ID非空表示负责人属于已停用组"`
BusinessUserGroupBusinessLine string `json:"business_user_group_business_line" description:"业务用户组所属业务线 (standard:标品, smart:智能产品, other:其他),空字符串表示未设置"`
Level int `json:"level" description:"店铺层级 (1-7级)"`
ContactName string `json:"contact_name" description:"联系人姓名"`
ContactPhone string `json:"contact_phone" description:"联系人电话"`
Province string `json:"province" description:"省份"`
City string `json:"city" description:"城市"`
District string `json:"district" description:"区县"`
Address string `json:"address" description:"详细地址"`
Status int `json:"status" description:"状态 (0:禁用, 1:启用)"`
StatusName string `json:"status_name" description:"状态名称(中文)"`
ClientLoginDisabled bool `json:"client_login_disabled" description:"是否禁止该店铺资产发起新的 C 端登录"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
// ShopPageResult 店铺分页响应

View File

@@ -3,7 +3,7 @@ package dto
type GetUploadURLRequest struct {
FileName string `json:"file_name" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"文件名cards.csv"`
ContentType string `json:"content_type" validate:"omitempty,max=100" maxLength:"100" description:"文件 MIME 类型text/csv留空则自动推断"`
Purpose string `json:"purpose" validate:"required,oneof=iot_import export attachment batch_purchase device_batch_allocation" required:"true" enum:"iot_import,export,attachment,batch_purchase,device_batch_allocation" description:"文件用途 (iot_import:ICCID导入, export:数据导出, attachment:附件, batch_purchase:资产套餐批量订购CSV, device_batch_allocation:设备批量分配或回收CSV)"`
Purpose string `json:"purpose" validate:"required,oneof=iot_import export attachment batch_purchase device_batch_allocation shop_import" required:"true" enum:"iot_import,export,attachment,batch_purchase,device_batch_allocation,shop_import" description:"文件用途 (iot_import:ICCID导入, export:数据导出, attachment:附件, batch_purchase:资产套餐批量订购CSV, device_batch_allocation:设备批量分配或回收CSV, shop_import:店铺负责人导入CSV)"`
}
type GetUploadURLResponse struct {

View File

@@ -102,3 +102,19 @@ const (
ImportTaskStatusCompleted = 3
ImportTaskStatusFailed = 4
)
// ImportTaskStatusName 返回既有导入任务状态机的中文名称,供导入场景统一投影。
func ImportTaskStatusName(status int) string {
switch status {
case ImportTaskStatusPending:
return "待处理"
case ImportTaskStatusProcessing:
return "处理中"
case ImportTaskStatusCompleted:
return "已完成"
case ImportTaskStatusFailed:
return "失败"
default:
return "未知"
}
}

View File

@@ -0,0 +1,67 @@
package model
import (
"database/sql/driver"
"time"
"github.com/bytedance/sonic"
"gorm.io/gorm"
)
// ShopBusinessOwnerImportTask 店铺负责人 CSV 导入任务。
// 独立成表以复用统一的导入状态机,不与设备导入任务共享表与启动补偿口径;
// 逐行独立事务,成功行提交、失败行保留原值,任务级失败与行级失败分开记录。
type ShopBusinessOwnerImportTask struct {
gorm.Model
BaseModel `gorm:"embedded"`
TaskNo string `gorm:"column:task_no;type:varchar(50);not null;uniqueIndex:uq_shop_business_owner_import_task_no,where:deleted_at IS NULL;comment:任务编号" json:"task_no"`
FileName string `gorm:"column:file_name;type:varchar(255);not null;default:'';comment:上传的源CSV文件名" json:"file_name"`
StorageKey string `gorm:"column:storage_key;type:varchar(500);not null;comment:源CSV对象存储Key" json:"storage_key"`
Status int `gorm:"column:status;type:int;not null;default:1;comment:任务状态 1-待处理 2-处理中 3-已完成 4-失败" json:"status"`
TotalCount int `gorm:"column:total_count;not null;default:0;comment:任务数据行总数" json:"total_count"`
SuccessCount int `gorm:"column:success_count;not null;default:0;comment:处理成功行数" json:"success_count"`
FailCount int `gorm:"column:fail_count;not null;default:0;comment:处理失败行数" json:"fail_count"`
ResultItems ShopBusinessOwnerImportResults `gorm:"column:result_items;type:jsonb;not null;default:'[]';comment:逐行结果明细" json:"result_items"`
ErrorMessage string `gorm:"column:error_message;type:text;not null;default:'';comment:任务级失败原因" json:"error_message"`
CreatorName string `gorm:"column:creator_name;type:varchar(100);not null;default:'';comment:任务创建人名称快照" json:"creator_name"`
StartedAt *time.Time `gorm:"column:started_at;comment:任务开始处理时间" json:"started_at"`
CompletedAt *time.Time `gorm:"column:completed_at;comment:任务处理完成时间" json:"completed_at"`
}
// TableName 指定店铺负责人导入任务表名。
func (ShopBusinessOwnerImportTask) TableName() string {
return "tb_shop_business_owner_import_task"
}
// ShopBusinessOwnerImportResultItem 导入单行结果;行号自数据首行起计,表头不计入。
type ShopBusinessOwnerImportResultItem struct {
Line int `json:"line"`
ShopCode string `json:"shop_code"`
OperationType string `json:"operation_type"`
Status int `json:"status"`
Reason string `json:"reason,omitempty"`
}
// ShopBusinessOwnerImportResults 导入逐行结果集合。
type ShopBusinessOwnerImportResults []ShopBusinessOwnerImportResultItem
// Value 将逐行结果序列化为 JSONB。
func (items ShopBusinessOwnerImportResults) Value() (driver.Value, error) {
if items == nil {
return "[]", nil
}
return sonic.Marshal(items)
}
// Scan 从 JSONB 读取逐行结果。
func (items *ShopBusinessOwnerImportResults) Scan(value any) error {
if value == nil {
*items = ShopBusinessOwnerImportResults{}
return nil
}
data, ok := value.([]byte)
if !ok {
return nil
}
return sonic.Unmarshal(data, items)
}