Files
2026-10-19 01:13:17 +08:00

209 lines
6.2 KiB
Go

package controllers
import (
"strconv"
"strings"
"server/models"
"github.com/beego/beego/v2/client/orm"
"server/pkg/identity"
)
// 移动端通讯录(企业内部通讯录)只读接口。
//
// 复用 AppOaController 的鉴权与响应封装(oaClaims / oaErr / oaOk):
// 鉴权放宽到 app / platform 令牌,数据一律按 JWT 中的 TenantId 隔离,前端无法指定租户。
// 写入能力(同步员工 / 新增 / 编辑 / 删除)仍留在 Web 管理端。
// appContactDTO 通讯录条目 DTO(移动端展示字段)。
func appContactDTO(row models.BackendErpContact, orgName string) map[string]interface{} {
return map[string]interface{}{
"id": row.ID,
"org_id": contactDerefUint64(row.OrgID),
"org_name": orgName,
"contact_type": row.ContactType,
"contact_name": row.ContactName,
"gender": row.Gender,
"phone": contactDerefString(row.Phone),
"mobiles": contactDerefString(row.Mobiles),
"work_phone": contactDerefString(row.WorkPhone),
"email": contactDerefString(row.Email),
"wechat": contactDerefString(row.Wechat),
"qq": contactDerefString(row.QQ),
"avatar": contactDerefString(row.Avatar),
"company_name": contactDerefString(row.CompanyName),
"dept_name": contactDerefString(row.DeptName),
"position_title": contactDerefString(row.PositionTitle),
"address": contactDerefString(row.Address),
"remark": contactDerefString(row.Remark),
"is_starred": row.IsStarred,
"status": row.Status,
}
}
// appContactOrgNames 批量查询组织 ID → 名称映射,避免逐行查询。
func appContactOrgNames(ids []uint64) map[uint64]string {
names := make(map[uint64]string)
if len(ids) == 0 {
return names
}
var orgs []models.BackendOrganization
if _, err := models.Orm.QueryTable(new(models.BackendOrganization)).
Filter("id__in", ids).All(&orgs); err == nil {
for _, o := range orgs {
names[o.ID] = o.OrgName
}
}
return names
}
// ContactList GET /app/oa/contact/list 通讯录分页列表。
// 支持 keyword(姓名/手机号/邮箱/职位/部门)、org_id(按组织筛选)、contact_type(1 内部 2 外部)。
func (c *AppOaController) ContactList() {
claims, err := c.oaClaims()
if err != nil {
code := identity.Status(err)
c.oaErr(code, code, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
keyword := strings.TrimSpace(c.GetString("keyword"))
orgID, _ := c.GetUint64("org_id")
contactType, _ := c.GetInt("contact_type")
qs := models.Orm.QueryTable(new(models.BackendErpContact)).
Filter("delete_time__isnull", true).
Exclude("status", 0).
Filter("tid", claims.TenantId)
if orgID > 0 {
qs = qs.Filter("org_id", orgID)
}
if contactType > 0 {
qs = qs.Filter("contact_type", contactType)
}
if keyword != "" {
cond := orm.NewCondition()
cond = cond.Or("contact_name__contains", keyword)
cond = cond.Or("phone__contains", keyword)
cond = cond.Or("mobiles__contains", keyword)
cond = cond.Or("email__contains", keyword)
cond = cond.Or("position_title__contains", keyword)
cond = cond.Or("dept_name__contains", keyword)
// 用 GetCond().AndCond 追加,避免 SetCond 覆盖掉租户等前置过滤条件
qs = qs.SetCond(qs.GetCond().AndCond(cond))
}
total, _ := qs.Count()
var rows []models.BackendErpContact
if _, err := qs.OrderBy("-is_starred", "contact_name", "-id").
Limit(pageSize).Offset((page - 1) * pageSize).All(&rows); err != nil && err != orm.ErrNoRows {
c.oaErr(500, 500, "查询通讯录失败")
return
}
ids := make([]uint64, 0, len(rows))
for _, r := range rows {
if r.OrgID != nil && *r.OrgID > 0 {
ids = append(ids, *r.OrgID)
}
}
names := appContactOrgNames(ids)
list := make([]map[string]interface{}, 0, len(rows))
for _, r := range rows {
orgName := ""
if r.OrgID != nil {
orgName = names[*r.OrgID]
}
list = append(list, appContactDTO(r, orgName))
}
c.oaOk(map[string]interface{}{"list": list, "total": total, "page": page, "pageSize": pageSize})
}
// ContactOrgTree GET /app/oa/contact/org-tree 组织架构树(含各部门内部联系人数量)。
func (c *AppOaController) ContactOrgTree() {
claims, err := c.oaClaims()
if err != nil {
code := identity.Status(err)
c.oaErr(code, code, err.Error())
return
}
var orgs []models.BackendOrganization
if _, err := models.Orm.QueryTable(new(models.BackendOrganization)).
Filter("delete_time__isnull", true).
Exclude("status", 0).
Filter("tid", claims.TenantId).
OrderBy("sort", "id").All(&orgs); err != nil && err != orm.ErrNoRows {
c.oaErr(500, 500, "查询组织架构失败")
return
}
counts := make(map[uint64]int64)
for _, org := range orgs {
cnt, _ := models.Orm.QueryTable(new(models.BackendErpContact)).
Filter("org_id", org.ID).
Filter("tid", claims.TenantId).
Filter("delete_time__isnull", true).
Exclude("status", 0).
Filter("contact_type", 1).Count()
counts[org.ID] = cnt
}
total, _ := models.Orm.QueryTable(new(models.BackendErpContact)).
Filter("tid", claims.TenantId).
Filter("delete_time__isnull", true).
Exclude("status", 0).
Filter("contact_type", 1).Count()
tree := (&BackendErpContactController{}).buildContactOrgTree(orgs, counts, uint64(total))
c.oaOk(tree)
}
// ContactDetail GET /app/oa/contact/:id 联系人详情。
func (c *AppOaController) ContactDetail() {
claims, err := c.oaClaims()
if err != nil {
code := identity.Status(err)
c.oaErr(code, code, err.Error())
return
}
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if id == 0 {
c.oaErr(400, 400, "无效的联系人ID")
return
}
var row models.BackendErpContact
if models.Orm.QueryTable(new(models.BackendErpContact)).
Filter("id", id).
Filter("tid", claims.TenantId).
Filter("delete_time__isnull", true).
Exclude("status", 0).One(&row) != nil {
c.oaErr(404, 404, "联系人不存在")
return
}
orgName := ""
if row.OrgID != nil && *row.OrgID > 0 {
var org models.BackendOrganization
if models.Orm.QueryTable(new(models.BackendOrganization)).
Filter("id", *row.OrgID).One(&org) == nil {
orgName = org.OrgName
}
}
c.oaOk(appContactDTO(row, orgName))
}