Files
sc-lktx-mp/sc-lktx-backend/internal/admin/handler.go
huangjin fe3ad20fe2 'init'
2026-06-29 17:34:59 +08:00

1899 lines
54 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package admin
import (
"encoding/json"
"fmt"
"path/filepath"
"strconv"
"strings"
"time"
"com.sclktx/m/v2/internal/common/model"
"com.sclktx/m/v2/internal/common/response"
"com.sclktx/m/v2/internal/common/utils"
"com.sclktx/m/v2/internal/pkg/wechat"
"github.com/gin-gonic/gin"
"github.com/xuri/excelize/v2"
"gorm.io/gorm"
)
// AdminHandler 管理员处理器
type AdminHandler struct {
db *gorm.DB
wxClient *wechat.MiniProgramClient
}
func NewAdminHandler(db *gorm.DB, wxClient *wechat.MiniProgramClient) *AdminHandler {
return &AdminHandler{db: db, wxClient: wxClient}
}
// ============ 用户管理 ============
// UserVO 用户视图对象
type UserVO struct {
ID uint `json:"id"`
Openid string `json:"openid"`
Nickname string `json:"nickname"`
RealName string `json:"real_name"`
Phone string `json:"phone"`
AvatarURL string `json:"avatar_url"`
Role string `json:"role"`
RoleName string `json:"role_name"`
Status int `json:"status"`
CreatedAt string `json:"created_at"`
}
// roleName 角色编码转中文名
func roleName(code string) string {
switch code {
case "employee":
return "员工"
case "guard":
return "保安"
default:
return "访客"
}
}
// @Summary 用户列表
// @Description 分页查询用户列表,支持关键词搜索
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param keyword query string false "搜索关键词(姓名/昵称/手机号)"
// @Success 200 {object} response.Response{data=response.PageData{list=[]UserVO}} "成功"
// @Router /admin/users [get]
// ListUsers 用户列表
func (h *AdminHandler) ListUsers(c *gin.Context) {
page, pageSize := utils.ParsePagination(c)
keyword := c.DefaultQuery("keyword", "")
userType := c.DefaultQuery("type", "") // employee / visitor / guard
statusStr := c.DefaultQuery("status", "")
var users []model.User
var total int64
query := h.db.Model(&model.User{})
if keyword != "" {
query = query.Where("real_name LIKE ? OR nickname LIKE ? OR phone LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
}
if userType != "" {
query = query.Where("role = ?", userType)
}
if statusStr != "" {
query = query.Where("status = ?", statusStr)
}
query.Count(&total)
query.Offset(utils.GetOffset(page, pageSize)).Limit(pageSize).Order("created_at DESC, id DESC").Find(&users)
var vos []UserVO
for _, u := range users {
vos = append(vos, userToVO(&u))
}
response.SuccessPage(c, vos, total, page, pageSize)
}
// @Summary 更新用户信息
// @Description 管理员更新用户信息(昵称、姓名、手机号、状态、角色等)
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "用户ID"
// @Param body body object{real_name=string,nickname=string,phone=string,status=int,role=string} true "更新信息"
// @Success 200 {object} response.Response "成功"
// @Router /admin/users/{id} [put]
// UpdateUser 更新用户信息(管理员操作)
func (h *AdminHandler) UpdateUser(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var req struct {
RealName string `json:"real_name"`
Nickname string `json:"nickname"`
Phone string `json:"phone"`
Status *int `json:"status"`
Role string `json:"role"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
updates := map[string]interface{}{}
if req.RealName != "" {
updates["real_name"] = req.RealName
}
if req.Nickname != "" {
updates["nickname"] = req.Nickname
}
if req.Phone != "" {
updates["phone"] = req.Phone
}
if req.Status != nil {
updates["status"] = *req.Status
}
if req.Role != "" {
updates["role"] = req.Role
}
if err := h.db.Model(&model.User{}).Where("id = ?", id).Updates(updates).Error; err != nil {
response.InternalError(c, "更新失败")
return
}
// 如果设为员工角色且尚无 Employee 记录,自动创建
if req.Role == "employee" {
var count int64
h.db.Model(&model.UserDepartment{}).Where("user_id = ?", id).Count(&count)
if count == 0 {
h.db.Model(&model.User{}).Where("id = ?", id).Update("role", "employee")
// 同时创建 UserDepartment 记录
h.db.Create(&model.UserDepartment{
UserID: uint(id),
EmployeeStatus: 1,
EmployeeType: "employee",
})
} else {
h.db.Model(&model.User{}).Where("id = ?", id).Update("role", "employee")
h.db.Model(&model.UserDepartment{}).Where("user_id = ?", id).Update("employee_status", 1)
}
}
response.SuccessWithMessage(c, "更新成功", nil)
}
func userToVO(u *model.User) UserVO {
code := u.Role
if code == "" {
code = "visitor"
}
return UserVO{
ID: u.ID,
Openid: u.Openid,
Nickname: u.Nickname,
RealName: u.RealName,
Phone: u.Phone,
AvatarURL: u.AvatarURL,
Status: u.Status,
Role: code,
RoleName: roleName(code),
CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"),
}
}
// RegisterRoutes 注册管理路由
func (h *AdminHandler) RegisterRoutes(r *gin.RouterGroup, jwtMiddleware gin.HandlerFunc) {
admin := r.Group("/admin")
admin.Use(jwtMiddleware)
{
// 用户管理
admin.GET("/users", h.ListUsers)
admin.PUT("/users/:id", h.UpdateUser)
// Banner 管理
admin.GET("/banners", h.ListBanners)
// 员工列表(审批配置用)
admin.GET("/employees", h.ListEmployees)
admin.POST("/banners", h.CreateBanner)
admin.PUT("/banners/:id", h.UpdateBanner)
admin.DELETE("/banners/:id", h.DeleteBanner)
// 通知管理
admin.GET("/notices", h.ListNotices)
admin.POST("/notices", h.CreateNotice)
admin.PUT("/notices/:id", h.UpdateNotice)
admin.DELETE("/notices/:id", h.DeleteNotice)
// 组织架构(部门树)
admin.GET("/departments", h.ListDepartments)
// 员工认证审核
admin.GET("/certifications", h.ListCertifications)
admin.PUT("/certifications/:id/audit", h.AuditCertification)
// 部门管理(树形结构)
admin.GET("/department-tree", h.ListDepartmentTree)
admin.GET("/departments-all", h.ListAllDepartments)
admin.POST("/departments", h.CreateDepartment)
admin.PUT("/departments/:id", h.UpdateDepartment)
admin.DELETE("/departments/:id", h.DeleteDepartment)
// 员工-部门关联
admin.PUT("/employees/:userId/department", h.SetEmployeeDepartment)
admin.DELETE("/employees/:userId/department", h.ClearEmployeeDepartment)
admin.GET("/employee-departments", h.ListEmployeeDepartments)
// 到访区域管理
admin.GET("/visitor-areas", h.ListVisitorAreas)
admin.POST("/visitor-areas", h.CreateVisitorArea)
admin.PUT("/visitor-areas/:id", h.UpdateVisitorArea)
admin.DELETE("/visitor-areas/:id", h.DeleteVisitorArea)
// 来访目的管理
admin.GET("/visit-types", h.ListVisitTypes)
admin.POST("/visit-types", h.CreateVisitType)
admin.PUT("/visit-types/:id", h.UpdateVisitType)
admin.DELETE("/visit-types/:id", h.DeleteVisitType)
// 预约管理(仅查看,审核走工作流引擎)
admin.GET("/appointments", h.ListAppointments)
// 审批流配置:部门分管领导(存储在 department.vp_user_ids
admin.PUT("/departments/:id/vp-users", h.SetDepartmentVPs)
// 审批流配置:设置部门指定审批人
admin.PUT("/departments/:id/approver", h.SetDepartmentApprover)
// 待确认员工管理(通讯录导入)
admin.POST("/employees/import", h.ImportPendingEmployees)
admin.POST("/employees/import-excel", h.ImportPendingEmployeesExcel)
admin.GET("/employees/pending", h.ListPendingEmployees)
admin.GET("/employees/pending/stats", h.GetPendingEmployeeStats)
admin.POST("/employees/pending/:id/match", h.MatchPendingEmployee)
admin.POST("/employees/pending/sync-all", h.SyncAllPendingEmployees)
// 全局兜底审批人
admin.GET("/global-approvers", h.ListGlobalApprovers)
admin.POST("/global-approvers", h.CreateGlobalApprover)
admin.DELETE("/global-approvers/:id", h.DeleteGlobalApprover)
}
}
// ============ Banner 管理 ============
// @Summary Banner列表
// @Description 获取所有Banner列表按排序字段升序
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response{data=[]model.Banner} "成功"
// @Router /admin/banners [get]
// ListBanners Banner 列表
func (h *AdminHandler) ListBanners(c *gin.Context) {
var banners []model.Banner
h.db.Order("sort ASC").Find(&banners)
response.Success(c, banners)
}
// @Summary 创建Banner
// @Description 创建一个新的Banner
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body object{image_url=string,jump_path=string,is_jump=bool,sort=int} true "Banner信息"
// @Success 200 {object} response.Response{data=model.Banner} "成功"
// @Router /admin/banners [post]
// CreateBanner 创建 Banner
func (h *AdminHandler) CreateBanner(c *gin.Context) {
var req struct {
ImageURL string `json:"image_url" binding:"required"`
JumpPath string `json:"jump_path"`
IsJump bool `json:"is_jump"`
Sort int `json:"sort"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
banner := model.Banner{
ImageURL: req.ImageURL,
JumpPath: req.JumpPath,
IsJump: req.IsJump,
Sort: req.Sort,
IsValid: true,
}
if err := h.db.Create(&banner).Error; err != nil {
response.InternalError(c, "创建失败")
return
}
response.SuccessWithMessage(c, "创建成功", banner)
}
// @Summary 更新Banner
// @Description 更新指定Banner的信息
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Banner ID"
// @Param body body object{image_url=string,jump_path=string,is_jump=bool,sort=int,is_valid=bool} true "更新信息"
// @Success 200 {object} response.Response "成功"
// @Router /admin/banners/{id} [put]
// UpdateBanner 更新 Banner
func (h *AdminHandler) UpdateBanner(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var banner model.Banner
if err := h.db.First(&banner, id).Error; err != nil {
response.NotFound(c, "Banner 不存在")
return
}
var req struct {
ImageURL string `json:"image_url"`
JumpPath string `json:"jump_path"`
IsJump *bool `json:"is_jump"`
Sort *int `json:"sort"`
IsValid *bool `json:"is_valid"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
updates := map[string]interface{}{}
if req.ImageURL != "" {
updates["image_url"] = req.ImageURL
}
if req.JumpPath != "" {
updates["jump_path"] = req.JumpPath
}
if req.IsJump != nil {
updates["is_jump"] = *req.IsJump
}
if req.Sort != nil {
updates["sort"] = *req.Sort
}
if req.IsValid != nil {
updates["is_valid"] = *req.IsValid
}
if err := h.db.Model(&banner).Updates(updates).Error; err != nil {
response.InternalError(c, "更新失败")
return
}
response.SuccessWithMessage(c, "更新成功", nil)
}
// @Summary 删除Banner
// @Description 删除指定Banner
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Banner ID"
// @Success 200 {object} response.Response "成功"
// @Router /admin/banners/{id} [delete]
// DeleteBanner 删除 Banner
func (h *AdminHandler) DeleteBanner(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
if err := h.db.Delete(&model.Banner{}, id).Error; err != nil {
response.InternalError(c, "删除失败")
return
}
response.SuccessWithMessage(c, "删除成功", nil)
}
// ============ 通知管理 ============
// @Summary 通知列表
// @Description 获取所有通知列表,按排序字段升序
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response{data=[]model.Notice} "成功"
// @Router /admin/notices [get]
// ListNotices 通知列表
func (h *AdminHandler) ListNotices(c *gin.Context) {
var notices []model.Notice
h.db.Order("sort ASC").Find(&notices)
response.Success(c, notices)
}
// @Summary 创建通知
// @Description 创建一个新的通知
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body object{title=string,content=string,sort=int} true "通知信息"
// @Success 200 {object} response.Response{data=model.Notice} "成功"
// @Router /admin/notices [post]
// CreateNotice 创建通知
func (h *AdminHandler) CreateNotice(c *gin.Context) {
var req struct {
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
Sort int `json:"sort"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
notice := model.Notice{
Title: req.Title,
Content: req.Content,
Sort: req.Sort,
IsValid: true,
}
if err := h.db.Create(&notice).Error; err != nil {
response.InternalError(c, "创建失败")
return
}
response.SuccessWithMessage(c, "创建成功", notice)
}
// @Summary 更新通知
// @Description 更新指定通知的信息
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "通知ID"
// @Param body body object{title=string,content=string,sort=int,is_valid=bool} true "更新信息"
// @Success 200 {object} response.Response "成功"
// @Router /admin/notices/{id} [put]
// UpdateNotice 更新通知
func (h *AdminHandler) UpdateNotice(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var notice model.Notice
if err := h.db.First(&notice, id).Error; err != nil {
response.NotFound(c, "通知不存在")
return
}
var req struct {
Title string `json:"title"`
Content string `json:"content"`
Sort *int `json:"sort"`
IsValid *bool `json:"is_valid"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
updates := map[string]interface{}{}
if req.Title != "" {
updates["title"] = req.Title
}
if req.Content != "" {
updates["content"] = req.Content
}
if req.Sort != nil {
updates["sort"] = *req.Sort
}
if req.IsValid != nil {
updates["is_valid"] = *req.IsValid
}
if err := h.db.Model(&notice).Updates(updates).Error; err != nil {
response.InternalError(c, "更新失败")
return
}
response.SuccessWithMessage(c, "更新成功", nil)
}
// @Summary 删除通知
// @Description 删除指定通知
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "通知ID"
// @Success 200 {object} response.Response "成功"
// @Router /admin/notices/{id} [delete]
// DeleteNotice 删除通知
func (h *AdminHandler) DeleteNotice(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
if err := h.db.Delete(&model.Notice{}, id).Error; err != nil {
response.InternalError(c, "删除失败")
return
}
response.SuccessWithMessage(c, "删除成功", nil)
}
// ============ 员工列表 ============
// @Summary 员工列表
// @Description 获取所有员工列表供审批配置选择用包含正式员工和role为employee的用户
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response{data=[]object{id=uint,user_id=uint,name=string,department_name=string}} "成功"
// @Router /admin/employees [get]
// ListEmployees 员工列表(供审批配置选择用)
func (h *AdminHandler) ListEmployees(c *gin.Context) {
type EmployeeItem struct {
ID uint `json:"id"`
UserID uint `json:"user_id"`
Name string `json:"name"`
DepartmentName string `json:"department_name"`
Position string `json:"position"`
EmployeeType string `json:"employee_type"`
}
var items []EmployeeItem
// 查询所有状态正常的 role=employee 用户,左连接 user_departments 获取部门信息
var rows []struct {
model.User
DeptName string `gorm:"column:dept_name"`
DeptPosition string `gorm:"column:dept_position"`
DeptType string `gorm:"column:dept_employee_type"`
}
h.db.Table("users").
Select("users.*, COALESCE(ud.department_name, '') as dept_name, COALESCE(ud.position, '') as dept_position, COALESCE(ud.employee_type, 'employee') as dept_employee_type").
Joins("LEFT JOIN user_departments ud ON ud.user_id = users.id").
Where("users.role = ? AND users.status = ?", "employee", 1).
Order("users.real_name ASC").
Scan(&rows)
employeeUserIDs := make(map[uint]bool)
for _, r := range rows {
employeeUserIDs[r.User.ID] = true
name := r.User.RealName
if name == "" {
name = r.User.RealName
}
items = append(items, EmployeeItem{
ID: r.User.ID,
UserID: r.User.ID,
Name: name,
DepartmentName: r.DeptName,
Position: r.DeptPosition,
EmployeeType: r.DeptType,
})
}
// 补充 role='employee' 但尚未有关联记录的用户(兼容旧数据)
var users []model.User
h.db.Where("role = ? AND status = ?", "employee", 1).Find(&users)
for _, u := range users {
if !employeeUserIDs[u.ID] {
items = append(items, EmployeeItem{
ID: u.ID,
UserID: u.ID,
Name: u.RealName,
DepartmentName: "",
Position: "",
EmployeeType: "employee",
})
}
}
if items == nil {
items = []EmployeeItem{}
}
response.Success(c, items)
}
// ============ 组织架构 ============
// DepartmentVO 部门视图
type DepartmentVO struct {
Name string `json:"name"`
Employees []EmployeeBrief `json:"employees"`
}
// EmployeeBrief 员工简要信息
type EmployeeBrief struct {
ID uint `json:"id"`
UserID uint `json:"user_id"`
Name string `json:"name"`
Phone string `json:"phone"`
Position string `json:"position"`
EmployeeType string `json:"employee_type"`
}
// @Summary 组织架构
// @Description 获取组织架构列表,按部门分组显示员工
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response{data=[]DepartmentVO} "成功"
// @Router /admin/departments [get]
// ListDepartments 获取组织架构(按部门分组)
func (h *AdminHandler) ListDepartments(c *gin.Context) {
type EmpRow struct {
model.User
DeptName string `gorm:"column:dept_name"`
DeptPosition string `gorm:"column:dept_position"`
DeptType string `gorm:"column:dept_employee_type"`
}
var rows []EmpRow
h.db.Table("users").
Select("users.*, COALESCE(ud.department_name, '') as dept_name, COALESCE(ud.position, '') as dept_position, COALESCE(ud.employee_type, 'employee') as dept_employee_type").
Joins("LEFT JOIN user_departments ud ON ud.user_id = users.id").
Where("users.status = ?", 1).
Order("dept_name ASC, users.real_name ASC").
Scan(&rows)
depMap := make(map[string][]EmployeeBrief)
for _, r := range rows {
dept := r.DeptName
if dept == "" {
dept = "未分配部门"
}
depMap[dept] = append(depMap[dept], EmployeeBrief{
ID: r.User.ID,
UserID: r.User.ID,
Name: r.User.RealName,
Phone: r.User.Phone,
Position: r.DeptPosition,
EmployeeType: r.DeptType,
})
}
var depts []DepartmentVO
for name, emps := range depMap {
depts = append(depts, DepartmentVO{Name: name, Employees: emps})
}
response.Success(c, depts)
}
// ============ 员工认证审核 ============
// @Summary 员工认证申请列表
// @Description 分页查询员工认证申请列表,支持按状态筛选和关键词搜索
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param status query int false "审核状态0待审核 1通过 2拒绝"
// @Param keyword query string false "搜索关键词"
// @Success 200 {object} response.Response{data=response.PageData{list=[]model.EmployeeCertification}} "成功"
// @Router /admin/certifications [get]
// ListCertifications 员工认证申请列表
func (h *AdminHandler) ListCertifications(c *gin.Context) {
page, pageSize := utils.ParsePagination(c)
statusStr := c.DefaultQuery("status", "")
keyword := c.Query("keyword")
var certs []model.EmployeeCertification
var total int64
query := h.db.Model(&model.EmployeeCertification{}).
Preload("User").
Preload("Department").
Preload("Approver")
if statusStr != "" {
status, _ := strconv.Atoi(statusStr)
query = query.Where("status = ?", status)
}
if keyword != "" {
query = query.Where("real_name LIKE ? OR phone LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
}
query.Count(&total)
query.Offset(utils.GetOffset(page, pageSize)).Limit(pageSize).Order("created_at DESC").Find(&certs)
response.SuccessPage(c, certs, total, page, pageSize)
}
// @Summary 审核员工认证
// @Description 审核员工认证申请(通过/拒绝),通过时需分配部门
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "认证申请ID"
// @Param body body object{status=int,department_id=uint,comment=string} true "审核信息status:1通过 2拒绝"
// @Success 200 {object} response.Response "成功"
// @Router /admin/certifications/{id}/audit [put]
// AuditCertification 审核员工认证申请
func (h *AdminHandler) AuditCertification(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var req struct {
Status int `json:"status" binding:"required"` // 1 通过 2 拒绝
RealName string `json:"real_name"`
Phone string `json:"phone"`
DepartmentID *uint `json:"department_id"`
Position string `json:"position"`
Role string `json:"role"`
Comment string `json:"comment"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
if req.Status != 1 && req.Status != 2 {
response.BadRequest(c, "状态值无效")
return
}
var cert model.EmployeeCertification
if err := h.db.First(&cert, id).Error; err != nil {
response.NotFound(c, "申请不存在")
return
}
if cert.Status != 0 {
response.BadRequest(c, "该申请已处理")
return
}
approverID := utils.GetUserID(c)
realName := req.RealName
if realName == "" {
realName = cert.RealName
}
phone := req.Phone
if phone == "" {
phone = cert.Phone
}
updates := map[string]interface{}{
"status": req.Status,
"approver_id": approverID,
"real_name": realName,
"phone": phone,
"comment": req.Comment,
}
// 如果通过,设置分配的部门、职位、角色类型
if req.Status == 1 {
if req.DepartmentID == nil {
response.BadRequest(c, "通过时必须分配部门")
return
}
var dept model.Department
if err := h.db.First(&dept, *req.DepartmentID).Error; err != nil {
response.BadRequest(c, "部门不存在")
return
}
updates["department_id"] = *req.DepartmentID
updates["position"] = req.Position
updates["role"] = req.Role
}
if err := h.db.Model(&cert).Updates(updates).Error; err != nil {
response.InternalError(c, "审核失败")
return
}
// 如果通过,同步用户表 + 员工-部门关联表
if req.Status == 1 {
// 用户角色取认证申请时的角色类型employee / guard
userRole := cert.Role
if userRole == "" {
userRole = "employee"
}
h.db.Model(&model.User{}).Where("id = ?", cert.UserID).Updates(map[string]interface{}{
"role": userRole,
"real_name": realName,
"phone": phone,
})
// 同步员工-部门关联表(含职位、员工类型、状态)
pos := req.Position
if pos == "" {
pos = cert.Position
}
empType := req.Role
if empType == "" {
empType = "employee"
}
// 查找通讯录中的钉钉UserId如已导入
var pendingEmp model.PendingEmployee
dingTalkUserID := ""
if err := h.db.Where("phone = ?", phone).First(&pendingEmp).Error; err == nil {
dingTalkUserID = pendingEmp.DingTalkUserID
}
udData := map[string]interface{}{
"position": pos,
"employee_type": empType,
"employee_status": 1,
"ding_talk_user_id": dingTalkUserID,
}
if req.DepartmentID != nil {
var dept model.Department
if err := h.db.First(&dept, *req.DepartmentID).Error; err == nil {
udData["department_id"] = *req.DepartmentID
udData["department_name"] = dept.Name
}
}
var ud model.UserDepartment
result := h.db.Where("user_id = ?", cert.UserID).First(&ud)
if result.Error != nil {
udData["user_id"] = cert.UserID
h.db.Model(&model.UserDepartment{}).Create(udData)
} else {
h.db.Model(&ud).Updates(udData)
}
}
response.SuccessWithMessage(c, "审核完成", nil)
}
// ============ 部门管理(树形结构)============
// DepartmentTreeNode 部门树节点
type DepartmentTreeNode struct {
model.Department
Children []*DepartmentTreeNode `json:"children"`
}
// buildDeptTree 构建部门树
func buildDeptTree(departments []model.Department, parentID *uint) []*DepartmentTreeNode {
var nodes []*DepartmentTreeNode
for _, d := range departments {
if (parentID == nil && d.ParentID == nil) ||
(parentID != nil && d.ParentID != nil && *d.ParentID == *parentID) {
node := &DepartmentTreeNode{
Department: d,
Children: buildDeptTree(departments, &d.ID),
}
nodes = append(nodes, node)
}
}
return nodes
}
// @Summary 部门树
// @Description 获取部门树形结构
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response{data=[]DepartmentTreeNode} "成功"
// @Router /admin/department-tree [get]
// ListDepartmentTree 获取部门树
func (h *AdminHandler) ListDepartmentTree(c *gin.Context) {
var departments []model.Department
h.db.Where("status = ?", 1).Order("sort ASC, id ASC").Find(&departments)
tree := buildDeptTree(departments, nil)
response.Success(c, tree)
}
// DepartmentItem 部门扁平列表项
type DepartmentItem struct {
ID uint `json:"id"`
Name string `json:"name"`
ParentID *uint `json:"parent_id"`
LeaderName string `json:"leader_name"`
LeaderPhone string `json:"leader_phone"`
ApproverUserIDs string `json:"approver_user_ids"`
VpUserIDs string `json:"vp_user_ids"`
Sort int `json:"sort"`
Status int `json:"status"`
CreatedAt string `json:"created_at"`
}
// @Summary 所有部门列表
// @Description 获取所有部门扁平列表
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response{data=[]DepartmentItem} "成功"
// @Router /admin/departments-all [get]
// ListAllDepartments 获取所有部门(扁平列表)
func (h *AdminHandler) ListAllDepartments(c *gin.Context) {
var departments []model.Department
h.db.Where("status = 1").Preload("Leader").Order("sort ASC, id ASC").Find(&departments)
var items []DepartmentItem
for _, d := range departments {
item := DepartmentItem{
ID: d.ID,
Name: d.Name,
ParentID: d.ParentID,
Sort: d.Sort,
Status: d.Status,
CreatedAt: d.CreatedAt.Format("2006-01-02 15:04:05"),
}
if d.Leader != nil {
item.LeaderName = d.Leader.RealName
item.LeaderPhone = d.Leader.Phone
}
item.ApproverUserIDs = d.ApproverUserIDs
item.VpUserIDs = d.VpUserIDs
items = append(items, item)
}
response.Success(c, items)
}
// @Summary 创建部门
// @Description 创建一个新的部门
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body object{name=string,parent_id=uint,leader_id=uint,deputy_id=uint,sort=int,description=string} true "部门信息"
// @Success 200 {object} response.Response{data=model.Department} "成功"
// @Router /admin/departments [post]
// CreateDepartment 创建部门
func (h *AdminHandler) CreateDepartment(c *gin.Context) {
var req struct {
Name string `json:"name" binding:"required"`
ParentID *uint `json:"parent_id"`
LeaderID *uint `json:"leader_id"`
DeputyID *uint `json:"deputy_id"`
Sort int `json:"sort"`
Description string `json:"description"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
// 如果指定了上级部门,检查是否存在
if req.ParentID != nil {
var parent model.Department
if err := h.db.First(&parent, *req.ParentID).Error; err != nil {
response.BadRequest(c, "上级部门不存在")
return
}
}
dept := model.Department{
Name: req.Name,
ParentID: req.ParentID,
LeaderID: req.LeaderID,
DeputyID: req.DeputyID,
Sort: req.Sort,
Status: 1,
Description: req.Description,
}
if err := h.db.Create(&dept).Error; err != nil {
response.InternalError(c, "创建失败")
return
}
response.SuccessWithMessage(c, "创建成功", dept)
}
// @Summary 更新部门
// @Description 更新指定部门的信息
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "部门ID"
// @Param body body object{name=string,parent_id=uint,leader_id=uint,deputy_id=uint,sort=int,status=int,description=string} true "更新信息"
// @Success 200 {object} response.Response "成功"
// @Router /admin/departments/{id} [put]
// UpdateDepartment 更新部门
func (h *AdminHandler) UpdateDepartment(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var dept model.Department
if err := h.db.First(&dept, id).Error; err != nil {
response.NotFound(c, "部门不存在")
return
}
var req struct {
Name string `json:"name"`
ParentID *uint `json:"parent_id"`
LeaderID *uint `json:"leader_id"`
DeputyID *uint `json:"deputy_id"`
Sort *int `json:"sort"`
Status *int `json:"status"`
Description string `json:"description"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
// 不能将自己的 parent_id 设为自己
if req.ParentID != nil && *req.ParentID == dept.ID {
response.BadRequest(c, "上级部门不能是自己")
return
}
updates := map[string]interface{}{}
if req.Name != "" {
updates["name"] = req.Name
}
if req.ParentID != nil {
updates["parent_id"] = *req.ParentID
}
if req.LeaderID != nil {
updates["leader_id"] = *req.LeaderID
}
if req.DeputyID != nil {
updates["deputy_id"] = *req.DeputyID
}
if req.Sort != nil {
updates["sort"] = *req.Sort
}
if req.Status != nil {
updates["status"] = *req.Status
}
if req.Description != "" {
updates["description"] = req.Description
}
if err := h.db.Model(&dept).Updates(updates).Error; err != nil {
response.InternalError(c, "更新失败")
return
}
response.SuccessWithMessage(c, "更新成功", nil)
}
// @Summary 删除部门
// @Description 删除指定部门及其关联检查
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "部门ID"
// @Success 200 {object} response.Response "成功"
// @Router /admin/departments/{id} [delete]
// DeleteDepartment 删除部门
func (h *AdminHandler) DeleteDepartment(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var dept model.Department
if err := h.db.First(&dept, id).Error; err != nil {
response.NotFound(c, "部门不存在")
return
}
// 检查是否有子部门
var childCount int64
h.db.Model(&model.Department{}).Where("parent_id = ?", id).Count(&childCount)
if childCount > 0 {
response.BadRequest(c, "该部门下存在子部门,请先删除子部门")
return
}
// 检查是否有员工关联
var empCount int64
h.db.Model(&model.EmployeeCertification{}).Where("department_id = ? AND status = 1", id).Count(&empCount)
if empCount > 0 {
response.BadRequest(c, "该部门下有认证员工,请先移除关联")
return
}
if err := h.db.Delete(&dept).Error; err != nil {
response.InternalError(c, "删除失败")
return
}
response.SuccessWithMessage(c, "删除成功", nil)
}
// ============ 员工-部门关联 ============
// @Summary 设置员工部门
// @Description 为指定员工设置所属部门
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param userId path int true "用户ID"
// @Param body body object{department_id=uint} true "部门ID"
// @Success 200 {object} response.Response "成功"
// @Router /admin/employees/{userId}/department [put]
// SetEmployeeDepartment 设置员工部门
func (h *AdminHandler) SetEmployeeDepartment(c *gin.Context) {
userID, err := strconv.ParseUint(c.Param("userId"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var req struct {
DepartmentID uint `json:"department_id" binding:"required"`
Position string `json:"position"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
// 检查用户是否存在
var user model.User
if err := h.db.First(&user, userID).Error; err != nil {
response.NotFound(c, "用户不存在")
return
}
// 检查部门是否存在
var dept model.Department
if err := h.db.First(&dept, req.DepartmentID).Error; err != nil {
response.NotFound(c, "部门不存在")
return
}
// 使用 UserDepartment 关联,有则更新,无则创建
var ud model.UserDepartment
result := h.db.Where("user_id = ?", userID).First(&ud)
fields := map[string]interface{}{
"department_id": req.DepartmentID,
"department_name": dept.Name,
"position": req.Position,
}
if result.Error != nil {
// 创建新关联
ud = model.UserDepartment{
UserID: uint(userID),
DepartmentID: req.DepartmentID,
DepartmentName: dept.Name,
Position: req.Position,
}
if err := h.db.Create(&ud).Error; err != nil {
response.InternalError(c, "设置部门失败")
return
}
} else {
// 更新已有关联
if err := h.db.Model(&ud).Updates(fields).Error; err != nil {
response.InternalError(c, "设置部门失败")
return
}
}
response.SuccessWithMessage(c, "设置成功", nil)
}
// @Summary 清除员工部门
// @Description 移除指定员工的部门归属
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param userId path int true "用户ID"
// @Success 200 {object} response.Response "成功"
// @Router /admin/employees/{userId}/department [delete]
// ClearEmployeeDepartment 清除员工部门
func (h *AdminHandler) ClearEmployeeDepartment(c *gin.Context) {
userID, err := strconv.ParseUint(c.Param("userId"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
if err := h.db.Where("user_id = ?", userID).Delete(&model.UserDepartment{}).Error; err != nil {
response.InternalError(c, "清除部门失败")
return
}
response.SuccessWithMessage(c, "已移除", nil)
}
// @Summary 员工-部门关联列表
// @Description 获取所有员工-部门关联关系
// @Tags 管理员
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response{data=[]model.UserDepartment} "成功"
// @Router /admin/employee-departments [get]
// ListEmployeeDepartments 员工-部门关联列表
func (h *AdminHandler) ListEmployeeDepartments(c *gin.Context) {
var list []model.UserDepartment
h.db.Preload("Department").Find(&list)
if list == nil {
list = []model.UserDepartment{}
}
response.Success(c, list)
}
// ============ 到访区域管理 ============
// ListVisitorAreas 到访区域列表
func (h *AdminHandler) ListVisitorAreas(c *gin.Context) {
var areas []model.VisitorArea
h.db.Order("sort ASC, id ASC").Find(&areas)
if areas == nil {
areas = []model.VisitorArea{}
}
response.Success(c, areas)
}
// CreateVisitorArea 创建到访区域
func (h *AdminHandler) CreateVisitorArea(c *gin.Context) {
var req struct {
Name string `json:"name" binding:"required"`
Sort int `json:"sort"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
area := model.VisitorArea{Name: req.Name, Sort: req.Sort, Status: 1}
if err := h.db.Create(&area).Error; err != nil {
response.InternalError(c, "创建失败")
return
}
response.SuccessWithMessage(c, "创建成功", area)
}
// UpdateVisitorArea 更新到访区域
func (h *AdminHandler) UpdateVisitorArea(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var area model.VisitorArea
if err := h.db.First(&area, id).Error; err != nil {
response.NotFound(c, "区域不存在")
return
}
var req struct {
Name *string `json:"name"`
Sort *int `json:"sort"`
Status *int `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
updates := map[string]interface{}{}
if req.Name != nil {
updates["name"] = *req.Name
}
if req.Sort != nil {
updates["sort"] = *req.Sort
}
if req.Status != nil {
updates["status"] = *req.Status
}
if len(updates) > 0 {
h.db.Model(&area).Updates(updates)
}
response.SuccessWithMessage(c, "更新成功", nil)
}
// DeleteVisitorArea 删除到访区域
func (h *AdminHandler) DeleteVisitorArea(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
if err := h.db.Delete(&model.VisitorArea{}, id).Error; err != nil {
response.InternalError(c, "删除失败")
return
}
response.SuccessWithMessage(c, "删除成功", nil)
}
// ============ 来访目的管理 ============
// ListVisitTypes 来访目的列表
func (h *AdminHandler) ListVisitTypes(c *gin.Context) {
var types []model.VisitType
h.db.Order("sort ASC, id ASC").Find(&types)
if types == nil {
types = []model.VisitType{}
}
response.Success(c, types)
}
// CreateVisitType 创建来访目的
func (h *AdminHandler) CreateVisitType(c *gin.Context) {
var req struct {
Name string `json:"name" binding:"required"`
Sort int `json:"sort"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
vt := model.VisitType{Name: req.Name, Sort: req.Sort, Status: 1}
if err := h.db.Create(&vt).Error; err != nil {
response.InternalError(c, "创建失败")
return
}
response.SuccessWithMessage(c, "创建成功", vt)
}
// UpdateVisitType 更新来访目的
func (h *AdminHandler) UpdateVisitType(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var vt model.VisitType
if err := h.db.First(&vt, id).Error; err != nil {
response.NotFound(c, "类型不存在")
return
}
var req struct {
Name *string `json:"name"`
Sort *int `json:"sort"`
Status *int `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
updates := map[string]interface{}{}
if req.Name != nil {
updates["name"] = *req.Name
}
if req.Sort != nil {
updates["sort"] = *req.Sort
}
if req.Status != nil {
updates["status"] = *req.Status
}
if len(updates) > 0 {
h.db.Model(&vt).Updates(updates)
}
response.SuccessWithMessage(c, "更新成功", nil)
}
// DeleteVisitType 删除来访目的
func (h *AdminHandler) DeleteVisitType(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
if err := h.db.Delete(&model.VisitType{}, id).Error; err != nil {
response.InternalError(c, "删除失败")
return
}
response.SuccessWithMessage(c, "删除成功", nil)
}
// ============ 预约管理(管理员) ============
// ListAppointments 管理员查看所有预约
func (h *AdminHandler) ListAppointments(c *gin.Context) {
page, pageSize := utils.ParsePagination(c)
statusStr := c.DefaultQuery("status", "")
keyword := c.DefaultQuery("keyword", "")
var appointments []model.Appointment
var total int64
query := h.db.Model(&model.Appointment{})
if statusStr != "" {
status, _ := strconv.Atoi(statusStr)
query = query.Where("status = ?", status)
}
if keyword != "" {
query = query.Where("visitor_info LIKE ? OR employee_info LIKE ?",
"%"+keyword+"%", "%"+keyword+"%")
}
query.Count(&total)
query.Offset(utils.GetOffset(page, pageSize)).Limit(pageSize).
Order("created_at DESC").Find(&appointments)
response.SuccessPage(c, appointments, total, page, pageSize)
}
// ApproveAppointment 管理员审核预约
// ============ 新审批流配置 ============
// SetDepartmentVPs 设置部门的分管领导用户ID列表
func (h *AdminHandler) SetDepartmentVPs(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var req struct {
VpUserIDs []uint `json:"vp_user_ids"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
// 序列化为 JSON 数组字符串
b, _ := json.Marshal(req.VpUserIDs)
vpIDsStr := string(b)
if req.VpUserIDs == nil {
vpIDsStr = ""
}
if err := h.db.Model(&model.Department{}).Where("id = ?", id).Update("vp_user_ids", vpIDsStr).Error; err != nil {
response.InternalError(c, "更新失败")
return
}
response.SuccessWithMessage(c, "更新成功", nil)
}
// SetDepartmentApprover 设置部门指定审批人列表
func (h *AdminHandler) SetDepartmentApprover(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var req struct {
ApproverUserIDs []uint `json:"approver_user_ids"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
// 序列化为 JSON 数组字符串
b, _ := json.Marshal(req.ApproverUserIDs)
idsStr := string(b)
if req.ApproverUserIDs == nil {
idsStr = ""
}
if err := h.db.Model(&model.Department{}).Where("id = ?", id).Update("approver_user_ids", idsStr).Error; err != nil {
response.InternalError(c, "更新失败")
return
}
response.SuccessWithMessage(c, "更新成功", nil)
}
// ============ 待确认员工管理 ============
// ImportPendingEmployees 批量导入待确认员工
func (h *AdminHandler) ImportPendingEmployees(c *gin.Context) {
var req struct {
Employees []struct {
RealName string `json:"real_name" binding:"required"`
Phone string `json:"phone" binding:"required"`
DepartmentID uint `json:"department_id" binding:"required"`
DepartmentName string `json:"department_name"`
Position string `json:"position"`
EmployeeType string `json:"employee_type"`
DingTalkUserID string `json:"dingtalk_user_id"`
} `json:"employees" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
imported := 0
updated := 0
for _, emp := range req.Employees {
if emp.EmployeeType == "" {
emp.EmployeeType = "employee"
}
var existing model.PendingEmployee
result := h.db.Where("phone = ?", emp.Phone).First(&existing)
if result.RowsAffected > 0 {
// 已存在 → 更新(岗位调动等情况)
if err := h.db.Model(&existing).Updates(map[string]interface{}{
"real_name": emp.RealName,
"department_id": emp.DepartmentID,
"department_name": emp.DepartmentName,
"position": emp.Position,
"employee_type": emp.EmployeeType,
"ding_talk_user_id": emp.DingTalkUserID,
}).Error; err != nil {
fmt.Printf("更新待确认员工失败(JSON): phone=%s err=%v\n", emp.Phone, err)
continue
}
updated++
continue
}
pe := model.PendingEmployee{
RealName: emp.RealName,
Phone: emp.Phone,
DepartmentID: emp.DepartmentID,
DepartmentName: emp.DepartmentName,
Position: emp.Position,
EmployeeType: emp.EmployeeType,
DingTalkUserID: emp.DingTalkUserID,
}
if err := h.db.Create(&pe).Error; err != nil {
fmt.Printf("创建待确认员工失败: phone=%s err=%v\n", emp.Phone, err)
continue
}
imported++
}
response.Success(c, gin.H{
"imported": imported,
"updated": updated,
"total": len(req.Employees),
})
}
// ListPendingEmployees 待确认员工列表
func (h *AdminHandler) ListPendingEmployees(c *gin.Context) {
page, pageSize := utils.ParsePagination(c)
status := c.DefaultQuery("status", "")
keyword := c.DefaultQuery("keyword", "")
var list []model.PendingEmployee
var total int64
query := h.db.Model(&model.PendingEmployee{})
if status == "matched" {
query = query.Where("is_matched = ?", true)
} else if status == "pending" {
query = query.Where("is_matched = ?", false)
}
if keyword != "" {
query = query.Where("real_name LIKE ? OR phone LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
}
deptIDStr := c.DefaultQuery("department_id", "")
if deptIDStr != "" {
deptID, _ := strconv.ParseUint(deptIDStr, 10, 64)
if deptID > 0 {
query = query.Where("department_id = ?", deptID)
}
}
query.Count(&total)
query.Order("created_at DESC").Offset(utils.GetOffset(page, pageSize)).Limit(pageSize).Find(&list)
if list == nil {
list = []model.PendingEmployee{}
}
response.SuccessPage(c, list, total, page, pageSize)
}
// GetPendingEmployeeStats 待确认员工统计
func (h *AdminHandler) GetPendingEmployeeStats(c *gin.Context) {
var total, pending, matched int64
h.db.Model(&model.PendingEmployee{}).Count(&total)
h.db.Model(&model.PendingEmployee{}).Where("is_matched = ?", false).Count(&pending)
h.db.Model(&model.PendingEmployee{}).Where("is_matched = ?", true).Count(&matched)
response.Success(c, gin.H{
"total": total,
"pending": pending,
"matched": matched,
})
}
// MatchPendingEmployee 手动匹配待确认员工到用户(按手机号)
func (h *AdminHandler) MatchPendingEmployee(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
var pending model.PendingEmployee
if err := h.db.First(&pending, id).Error; err != nil {
response.NotFound(c, "记录不存在")
return
}
if pending.IsMatched {
// 已匹配过的也允许重新同步,不阻塞
}
// 按手机号查找用户(不要求 phone_verified手机号本身即可
var user model.User
if err := h.db.Where("phone = ?", pending.Phone).First(&user).Error; err != nil {
response.BadRequest(c, "未找到已绑定该手机号的用户,请先让员工登录并绑定手机")
return
}
now := time.Now()
// 创建 UserDepartment
ud := model.UserDepartment{
UserID: user.ID,
DepartmentID: pending.DepartmentID,
DepartmentName: pending.DepartmentName,
Position: pending.Position,
EmployeeType: pending.EmployeeType,
EmployeeStatus: 1,
DingTalkUserID: pending.DingTalkUserID,
}
h.db.Where("user_id = ?", user.ID).Assign(ud).FirstOrCreate(&model.UserDepartment{UserID: user.ID})
// 升级角色 + 同步真实姓名
updates := map[string]interface{}{
"role": "employee",
}
if user.RealName == "" || user.RealName != pending.RealName {
updates["real_name"] = pending.RealName
}
h.db.Model(&user).Updates(updates)
// 标记已匹配
h.db.Model(&pending).Updates(map[string]interface{}{
"is_matched": true,
"matched_user_id": &user.ID,
"matched_at": &now,
})
response.SuccessWithMessage(c, "匹配成功", gin.H{
"user_id": user.ID,
"real_name": user.RealName,
"phone": user.Phone,
})
}
// SyncAllPendingEmployees 一键同步所有待确认员工信息到用户
func (h *AdminHandler) SyncAllPendingEmployees(c *gin.Context) {
var pendings []model.PendingEmployee
h.db.Find(&pendings)
synced := 0
skipped := 0
for _, p := range pendings {
var user model.User
if err := h.db.Where("phone = ?", p.Phone).First(&user).Error; err != nil {
skipped++
continue
}
now := time.Now()
// 创建或更新 UserDepartment
ud := model.UserDepartment{
UserID: user.ID,
DepartmentID: p.DepartmentID,
DepartmentName: p.DepartmentName,
Position: p.Position,
EmployeeType: p.EmployeeType,
EmployeeStatus: 1,
DingTalkUserID: p.DingTalkUserID,
}
h.db.Where("user_id = ?", user.ID).Assign(ud).FirstOrCreate(&model.UserDepartment{UserID: user.ID})
// 升级角色 + 同步姓名
userUpdates := map[string]interface{}{
"role": "employee",
}
if user.RealName == "" || user.RealName != p.RealName {
userUpdates["real_name"] = p.RealName
}
h.db.Model(&user).Updates(userUpdates)
// 标记已匹配
h.db.Model(&p).Updates(map[string]interface{}{
"is_matched": true,
"matched_user_id": &user.ID,
"matched_at": &now,
})
synced++
}
response.Success(c, gin.H{
"synced": synced,
"skipped": skipped,
"total": len(pendings),
})
}
// ============ 全局兜底审批人 ============
func (h *AdminHandler) ListGlobalApprovers(c *gin.Context) {
var list []model.GlobalApprover
h.db.Preload("User").Order("created_at ASC").Find(&list)
if list == nil {
list = []model.GlobalApprover{}
}
response.Success(c, list)
}
func (h *AdminHandler) CreateGlobalApprover(c *gin.Context) {
var req struct {
UserID uint `json:"user_id" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "参数错误")
return
}
ga := model.GlobalApprover{UserID: req.UserID}
if err := h.db.Create(&ga).Error; err != nil {
response.InternalError(c, "创建失败")
return
}
h.db.Preload("User").First(&ga, ga.ID)
response.SuccessWithMessage(c, "创建成功", ga)
}
func (h *AdminHandler) DeleteGlobalApprover(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "参数错误")
return
}
if err := h.db.Delete(&model.GlobalApprover{}, id).Error; err != nil {
response.InternalError(c, "删除失败")
return
}
response.SuccessWithMessage(c, "删除成功", nil)
}
// ImportPendingEmployeesExcel 通过 Excel 文件导入待确认员工
func (h *AdminHandler) ImportPendingEmployeesExcel(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
response.BadRequest(c, "请上传 Excel 文件")
return
}
ext := strings.ToLower(filepath.Ext(file.Filename))
if ext != ".xlsx" && ext != ".xls" {
response.BadRequest(c, "仅支持 .xlsx 或 .xls 文件")
return
}
src, err := file.Open()
if err != nil {
response.InternalError(c, "无法读取文件")
return
}
defer src.Close()
f, err := excelize.OpenReader(src)
if err != nil {
response.BadRequest(c, "无法解析 Excel 文件")
return
}
rows, err := f.GetRows(f.GetSheetName(0))
if err != nil || len(rows) < 4 {
response.BadRequest(c, "Excel 文件为空或格式不正确")
return
}
var departments []model.Department
h.db.Where("status = 1").Find(&departments)
deptMap := make(map[string]uint)
for _, d := range departments {
deptMap[d.Name] = d.ID
}
var imported, updated, noDept int
for i := 3; i < len(rows); i++ {
row := rows[i]
if len(row) < 4 {
continue
}
dingTalkUserID := strings.TrimSpace(row[0])
name := strings.TrimSpace(row[1])
phone := strings.TrimSpace(row[2])
deptFull := strings.TrimSpace(row[3])
if name == "" || phone == "" || deptFull == "" {
continue
}
if !strings.Contains(deptFull, "四川") {
continue
}
phone = strings.ReplaceAll(phone, "+86-", "")
phone = strings.ReplaceAll(phone, "+86", "")
phone = strings.TrimSpace(phone)
subDept := deptFull
if idx := strings.Index(deptFull, "-"); idx >= 0 {
subDept = strings.TrimSpace(deptFull[idx+1:])
}
deptID, ok := deptMap[subDept]
if !ok {
deptID, ok = deptMap[deptFull]
}
if !ok {
noDept++
continue
}
updates := map[string]interface{}{
"real_name": name,
"department_id": deptID,
"department_name": subDept,
"position": "",
"employee_type": "employee",
"ding_talk_user_id": dingTalkUserID,
}
var existing model.PendingEmployee
result := h.db.Where("phone = ?", phone).First(&existing)
if result.RowsAffected > 0 {
// 已存在 → 更新
if err := h.db.Model(&existing).Updates(updates).Error; err != nil {
continue
}
updated++
updated++
continue
}
pe := model.PendingEmployee{
RealName: name,
Phone: phone,
DepartmentID: deptID,
DepartmentName: subDept,
Position: "",
EmployeeType: "employee",
DingTalkUserID: dingTalkUserID,
}
if err := h.db.Create(&pe).Error; err != nil {
continue
}
imported++
}
response.Success(c, gin.H{
"imported": imported,
"updated": updated,
"noDept": noDept,
"total": imported + updated + noDept,
})
}