f382c27200
后台新增「数据概览」(商品/合格/按状态/品牌/分类/待审核) 与「操作日志」(全局审计分页);商品列表支持多选批量改状态/分类。公开首页标题改为「天工」并展示合格档案数;新增公开接口 /api/v1/stats 与后台 /api/stats、/api/audit、/api/products/bulk。合格口径=quality_score≥0.6 且在用。 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
81 lines
2.2 KiB
Go
81 lines
2.2 KiB
Go
package adminstore
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
)
|
|
|
|
// Bulk-operation errors.
|
|
var (
|
|
// ErrNoTargets is returned when a bulk request selects no products.
|
|
ErrNoTargets = errors.New("no products selected")
|
|
// ErrInvalidStatus is returned for an unknown product status value.
|
|
ErrInvalidStatus = errors.New("invalid status")
|
|
)
|
|
|
|
var validStatus = map[string]bool{"active": true, "merged": true, "deprecated": true}
|
|
|
|
// BulkSetStatus updates the status of every selected product in one statement.
|
|
func (s *Store) BulkSetStatus(ctx context.Context, actor string, ids []string, status string) (int, error) {
|
|
if len(ids) == 0 {
|
|
return 0, ErrNoTargets
|
|
}
|
|
if !validStatus[status] {
|
|
return 0, ErrInvalidStatus
|
|
}
|
|
ct, err := s.pool.Exec(ctx,
|
|
"UPDATE product SET status = $1 WHERE id = ANY($2)", status, ids)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
n := int(ct.RowsAffected())
|
|
_ = s.writeAudit(ctx, actor, "bulk_status", "product", nil,
|
|
[]string{"status"}, map[string]any{"ids": ids}, map[string]any{"status": status})
|
|
return n, nil
|
|
}
|
|
|
|
// BulkSetCategory reassigns the category of every selected product, syncing the
|
|
// GPC brick code and recomputing quality for each one.
|
|
func (s *Store) BulkSetCategory(ctx context.Context, actor string, ids []string, categoryID *string) (int, error) {
|
|
if len(ids) == 0 {
|
|
return 0, ErrNoTargets
|
|
}
|
|
tx, err := s.pool.Begin(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
var gpc *string
|
|
if categoryID != nil && *categoryID != "" {
|
|
if err := tx.QueryRow(ctx, "SELECT gpc_brick_code FROM category WHERE id = $1", *categoryID).Scan(&gpc); err != nil {
|
|
return 0, ErrInvalidParent
|
|
}
|
|
} else {
|
|
categoryID = nil
|
|
}
|
|
|
|
ct, err := tx.Exec(ctx,
|
|
"UPDATE product SET category_id = $1, gpc_brick_code = $2 WHERE id = ANY($3)",
|
|
categoryID, gpc, ids)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
for _, id := range ids {
|
|
if _, err := s.recomputeQualityTx(ctx, tx, id); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return 0, err
|
|
}
|
|
n := int(ct.RowsAffected())
|
|
cat := ""
|
|
if categoryID != nil {
|
|
cat = *categoryID
|
|
}
|
|
_ = s.writeAudit(ctx, actor, "bulk_category", "product", nil,
|
|
[]string{"category"}, map[string]any{"ids": ids}, map[string]any{"category_id": cat})
|
|
return n, nil
|
|
}
|