feat: 按创建人隔离采集采购任务 (#127)
This commit is contained in:
+96
-19
@@ -113,7 +113,7 @@ func GetTask(db *sql.DB, taskID string) (*model.Task, error) {
|
||||
var t model.Task
|
||||
var assigned, claimedAt, sybID, orderNo, goodsID, skuID sql.NullString
|
||||
var pddGoodsID, pddOptions, resultData, errCode, errMsg, finishedAt sql.NullString
|
||||
var liveConfirmedBy, liveConfirmedAt sql.NullString
|
||||
var liveConfirmedBy, liveConfirmedAt, createdByUserID sql.NullString
|
||||
var quantity, maxPrice sql.NullInt64
|
||||
|
||||
err := db.QueryRow(`
|
||||
@@ -123,7 +123,7 @@ func GetTask(db *sql.DB, taskID string) (*model.Task, error) {
|
||||
pdd_goods_url, pdd_goods_id, pdd_options,
|
||||
quantity, max_price_cent,
|
||||
result_data, error_code, error_message, finished_at,
|
||||
live_confirmed_by, live_confirmed_at,
|
||||
live_confirmed_by, live_confirmed_at, created_by_user_id,
|
||||
created_at, updated_at
|
||||
FROM tasks WHERE task_id = ?`, taskID).Scan(
|
||||
&t.TaskID, &t.TaskType, &t.Status, &t.ExecutionMode, &t.Version, &t.Priority,
|
||||
@@ -132,7 +132,7 @@ func GetTask(db *sql.DB, taskID string) (*model.Task, error) {
|
||||
&t.PddGoodsURL, &pddGoodsID, &pddOptions,
|
||||
&quantity, &maxPrice,
|
||||
&resultData, &errCode, &errMsg, &finishedAt,
|
||||
&liveConfirmedBy, &liveConfirmedAt,
|
||||
&liveConfirmedBy, &liveConfirmedAt, &createdByUserID,
|
||||
&t.CreatedAt, &t.UpdatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
@@ -157,9 +157,37 @@ func GetTask(db *sql.DB, taskID string) (*model.Task, error) {
|
||||
t.FinishedAt = finishedAt.String
|
||||
t.LiveConfirmedBy = liveConfirmedBy.String
|
||||
t.LiveConfirmedAt = liveConfirmedAt.String
|
||||
t.CreatedByUserID = createdByUserID.String
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// TaskVisibleToUser 判断任务是否在采购员创建人范围内。visibleUserID 为空表示管理员。
|
||||
func TaskVisibleToUser(q Execer, taskID, visibleUserID string) (bool, error) {
|
||||
query := `SELECT COUNT(*) FROM tasks WHERE task_id = ?`
|
||||
args := []any{taskID}
|
||||
if visibleUserID != "" {
|
||||
query += ` AND created_by_user_id = ?`
|
||||
args = append(args, visibleUserID)
|
||||
}
|
||||
var count int
|
||||
if err := q.QueryRow(query, args...).Scan(&count); err != nil {
|
||||
return false, fmt.Errorf("检查任务可见范围失败: %w", err)
|
||||
}
|
||||
return count == 1, nil
|
||||
}
|
||||
|
||||
// GetTaskCreatorUsername 返回任务创建人的用户名;历史任务或账号不存在时为空。
|
||||
func GetTaskCreatorUsername(q Execer, taskID string) (string, error) {
|
||||
var username sql.NullString
|
||||
if err := q.QueryRow(`SELECT u.username FROM tasks t LEFT JOIN users u ON u.user_id=t.created_by_user_id WHERE t.task_id=?`, taskID).Scan(&username); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return "", nil
|
||||
}
|
||||
return "", fmt.Errorf("读取任务创建人失败: %w", err)
|
||||
}
|
||||
return username.String, nil
|
||||
}
|
||||
|
||||
// RecordClaim 记一笔"某客户端领过某任务"。
|
||||
//
|
||||
// 同一台客户端重复领同一个任务时只更新时间,不报错。
|
||||
@@ -278,16 +306,23 @@ type TaskFilter struct {
|
||||
Type model.TaskType
|
||||
Status model.TaskStatus
|
||||
Keyword string // 同时匹配任务编号、订单号、PDD 商品 ID
|
||||
// VisibleUserID 非空时强制只返回该创建人的任务,供采购员权限隔离。
|
||||
VisibleUserID string
|
||||
// CreatorUserID / CreatorHistory 只供管理员的创建人筛选使用。
|
||||
CreatorUserID string
|
||||
CreatorHistory bool
|
||||
}
|
||||
|
||||
// TaskListRow 是列表一行要用到的原始字段,还没翻成界面文字——
|
||||
// 那是 service 层的事(尤其是「目标」列的拼接,见 #19)。
|
||||
type TaskListRow struct {
|
||||
TaskID string
|
||||
TaskType model.TaskType
|
||||
Status model.TaskStatus
|
||||
ExecutionMode model.TaskExecutionMode
|
||||
AssignedClient string // 空表示无主任务
|
||||
TaskID string
|
||||
TaskType model.TaskType
|
||||
Status model.TaskStatus
|
||||
ExecutionMode model.TaskExecutionMode
|
||||
AssignedClient string // 空表示无主任务
|
||||
CreatedByUserID string
|
||||
CreatedByUsername string
|
||||
|
||||
OrderNo string
|
||||
PddGoodsID string
|
||||
@@ -324,6 +359,15 @@ func taskFilterClause(filter TaskFilter) (string, []any) {
|
||||
"(t.task_id LIKE ? ESCAPE '!' OR t.order_no LIKE ? ESCAPE '!' OR t.pdd_goods_id LIKE ? ESCAPE '!')")
|
||||
args = append(args, pattern, pattern, pattern)
|
||||
}
|
||||
if filter.VisibleUserID != "" {
|
||||
clauses = append(clauses, "t.created_by_user_id = ?")
|
||||
args = append(args, filter.VisibleUserID)
|
||||
} else if filter.CreatorHistory {
|
||||
clauses = append(clauses, "t.created_by_user_id IS NULL")
|
||||
} else if filter.CreatorUserID != "" {
|
||||
clauses = append(clauses, "t.created_by_user_id = ?")
|
||||
args = append(args, filter.CreatorUserID)
|
||||
}
|
||||
|
||||
if len(clauses) == 0 {
|
||||
return "", args
|
||||
@@ -343,10 +387,11 @@ func ListTasks(q Execer, filter TaskFilter, limit, offset int) ([]TaskListRow, e
|
||||
sqlText := `
|
||||
SELECT t.task_id, t.task_type, t.status, t.execution_mode, t.assigned_client,
|
||||
t.order_no, t.pdd_goods_id, t.pdd_options, t.quantity, t.max_price_cent,
|
||||
t.updated_at, p.title
|
||||
t.updated_at, p.title, t.created_by_user_id, u.username
|
||||
FROM tasks t
|
||||
LEFT JOIN pdd_products p
|
||||
ON p.goods_id = t.pdd_goods_id AND p.deleted_at IS NULL` +
|
||||
ON p.goods_id = t.pdd_goods_id AND p.deleted_at IS NULL
|
||||
LEFT JOIN users u ON u.user_id = t.created_by_user_id` +
|
||||
where + ` ORDER BY t.updated_at DESC, t.task_id DESC LIMIT ? OFFSET ?`
|
||||
args = append(args, limit, offset)
|
||||
|
||||
@@ -359,13 +404,13 @@ func ListTasks(q Execer, filter TaskFilter, limit, offset int) ([]TaskListRow, e
|
||||
list := make([]TaskListRow, 0, 16)
|
||||
for rows.Next() {
|
||||
var r TaskListRow
|
||||
var assigned, orderNo, pddGoodsID, pddOptions, title sql.NullString
|
||||
var assigned, orderNo, pddGoodsID, pddOptions, title, creatorID, creatorName sql.NullString
|
||||
var quantity, maxPrice sql.NullInt64
|
||||
|
||||
if err := rows.Scan(
|
||||
&r.TaskID, &r.TaskType, &r.Status, &r.ExecutionMode, &assigned,
|
||||
&orderNo, &pddGoodsID, &pddOptions, &quantity, &maxPrice,
|
||||
&r.UpdatedAt, &title,
|
||||
&r.UpdatedAt, &title, &creatorID, &creatorName,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("读取任务列表失败: %w", err)
|
||||
}
|
||||
@@ -377,6 +422,8 @@ func ListTasks(q Execer, filter TaskFilter, limit, offset int) ([]TaskListRow, e
|
||||
r.Quantity = int(quantity.Int64)
|
||||
r.MaxPriceCent = maxPrice.Int64
|
||||
r.PddTitle = title.String
|
||||
r.CreatedByUserID = creatorID.String
|
||||
r.CreatedByUsername = creatorName.String
|
||||
list = append(list, r)
|
||||
}
|
||||
return list, rows.Err()
|
||||
@@ -431,16 +478,44 @@ func DeleteTasks(q Execer, taskIDs []string) (int64, error) {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
// DeleteTasksInScope 原子删除指定权限范围内的任务。只要任一编号不存在或不在
|
||||
// 当前采购员范围内,受影响行数就不足,调用方必须回滚整个事务。
|
||||
func DeleteTasksInScope(q Execer, taskIDs []string, visibleUserID string) (int64, error) {
|
||||
if len(taskIDs) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(taskIDs)), ",")
|
||||
args := make([]any, 0, len(taskIDs)+1)
|
||||
for _, id := range taskIDs {
|
||||
args = append(args, id)
|
||||
}
|
||||
where := `task_id IN (` + placeholders + `)`
|
||||
if visibleUserID != "" {
|
||||
where += ` AND created_by_user_id = ?`
|
||||
args = append(args, visibleUserID)
|
||||
}
|
||||
res, err := q.Exec(`DELETE FROM tasks WHERE `+where, args...)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("批量删除任务失败: %w", err)
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
// InsertCollectTask 建一条不指定客户端的采集任务。
|
||||
// 保留这个入口给蝦皮、顺运宝等现有流程使用,避免它们被 PDD 页的新选项影响。
|
||||
func InsertCollectTask(q Execer, taskID, goodsID, goodsURL string) error {
|
||||
return InsertCollectTaskForClient(q, taskID, goodsID, goodsURL, "")
|
||||
return InsertCollectTaskForClientAndUser(q, taskID, goodsID, goodsURL, "", "")
|
||||
}
|
||||
|
||||
// InsertCollectTaskForClient 建一条采集任务。
|
||||
// assignedClient 为空时任务无主待领;有值时只等待指定客户端领取。
|
||||
// goodsURL 必填 —— Client 契约里 pdd_goods_url 是 NOT NULL。
|
||||
func InsertCollectTaskForClient(q Execer, taskID, goodsID, goodsURL, assignedClient string) error {
|
||||
return InsertCollectTaskForClientAndUser(q, taskID, goodsID, goodsURL, assignedClient, "")
|
||||
}
|
||||
|
||||
// InsertCollectTaskForClientAndUser 建一条带创建人审计的采集任务。
|
||||
func InsertCollectTaskForClientAndUser(q Execer, taskID, goodsID, goodsURL, assignedClient, createdByUserID string) error {
|
||||
if goodsID == "" || goodsURL == "" {
|
||||
return fmt.Errorf("采集任务的商品 ID 和链接都不能为空")
|
||||
}
|
||||
@@ -452,11 +527,12 @@ func InsertCollectTaskForClient(q Execer, taskID, goodsID, goodsURL, assignedCli
|
||||
assigned = sql.NullString{String: assignedClient, Valid: true}
|
||||
}
|
||||
now := model.NowISO()
|
||||
creator := sql.NullString{String: strings.TrimSpace(createdByUserID), Valid: strings.TrimSpace(createdByUserID) != ""}
|
||||
_, err := q.Exec(`
|
||||
INSERT INTO tasks (task_id, task_type, status, assigned_client,
|
||||
pdd_goods_url, pdd_goods_id, created_at, updated_at)
|
||||
VALUES (?, 'collect', ?, ?, ?, ?, ?, ?)`,
|
||||
taskID, status, assigned, goodsURL, goodsID, now, now)
|
||||
pdd_goods_url, pdd_goods_id, created_by_user_id, created_at, updated_at)
|
||||
VALUES (?, 'collect', ?, ?, ?, ?, ?, ?, ?)`,
|
||||
taskID, status, assigned, goodsURL, goodsID, creator, now, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建商品 %s 的采集任务失败: %w", goodsID, err)
|
||||
}
|
||||
@@ -505,12 +581,13 @@ func InsertPurchaseTask(q Execer, task model.Task) error {
|
||||
(task_id, task_type, status, execution_mode, assigned_client,
|
||||
syb_id, order_no, goods_id, shopee_sku_id,
|
||||
pdd_goods_url, pdd_goods_id, pdd_options,
|
||||
quantity, max_price_cent, live_confirmed_by, live_confirmed_at,
|
||||
quantity, max_price_cent, live_confirmed_by, live_confirmed_at, created_by_user_id,
|
||||
created_at, updated_at)
|
||||
VALUES (?, 'purchase', 'assigned', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
VALUES (?, 'purchase', 'assigned', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
task.TaskID, task.ExecutionMode, task.AssignedClient, task.SybID, task.OrderNo,
|
||||
task.GoodsID, task.ShopeeSKUID, task.PddGoodsURL, task.PddGoodsID,
|
||||
task.PddOptions, task.Quantity, task.MaxPriceCent, liveConfirmedBy, liveConfirmedAt, now, now)
|
||||
task.PddOptions, task.Quantity, task.MaxPriceCent, liveConfirmedBy, liveConfirmedAt,
|
||||
sql.NullString{String: task.CreatedByUserID, Valid: strings.TrimSpace(task.CreatedByUserID) != ""}, now, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建顺运宝明细 %s 的采购任务失败: %w", task.SybID, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user