feat: 按创建人隔离采集采购任务 (#127)
This commit is contained in:
+108
-1
@@ -239,9 +239,41 @@ type TaskView struct {
|
||||
StatusText string
|
||||
IsWarn bool // 失败 / 需人工,标黄提醒
|
||||
ClientText string
|
||||
CreatorText string
|
||||
UpdatedAt string
|
||||
}
|
||||
|
||||
const TaskCreatorHistoryValue = "__history__"
|
||||
|
||||
// TaskCreatorOption 是管理员创建人筛选的一项。
|
||||
type TaskCreatorOption struct {
|
||||
Value string
|
||||
Text string
|
||||
}
|
||||
|
||||
// TaskCreatorOptions 返回全部账号(含已禁用)以及存量历史任务选项。
|
||||
func TaskCreatorOptions(db *sql.DB, actor *model.User) ([]TaskCreatorOption, error) {
|
||||
if actor == nil {
|
||||
return nil, ErrUnauthenticated
|
||||
}
|
||||
if !actor.IsAdmin() {
|
||||
return nil, nil
|
||||
}
|
||||
users, err := repository.ListTaskCreatorUsers(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := []TaskCreatorOption{{Value: "", Text: "全部创建人"}, {Value: TaskCreatorHistoryValue, Text: "历史任务"}}
|
||||
for _, user := range users {
|
||||
text := user.Username
|
||||
if user.Status == model.UserDisabled {
|
||||
text += "(已禁用)"
|
||||
}
|
||||
options = append(options, TaskCreatorOption{Value: user.UserID, Text: text})
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// TaskListResult 是列表页要的全部数据。
|
||||
type TaskListResult struct {
|
||||
Rows []TaskView
|
||||
@@ -281,7 +313,7 @@ func ListTasksView(db *sql.DB, filter repository.TaskFilter, requestedPage int)
|
||||
Counts: counts,
|
||||
Total: total, Page: page, TotalPages: totalPages,
|
||||
IsFiltered: filter.Type != "" || filter.Status != "" ||
|
||||
strings.TrimSpace(filter.Keyword) != "",
|
||||
strings.TrimSpace(filter.Keyword) != "" || filter.CreatorUserID != "" || filter.CreatorHistory,
|
||||
}
|
||||
for _, r := range rows {
|
||||
result.Rows = append(result.Rows, TaskView{
|
||||
@@ -292,12 +324,40 @@ func ListTasksView(db *sql.DB, filter repository.TaskFilter, requestedPage int)
|
||||
StatusText: taskStatusText(r.Status),
|
||||
IsWarn: isTaskWarn(r.Status),
|
||||
ClientText: clientText(r.AssignedClient),
|
||||
CreatorText: taskCreatorText(r.CreatedByUsername),
|
||||
UpdatedAt: formatLocalTime(r.UpdatedAt),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func taskCreatorText(username string) string {
|
||||
if strings.TrimSpace(username) == "" {
|
||||
return "历史任务"
|
||||
}
|
||||
return username
|
||||
}
|
||||
|
||||
// ListTasksViewForUser 在服务端固定当前账号范围,忽略采购员伪造的创建人筛选。
|
||||
func ListTasksViewForUser(db *sql.DB, actor *model.User, filter repository.TaskFilter, creatorValue string, requestedPage int) (*TaskListResult, error) {
|
||||
visibleUserID, err := visibleClientUserID(actor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter.VisibleUserID = visibleUserID
|
||||
filter.CreatorUserID = ""
|
||||
filter.CreatorHistory = false
|
||||
if actor.IsAdmin() {
|
||||
creatorValue = strings.TrimSpace(creatorValue)
|
||||
if creatorValue == TaskCreatorHistoryValue {
|
||||
filter.CreatorHistory = true
|
||||
} else if creatorValue != "" {
|
||||
filter.CreatorUserID = creatorValue
|
||||
}
|
||||
}
|
||||
return ListTasksView(db, filter, requestedPage)
|
||||
}
|
||||
|
||||
// StatusLine 拼底部状态条,形如:
|
||||
//
|
||||
// 共 42 条 · 待分配 3 · 待领取 5 · 已领取 2 · 成功 30 · 需人工 1 · 失败 1 · 已取消 0
|
||||
@@ -317,6 +377,34 @@ func DeleteTasks(db *sql.DB, taskIDs []string) (int64, error) {
|
||||
return repository.DeleteTasks(db, dedupe(taskIDs))
|
||||
}
|
||||
|
||||
// DeleteTasksForUser 保证整批删除要么全部在当前范围内成功,要么全部回滚。
|
||||
func DeleteTasksForUser(db *sql.DB, actor *model.User, taskIDs []string) (int64, error) {
|
||||
visibleUserID, err := visibleClientUserID(actor)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ids := dedupe(taskIDs)
|
||||
if len(ids) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("开始删除任务事务失败: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
n, err := repository.DeleteTasksInScope(tx, ids, visibleUserID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n != int64(len(ids)) {
|
||||
return 0, ErrTaskNotVisible
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return 0, fmt.Errorf("提交删除任务事务失败: %w", err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ---------- 详情弹窗 ----------
|
||||
|
||||
// resultDataLimit 是弹窗里展开显示 result_data 的最大字符数。
|
||||
@@ -333,6 +421,7 @@ type TaskDetailView struct {
|
||||
ExecutionModeText string
|
||||
StatusText string
|
||||
ClientText string
|
||||
CreatorText string
|
||||
ClaimedAt string
|
||||
FinishedAt string
|
||||
|
||||
@@ -369,6 +458,10 @@ func GetTaskDetail(db *sql.DB, taskID string) (*TaskDetailView, error) {
|
||||
if err != nil || t == nil {
|
||||
return nil, err
|
||||
}
|
||||
creatorUsername, err := repository.GetTaskCreatorUsername(db, taskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &TaskDetailView{
|
||||
TaskID: t.TaskID,
|
||||
@@ -376,6 +469,7 @@ func GetTaskDetail(db *sql.DB, taskID string) (*TaskDetailView, error) {
|
||||
ExecutionModeText: taskExecutionModeText(t.TaskType, t.ExecutionMode),
|
||||
StatusText: taskStatusText(t.Status),
|
||||
ClientText: clientText(t.AssignedClient),
|
||||
CreatorText: taskCreatorText(creatorUsername),
|
||||
ClaimedAt: formatLocalTime(t.ClaimedAt),
|
||||
FinishedAt: formatLocalTime(t.FinishedAt),
|
||||
PddGoodsURL: t.PddGoodsURL,
|
||||
@@ -401,6 +495,19 @@ func GetTaskDetail(db *sql.DB, taskID string) (*TaskDetailView, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// GetTaskDetailForUser 对采购员隐藏其他人和历史任务,统一表现为不存在。
|
||||
func GetTaskDetailForUser(db *sql.DB, actor *model.User, taskID string) (*TaskDetailView, error) {
|
||||
visibleUserID, err := visibleClientUserID(actor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
visible, err := repository.TaskVisibleToUser(db, taskID, visibleUserID)
|
||||
if err != nil || !visible {
|
||||
return nil, err
|
||||
}
|
||||
return GetTaskDetail(db, taskID)
|
||||
}
|
||||
|
||||
// purchaseResult 只声明详情页需要展示的字段,避免把 Client 的完整结果结构
|
||||
// 复制到 Admin。其余原始字段仍保存在 result_data 并可在详情底部展开查看。
|
||||
type purchaseResult struct {
|
||||
|
||||
Reference in New Issue
Block a user