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(¬ices) 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(¬ice).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(¬ice, 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(¬ice).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, }) }