feat: 增加客户端采购员归属管理 (#54)
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"cmautobuy/admin/config"
|
||||
"cmautobuy/admin/model"
|
||||
"cmautobuy/admin/repository"
|
||||
"cmautobuy/admin/service"
|
||||
"cmautobuy/admin/syb"
|
||||
@@ -535,8 +536,9 @@ func (h *Handler) taskRedirect(c *gin.Context, msg string) {
|
||||
// (没有心跳是有意的,见 docs/admin/04-client-api.md §3)。
|
||||
func (h *Handler) ClientList(c *gin.Context) {
|
||||
keyword := c.Query("name")
|
||||
actor := currentUser(c)
|
||||
|
||||
views, err := service.ListClientViews(h.db, keyword, h.onlineThreshold)
|
||||
views, err := service.ListClientViewsForUser(h.db, actor, keyword, h.onlineThreshold)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError,
|
||||
"读取客户端列表失败,数据没有被改动。请稍后重试,或查看 data/logs/ 里的日志。")
|
||||
@@ -552,16 +554,85 @@ func (h *Handler) ClientList(c *gin.Context) {
|
||||
status := fmt.Sprintf("共 %d 台客户端 · 在线 %d · 离线 %d",
|
||||
len(views), online, len(views)-online)
|
||||
if len(views) == 0 {
|
||||
status = "还没有客户端。客户端第一次调用领取接口时会自动登记。"
|
||||
if actor != nil && actor.IsAdmin() {
|
||||
status = "还没有客户端。客户端第一次调用领取接口时会自动登记。"
|
||||
} else {
|
||||
status = "当前没有绑定给你的客户端,请联系管理员。"
|
||||
}
|
||||
}
|
||||
|
||||
purchasers := []model.User{}
|
||||
if actor != nil && actor.IsAdmin() {
|
||||
purchasers, err = service.ListActivePurchasers(h.db, actor)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError,
|
||||
"读取可绑定采购员失败,数据没有被改动。请稍后重试。")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "client/list", page(c, "clients", "客户端列表", gin.H{
|
||||
"Keyword": keyword,
|
||||
"Rows": views,
|
||||
"Status": status,
|
||||
"Keyword": keyword, "Rows": views, "Status": status,
|
||||
"Purchasers": purchasers, "Message": c.Query("msg"), "Error": c.Query("error"),
|
||||
}))
|
||||
}
|
||||
|
||||
// ClientAssign 由管理员绑定或转交客户端。只改归属历史,不改 tasks。
|
||||
func (h *Handler) ClientAssign(c *gin.Context) {
|
||||
changed, transferred, err := service.AssignClient(h.db, currentUser(c),
|
||||
c.PostForm("client_id"), c.PostForm("user_id"), time.Now())
|
||||
if err != nil {
|
||||
h.clientActionFailure(c, err, "绑定客户端失败")
|
||||
return
|
||||
}
|
||||
message := "客户端已经绑定给该采购员,无需重复操作"
|
||||
if changed {
|
||||
message = "客户端绑定成功"
|
||||
}
|
||||
if transferred {
|
||||
message = "客户端已转交,新负责人立即生效;既有任务保持不变"
|
||||
}
|
||||
log.Printf("client_assignment_changed actor=%s client_id=%s user_id=%s transferred=%t changed=%t",
|
||||
currentUser(c).Username, c.PostForm("client_id"), c.PostForm("user_id"), transferred, changed)
|
||||
redirectClients(c, message, "")
|
||||
}
|
||||
|
||||
// ClientUnassign 由管理员解绑客户端。前端确认用于防误触,服务端权限仍是硬边界。
|
||||
func (h *Handler) ClientUnassign(c *gin.Context) {
|
||||
err := service.UnassignClient(h.db, currentUser(c), c.PostForm("client_id"), time.Now())
|
||||
if err != nil {
|
||||
h.clientActionFailure(c, err, "解绑客户端失败")
|
||||
return
|
||||
}
|
||||
log.Printf("client_assignment_ended actor=%s client_id=%s",
|
||||
currentUser(c).Username, c.PostForm("client_id"))
|
||||
redirectClients(c, "客户端已解绑;归属历史和既有任务均已保留", "")
|
||||
}
|
||||
|
||||
func (h *Handler) clientActionFailure(c *gin.Context, err error, fallback string) {
|
||||
if service.IsValidationError(err) || errors.Is(err, repository.ErrClientNotFound) ||
|
||||
errors.Is(err, repository.ErrPurchaserNotActive) || errors.Is(err, repository.ErrClientNotAssigned) {
|
||||
redirectClients(c, "", err.Error())
|
||||
return
|
||||
}
|
||||
fail(c, http.StatusInternalServerError, fallback+",已有归属和任务数据保持不变。刷新后重试。")
|
||||
}
|
||||
|
||||
func redirectClients(c *gin.Context, message, errorMessage string) {
|
||||
query := url.Values{}
|
||||
if message != "" {
|
||||
query.Set("msg", message)
|
||||
}
|
||||
if errorMessage != "" {
|
||||
query.Set("error", errorMessage)
|
||||
}
|
||||
target := "/clients"
|
||||
if encoded := query.Encode(); encoded != "" {
|
||||
target += "?" + encoded
|
||||
}
|
||||
c.Redirect(http.StatusSeeOther, target)
|
||||
}
|
||||
|
||||
// ClientDelete 批量删除客户端。
|
||||
//
|
||||
// 二次确认在前端做(见 static/js/app.js),这里直接删。
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"cmautobuy/admin/service"
|
||||
)
|
||||
|
||||
// AdminRequired 叠加在已认证的 /users 路由上。采购员能使用业务页,但不能管理账号。
|
||||
// AdminRequired 叠加在已认证路由上。采购员能使用业务页,但不能执行管理操作。
|
||||
func AdminRequired() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
user := currentUser(c)
|
||||
@@ -23,7 +23,7 @@ func AdminRequired() gin.HandlerFunc {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
fail(c, http.StatusForbidden, "只有管理员可以访问用户管理。当前账号仍可使用其他业务页面。")
|
||||
fail(c, http.StatusForbidden, "只有管理员可以执行此管理操作。当前账号仍可使用其他业务页面。")
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,10 @@ func Register(r *gin.Engine, db *sql.DB, onlineThreshold time.Duration) {
|
||||
|
||||
// 5. 客户端列表
|
||||
pages.GET("/clients", h.ClientList)
|
||||
pages.POST("/clients/delete", h.ClientDelete)
|
||||
clients := pages.Group("/clients", AdminRequired())
|
||||
clients.POST("/assign", h.ClientAssign)
|
||||
clients.POST("/unassign", h.ClientUnassign)
|
||||
clients.POST("/delete", h.ClientDelete)
|
||||
|
||||
// 6. 用户管理:先经过网页登录,再叠加管理员角色校验。
|
||||
users := pages.Group("/users", AdminRequired())
|
||||
|
||||
Reference in New Issue
Block a user