995 lines
28 KiB
Go
995 lines
28 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/models"
|
|
"server/pkg/jwtutil"
|
|
|
|
"github.com/beego/beego/v2/client/orm"
|
|
beego "github.com/beego/beego/v2/server/web"
|
|
)
|
|
|
|
// BackendErpContactController 通讯录管理接口
|
|
type BackendErpContactController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
// contactClaims 从请求头解析登录态 JWT,返回后端用户 Claims
|
|
func (c *BackendErpContactController) contactClaims() (*jwtutil.Claims, error) {
|
|
auth := c.Ctx.Request.Header.Get("Authorization")
|
|
if auth == "" {
|
|
return nil, fmt.Errorf("未登录")
|
|
}
|
|
parts := strings.SplitN(auth, " ", 2)
|
|
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
|
|
return nil, fmt.Errorf("认证信息格式错误")
|
|
}
|
|
claims, err := jwtutil.ParseToken(parts[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("无效的token")
|
|
}
|
|
if claims.UserType != "backend" {
|
|
return nil, fmt.Errorf("无权访问")
|
|
}
|
|
return claims, nil
|
|
}
|
|
|
|
// contactTenantID 获取当前请求的租户ID:优先使用登录态 JWT 中的租户,
|
|
// 仅当显式传入 tid 参数时才以参数为准(兼容管理端等显式指定场景)。
|
|
func (c *BackendErpContactController) contactTenantID() (uint64, error) {
|
|
claims, err := c.contactClaims()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
tid := uint64(claims.TenantId)
|
|
if v, e := c.GetInt64("tid"); e == nil && v > 0 {
|
|
tid = uint64(v)
|
|
}
|
|
return tid, nil
|
|
}
|
|
|
|
type erpContactDTO struct {
|
|
ID uint64 `json:"id"`
|
|
Tid uint64 `json:"tid"`
|
|
EmployeeID uint64 `json:"employee_id"`
|
|
OrgID uint64 `json:"org_id"`
|
|
OrgName string `json:"org_name"`
|
|
ContactType int8 `json:"contact_type"`
|
|
ContactName string `json:"contact_name"`
|
|
Gender int8 `json:"gender"`
|
|
Phone string `json:"phone"`
|
|
// 手机号可登记多个(有的人有多个号码):JSON 数组,最多 5 个;phone 保留为第一个
|
|
Mobiles string `json:"mobiles"`
|
|
WorkPhone string `json:"work_phone"`
|
|
Email string `json:"email"`
|
|
Wechat string `json:"wechat"`
|
|
QQ string `json:"qq"`
|
|
Avatar string `json:"avatar"`
|
|
CompanyName string `json:"company_name"`
|
|
DeptName string `json:"dept_name"`
|
|
PositionID uint64 `json:"position_id"`
|
|
PositionTitle string `json:"position_title"`
|
|
Address string `json:"address"`
|
|
Remark string `json:"remark"`
|
|
IsStarred int8 `json:"is_starred"`
|
|
Sort uint `json:"sort"`
|
|
Status int8 `json:"status"`
|
|
CreateTime string `json:"create_time"`
|
|
}
|
|
|
|
// List 获取通讯录列表(支持分页、搜索、按组织筛选)
|
|
// GET /backend/erp/contact/list
|
|
func (c *BackendErpContactController) List() {
|
|
tid, err := c.contactTenantID()
|
|
if err != nil {
|
|
c.contactJsonError(401, "未登录或无权访问")
|
|
return
|
|
}
|
|
page, _ := c.GetInt("page", 1)
|
|
pageSize, _ := c.GetInt("page_size", 20)
|
|
keyword := strings.TrimSpace(c.GetString("keyword"))
|
|
orgID, _ := c.GetUint64("org_id")
|
|
contactType, _ := c.GetInt("contact_type")
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 20
|
|
}
|
|
|
|
qs := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("delete_time__isnull", true).
|
|
Exclude("status", 0).
|
|
Filter("tid", tid)
|
|
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("email__contains", keyword)
|
|
cond = cond.Or("wechat__contains", keyword)
|
|
cond = cond.Or("qq__contains", keyword)
|
|
qs = qs.SetCond(cond)
|
|
}
|
|
|
|
total, err := qs.Count()
|
|
if err != nil {
|
|
c.contactJsonError(500, "查询通讯录失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
var rows []models.BackendErpContact
|
|
_, err = qs.OrderBy("-is_starred", "contact_name", "-id").
|
|
Limit(pageSize, (page-1)*pageSize).
|
|
All(&rows)
|
|
if err != nil {
|
|
c.contactJsonError(500, "查询通讯录失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
list := make([]erpContactDTO, 0, len(rows))
|
|
for _, row := range rows {
|
|
list = append(list, c.contactDTO(row))
|
|
}
|
|
|
|
c.contactJsonOK(map[string]interface{}{
|
|
"list": list,
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
})
|
|
}
|
|
|
|
// Detail 获取通讯录详情
|
|
// GET /backend/erp/contact/detail/:id
|
|
func (c *BackendErpContactController) Detail() {
|
|
id, ok := c.contactPathUint64(":id")
|
|
if !ok {
|
|
c.contactJsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
var row models.BackendErpContact
|
|
err := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("id", id).
|
|
Filter("delete_time__isnull", true).
|
|
One(&row)
|
|
if err != nil {
|
|
c.contactJsonError(404, "联系人不存在")
|
|
return
|
|
}
|
|
|
|
c.contactJsonOK(c.contactDTO(row))
|
|
}
|
|
|
|
// Create 创建联系人(手动添加外部联系人)
|
|
// POST /backend/erp/contact/create
|
|
func (c *BackendErpContactController) Create() {
|
|
body := c.contactParseJSONBody()
|
|
|
|
contactName, _ := c.contactGetStringValue(body, "contact_name", "name")
|
|
contactName = strings.TrimSpace(contactName)
|
|
if contactName == "" {
|
|
c.contactJsonError(400, "联系人姓名不能为空")
|
|
return
|
|
}
|
|
|
|
tid, _ := c.contactGetUint64Value(body, "tid", "tenant_id")
|
|
orgID, _ := c.contactGetUint64Value(body, "org_id")
|
|
contactType, _ := c.contactGetIntValue(body, "contact_type")
|
|
gender, _ := c.contactGetIntValue(body, "gender")
|
|
phone, _ := c.contactGetStringValue(body, "phone")
|
|
mobilesJSON, _ := c.contactGetStringValue(body, "mobiles")
|
|
// 多手机号:优先使用 mobiles 字段;未传手机号时从 mobiles 取第一个回填 phone(兼容旧调用方)
|
|
if strings.TrimSpace(phone) == "" {
|
|
phone = firstMobile(strings.TrimSpace(mobilesJSON))
|
|
}
|
|
workPhone, _ := c.contactGetStringValue(body, "work_phone")
|
|
email, _ := c.contactGetStringValue(body, "email")
|
|
wechat, _ := c.contactGetStringValue(body, "wechat")
|
|
qq, _ := c.contactGetStringValue(body, "qq")
|
|
avatar, _ := c.contactGetStringValue(body, "avatar")
|
|
companyName, _ := c.contactGetStringValue(body, "company_name")
|
|
deptName, _ := c.contactGetStringValue(body, "dept_name")
|
|
positionID, _ := c.contactGetUint64Value(body, "position_id")
|
|
positionTitle, _ := c.contactGetStringValue(body, "position_title")
|
|
address, _ := c.contactGetStringValue(body, "address")
|
|
remark, _ := c.contactGetStringValue(body, "remark")
|
|
sortVal, _ := c.contactGetUintValue(body, "sort")
|
|
|
|
if contactType == 0 {
|
|
contactType = 2 // 默认外部联系人
|
|
}
|
|
|
|
row := models.BackendErpContact{
|
|
Tid: tid,
|
|
ContactType: int8(contactType),
|
|
ContactName: contactName,
|
|
Gender: int8(gender),
|
|
Phone: contactStrPtr(phone),
|
|
Mobiles: contactStrPtr(mobilesJSON),
|
|
WorkPhone: contactStrPtr(workPhone),
|
|
Email: contactStrPtr(email),
|
|
Wechat: contactStrPtr(wechat),
|
|
QQ: contactStrPtr(qq),
|
|
Avatar: contactStrPtr(avatar),
|
|
CompanyName: contactStrPtr(companyName),
|
|
DeptName: contactStrPtr(deptName),
|
|
PositionTitle: contactStrPtr(positionTitle),
|
|
Address: contactStrPtr(address),
|
|
Remark: contactStrPtr(remark),
|
|
Status: 1,
|
|
Sort: sortVal,
|
|
}
|
|
if orgID > 0 {
|
|
row.OrgID = &orgID
|
|
}
|
|
if positionID > 0 {
|
|
row.PositionID = &positionID
|
|
}
|
|
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
c.contactJsonError(500, "创建联系人失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.contactJsonOK(map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Update 更新联系人
|
|
// POST /backend/erp/contact/update/:id
|
|
func (c *BackendErpContactController) Update() {
|
|
id, ok := c.contactPathUint64(":id")
|
|
if !ok {
|
|
c.contactJsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
body := c.contactParseJSONBody()
|
|
update := orm.Params{}
|
|
|
|
if v, has := c.contactGetStringValue(body, "contact_name", "name"); has {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
c.contactJsonError(400, "联系人姓名不能为空")
|
|
return
|
|
}
|
|
update["contact_name"] = v
|
|
}
|
|
if v, has := c.contactGetUint64Value(body, "org_id"); has {
|
|
if v == 0 {
|
|
update["org_id"] = nil
|
|
} else {
|
|
update["org_id"] = v
|
|
}
|
|
}
|
|
if v, has := c.contactGetIntValue(body, "contact_type"); has {
|
|
update["contact_type"] = int8(v)
|
|
}
|
|
if v, has := c.contactGetIntValue(body, "gender"); has {
|
|
update["gender"] = int8(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "phone"); has {
|
|
update["phone"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "mobiles"); has {
|
|
update["mobiles"] = contactNullableString(v)
|
|
// 多手机号下,phone 始终为第一个号码,保持两者一致
|
|
if p := firstMobile(strings.TrimSpace(v)); p != "" {
|
|
update["phone"] = contactNullableString(p)
|
|
}
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "work_phone"); has {
|
|
update["work_phone"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "email"); has {
|
|
update["email"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "wechat"); has {
|
|
update["wechat"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "qq"); has {
|
|
update["qq"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "avatar"); has {
|
|
update["avatar"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "company_name"); has {
|
|
update["company_name"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "dept_name"); has {
|
|
update["dept_name"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetUint64Value(body, "position_id"); has {
|
|
if v == 0 {
|
|
update["position_id"] = nil
|
|
} else {
|
|
update["position_id"] = v
|
|
}
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "position_title"); has {
|
|
update["position_title"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "address"); has {
|
|
update["address"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetStringValue(body, "remark"); has {
|
|
update["remark"] = contactNullableString(v)
|
|
}
|
|
if v, has := c.contactGetIntValue(body, "is_starred"); has {
|
|
update["is_starred"] = int8(v)
|
|
}
|
|
if v, has := c.contactGetUintValue(body, "sort"); has {
|
|
update["sort"] = v
|
|
}
|
|
if v, has := c.contactGetIntValue(body, "status"); has {
|
|
update["status"] = int8(v)
|
|
}
|
|
|
|
if len(update) == 0 {
|
|
c.contactJsonError(400, "无更新字段")
|
|
return
|
|
}
|
|
|
|
num, err := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("id", id).
|
|
Filter("delete_time__isnull", true).
|
|
Update(update)
|
|
if err != nil {
|
|
c.contactJsonError(500, "更新联系人失败: "+err.Error())
|
|
return
|
|
}
|
|
if num == 0 {
|
|
c.contactJsonError(404, "联系人不存在")
|
|
return
|
|
}
|
|
|
|
c.contactJsonOK(nil)
|
|
}
|
|
|
|
// Delete 删除联系人(软删除)
|
|
// DELETE /backend/erp/contact/delete/:id
|
|
func (c *BackendErpContactController) Delete() {
|
|
id, ok := c.contactPathUint64(":id")
|
|
if !ok {
|
|
c.contactJsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
now := time.Now().Format("2006-01-02 15:04:05")
|
|
num, err := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("id", id).
|
|
Filter("delete_time__isnull", true).
|
|
Update(orm.Params{"delete_time": now, "status": 0})
|
|
if err != nil {
|
|
c.contactJsonError(500, "删除联系人失败: "+err.Error())
|
|
return
|
|
}
|
|
if num == 0 {
|
|
c.contactJsonError(404, "联系人不存在")
|
|
return
|
|
}
|
|
|
|
c.contactJsonOK(nil)
|
|
}
|
|
|
|
// Star 收藏/取消收藏联系人
|
|
// POST /backend/erp/contact/star/:id
|
|
func (c *BackendErpContactController) Star() {
|
|
id, ok := c.contactPathUint64(":id")
|
|
if !ok {
|
|
c.contactJsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
body := c.contactParseJSONBody()
|
|
starred, _ := c.contactGetIntValue(body, "is_starred", "starred")
|
|
|
|
num, err := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("id", id).
|
|
Filter("delete_time__isnull", true).
|
|
Update(orm.Params{"is_starred": int8(starred)})
|
|
if err != nil {
|
|
c.contactJsonError(500, "操作失败: "+err.Error())
|
|
return
|
|
}
|
|
if num == 0 {
|
|
c.contactJsonError(404, "联系人不存在")
|
|
return
|
|
}
|
|
|
|
c.contactJsonOK(nil)
|
|
}
|
|
|
|
// SyncAllContacts 全量同步:将所有员工同步到通讯录
|
|
// POST /backend/erp/contact/syncAll
|
|
func (c *BackendErpContactController) SyncAllContacts() {
|
|
tid, _ := c.GetInt64("tid")
|
|
|
|
qs := models.Orm.QueryTable(new(models.BackendEmployee)).
|
|
Filter("delete_time__isnull", true).
|
|
Exclude("account_status", 2)
|
|
if tid > 0 {
|
|
qs = qs.Filter("tid", tid)
|
|
}
|
|
|
|
var employees []models.BackendEmployee
|
|
_, err := qs.All(&employees)
|
|
if err != nil {
|
|
c.contactJsonError(500, "查询员工失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
created := 0
|
|
updated := 0
|
|
for _, emp := range employees {
|
|
tidVal := uint64(0)
|
|
if emp.Tid != nil {
|
|
tidVal = uint64(*emp.Tid)
|
|
}
|
|
empID := uint64(emp.ID)
|
|
result := syncContactFromEmployee(tidVal, empID, &emp)
|
|
if result == "created" {
|
|
created++
|
|
} else if result == "updated" {
|
|
updated++
|
|
}
|
|
}
|
|
|
|
c.contactJsonOK(map[string]interface{}{
|
|
"total": len(employees),
|
|
"created": created,
|
|
"updated": updated,
|
|
})
|
|
}
|
|
|
|
// GetContactOrgTree 获取通讯录组织树(带各部门联系人数量)
|
|
// GET /backend/erp/contact/orgTree
|
|
func (c *BackendErpContactController) GetContactOrgTree() {
|
|
tid, err := c.contactTenantID()
|
|
if err != nil {
|
|
c.contactJsonError(401, "未登录或无权访问")
|
|
return
|
|
}
|
|
|
|
qs := models.Orm.QueryTable(new(models.BackendOrganization)).
|
|
Filter("delete_time__isnull", true).
|
|
Exclude("status", 0).
|
|
Filter("tid", tid)
|
|
|
|
var orgs []models.BackendOrganization
|
|
_, err = qs.OrderBy("sort", "id").All(&orgs)
|
|
if err != nil {
|
|
c.contactJsonError(500, "查询组织架构失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
orgContactCounts := make(map[uint64]int64)
|
|
cqs := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("delete_time__isnull", true).
|
|
Exclude("status", 0).
|
|
Filter("contact_type", 1).
|
|
Filter("tid", tid)
|
|
|
|
type orgCount struct {
|
|
OrgID uint64 `orm:"column(org_id)"`
|
|
Count int64 `orm:"column(cnt)"`
|
|
}
|
|
var counts []orgCount
|
|
_, err = cqs.GroupBy("org_id").All(&counts, "org_id")
|
|
if err == nil {
|
|
// Beego ORM GroupBy doesn't support raw count in All, so count manually
|
|
}
|
|
|
|
// Fallback: count per org manually
|
|
for _, org := range orgs {
|
|
cnt, _ := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("org_id", org.ID).
|
|
Filter("tid", tid).
|
|
Filter("delete_time__isnull", true).
|
|
Exclude("status", 0).
|
|
Filter("contact_type", 1).
|
|
Count()
|
|
orgContactCounts[org.ID] = cnt
|
|
}
|
|
|
|
// Also get total internal contacts count for root
|
|
totalInternal, _ := cqs.Count()
|
|
|
|
// Build tree
|
|
tree := c.buildContactOrgTree(orgs, orgContactCounts, uint64(totalInternal))
|
|
|
|
c.contactJsonOK(tree)
|
|
}
|
|
|
|
// buildContactOrgTree 构建通讯录组织树
|
|
func (c *BackendErpContactController) buildContactOrgTree(
|
|
orgs []models.BackendOrganization,
|
|
counts map[uint64]int64,
|
|
totalInternal uint64,
|
|
) []map[string]interface{} {
|
|
nodeMap := make(map[uint64]map[string]interface{})
|
|
tree := make([]map[string]interface{}, 0)
|
|
|
|
for _, org := range orgs {
|
|
node := map[string]interface{}{
|
|
"id": org.ID,
|
|
"org_name": org.OrgName,
|
|
"parent_id": org.ParentID,
|
|
"is_company": org.IsCompany,
|
|
"status": org.Status,
|
|
"contact_count": counts[org.ID],
|
|
"children": make([]map[string]interface{}, 0),
|
|
}
|
|
nodeMap[org.ID] = node
|
|
}
|
|
|
|
for _, org := range orgs {
|
|
node := nodeMap[org.ID]
|
|
if org.ParentID == 0 {
|
|
node["contact_count"] = int64(totalInternal)
|
|
tree = append(tree, node)
|
|
} else {
|
|
if parent, exists := nodeMap[org.ParentID]; exists {
|
|
parent["children"] = append(parent["children"].([]map[string]interface{}), node)
|
|
}
|
|
}
|
|
}
|
|
|
|
return tree
|
|
}
|
|
|
|
// --- DTO builder ---
|
|
|
|
func (c *BackendErpContactController) contactDTO(row models.BackendErpContact) erpContactDTO {
|
|
orgName := ""
|
|
if row.OrgID != nil && *row.OrgID > 0 {
|
|
var org models.BackendOrganization
|
|
if err := models.Orm.QueryTable(new(models.BackendOrganization)).
|
|
Filter("id", *row.OrgID).
|
|
One(&org); err == nil {
|
|
orgName = org.OrgName
|
|
}
|
|
}
|
|
|
|
return erpContactDTO{
|
|
ID: row.ID,
|
|
Tid: row.Tid,
|
|
EmployeeID: contactDerefUint64(row.EmployeeID),
|
|
OrgID: contactDerefUint64(row.OrgID),
|
|
OrgName: orgName,
|
|
ContactType: row.ContactType,
|
|
ContactName: row.ContactName,
|
|
Gender: row.Gender,
|
|
Phone: contactDerefString(row.Phone),
|
|
Mobiles: contactDerefString(row.Mobiles),
|
|
WorkPhone: contactDerefString(row.WorkPhone),
|
|
Email: contactDerefString(row.Email),
|
|
Wechat: contactDerefString(row.Wechat),
|
|
QQ: contactDerefString(row.QQ),
|
|
Avatar: contactDerefString(row.Avatar),
|
|
CompanyName: contactDerefString(row.CompanyName),
|
|
DeptName: contactDerefString(row.DeptName),
|
|
PositionID: contactDerefUint64(row.PositionID),
|
|
PositionTitle: contactDerefString(row.PositionTitle),
|
|
Address: contactDerefString(row.Address),
|
|
Remark: contactDerefString(row.Remark),
|
|
IsStarred: row.IsStarred,
|
|
Sort: row.Sort,
|
|
Status: row.Status,
|
|
CreateTime: row.CreateTime.Format("2006-01-02 15:04:05"),
|
|
}
|
|
}
|
|
|
|
// --- Sync from Employee ---
|
|
|
|
// SyncContactOnEmployeeCreate 员工创建后同步到通讯录(由组织架构控制器调用)
|
|
func SyncContactOnEmployeeCreate(tid uint64, employeeID uint64, emp *models.BackendEmployee) {
|
|
syncContactFromEmployee(tid, employeeID, emp)
|
|
}
|
|
|
|
// SyncContactOnEmployeeRefresh 员工信息变更后重新全量同步通讯录(由组织架构控制器调用)。
|
|
// 走的是与建档同步同一套逻辑:先取员工基础字段,再用人事档案字段覆盖(工作邮箱、家庭地址优先)。
|
|
func SyncContactOnEmployeeRefresh(tid uint64, employeeID uint64) {
|
|
var emp models.BackendEmployee
|
|
if err := models.Orm.QueryTable(new(models.BackendEmployee)).
|
|
Filter("id", employeeID).
|
|
Filter("delete_time__isnull", true).
|
|
One(&emp); err != nil {
|
|
return
|
|
}
|
|
syncContactFromEmployee(tid, employeeID, &emp)
|
|
}
|
|
|
|
// SyncContactOnEmployeeFileChange 人事档案建档/变更后同步通讯录(由 OA 档案控制器调用)。
|
|
// 通讯录以 employee_id 关联员工,档案里的字段(工作邮箱、家庭地址)优先级高于员工自身字段。
|
|
// 只做字段同步:不会新增/删除员工,也不会清理通讯录记录。
|
|
func SyncContactOnEmployeeFileChange(tid uint64, employeeID uint64) {
|
|
SyncContactOnEmployeeRefresh(tid, employeeID)
|
|
}
|
|
|
|
// SyncContactOnEmployeeUpdate 员工更新后同步到通讯录(由 ERP 控制器调用)
|
|
func SyncContactOnEmployeeUpdate(employeeID uint64, update orm.Params) {
|
|
var contact models.BackendErpContact
|
|
err := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("employee_id", employeeID).
|
|
Filter("delete_time__isnull", true).
|
|
One(&contact)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
contactUpdate := orm.Params{}
|
|
if v, ok := update["name"]; ok {
|
|
contactUpdate["contact_name"] = v
|
|
}
|
|
if v, ok := update["phone"]; ok {
|
|
contactUpdate["phone"] = v
|
|
}
|
|
if v, ok := update["email"]; ok {
|
|
contactUpdate["email"] = v
|
|
}
|
|
if v, ok := update["wechat"]; ok {
|
|
contactUpdate["wechat"] = v
|
|
}
|
|
if v, ok := update["gender"]; ok {
|
|
contactUpdate["gender"] = v
|
|
}
|
|
if v, ok := update["department"]; ok {
|
|
if v == nil {
|
|
contactUpdate["org_id"] = nil
|
|
} else {
|
|
switch dept := v.(type) {
|
|
case string:
|
|
orgID, err := strconv.ParseUint(strings.TrimSpace(dept), 10, 64)
|
|
if err == nil && orgID > 0 {
|
|
contactUpdate["org_id"] = orgID
|
|
} else {
|
|
contactUpdate["org_id"] = nil
|
|
}
|
|
case uint64:
|
|
if dept > 0 {
|
|
contactUpdate["org_id"] = dept
|
|
} else {
|
|
contactUpdate["org_id"] = nil
|
|
}
|
|
case uint:
|
|
if dept > 0 {
|
|
contactUpdate["org_id"] = uint64(dept)
|
|
} else {
|
|
contactUpdate["org_id"] = nil
|
|
}
|
|
default:
|
|
contactUpdate["org_id"] = v
|
|
}
|
|
}
|
|
}
|
|
if v, ok := update["position"]; ok {
|
|
contactUpdate["position_title"] = v
|
|
}
|
|
if v, ok := update["home_address"]; ok {
|
|
contactUpdate["address"] = v
|
|
}
|
|
// 员工账号状态(启用/禁用/离职)不同步到通讯录:
|
|
// 通讯录是企业数据,员工离职或账号被删除都不代表联系人应当停用,是否停用由通讯录模块自行维护。
|
|
|
|
if len(contactUpdate) > 0 {
|
|
models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("id", contact.ID).
|
|
Update(contactUpdate)
|
|
}
|
|
}
|
|
|
|
// SyncContactOnEmployeeDelete 清理员工关联的通讯录记录。
|
|
// 注意:员工离职/禁用(deleteEmployee)与后台账号删除(deleteUser)都不得调用本函数——
|
|
// 通讯录是企业数据,账号或状态变化不等于联系人消失。仅在员工数据被真正物理清理时使用。
|
|
func SyncContactOnEmployeeDelete(employeeID uint64) {
|
|
now := time.Now().Format("2006-01-02 15:04:05")
|
|
models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("employee_id", employeeID).
|
|
Filter("delete_time__isnull", true).
|
|
Update(orm.Params{"delete_time": now, "status": 0})
|
|
}
|
|
|
|
// syncContactFromEmployee 内部同步函数
|
|
// 从员工信息和人事档案同步数据到通讯录
|
|
func syncContactFromEmployee(tid uint64, employeeID uint64, emp *models.BackendEmployee) string {
|
|
var existing models.BackendErpContact
|
|
err := models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("employee_id", employeeID).
|
|
Filter("delete_time__isnull", true).
|
|
One(&existing)
|
|
|
|
// 获取人事档案数据
|
|
fileData := getEmployeeFileForContact(employeeID)
|
|
|
|
// 通讯录字段优先级:人事档案 > 员工(档案字段为空时回退员工自身字段)
|
|
email := contactFirstNonEmptyPtr(&fileData.WorkEmail, emp.Email)
|
|
address := contactFirstNonEmptyPtr(&fileData.HouseholdAddress, &fileData.CurrentAddress, emp.HomeAddress)
|
|
|
|
if err != nil {
|
|
// Not found - create new contact
|
|
var orgID *uint64
|
|
if emp.Department != nil {
|
|
if oid, err := strconv.ParseUint(strings.TrimSpace(*emp.Department), 10, 64); err == nil && oid > 0 {
|
|
orgID = &oid
|
|
}
|
|
}
|
|
|
|
contact := models.BackendErpContact{
|
|
Tid: tid,
|
|
EmployeeID: &employeeID,
|
|
OrgID: orgID,
|
|
ContactType: 1,
|
|
ContactName: emp.Name,
|
|
Gender: emp.Gender,
|
|
Phone: emp.Phone,
|
|
Email: email,
|
|
Wechat: emp.Wechat,
|
|
PositionTitle: emp.Position,
|
|
Address: address,
|
|
Status: 1,
|
|
}
|
|
models.Orm.Insert(&contact)
|
|
return "created"
|
|
}
|
|
|
|
// Found - update
|
|
update := orm.Params{
|
|
"contact_name": emp.Name,
|
|
"gender": emp.Gender,
|
|
"phone": emp.Phone,
|
|
"email": email,
|
|
"wechat": emp.Wechat,
|
|
"position_title": emp.Position,
|
|
"address": address,
|
|
}
|
|
if emp.Department != nil {
|
|
if oid, err := strconv.ParseUint(strings.TrimSpace(*emp.Department), 10, 64); err == nil && oid > 0 {
|
|
update["org_id"] = oid
|
|
} else {
|
|
update["org_id"] = nil
|
|
}
|
|
} else {
|
|
update["org_id"] = nil
|
|
}
|
|
|
|
models.Orm.QueryTable(new(models.BackendErpContact)).
|
|
Filter("id", existing.ID).
|
|
Update(update)
|
|
return "updated"
|
|
}
|
|
|
|
// getEmployeeFileForContact 获取员工人事档案数据(用于通讯录同步)
|
|
// 返回档案中与通讯录相关的字段
|
|
func getEmployeeFileForContact(employeeID uint64) struct {
|
|
WorkEmail string
|
|
HouseholdAddress string
|
|
CurrentAddress string
|
|
EmergencyContact string
|
|
EmergencyPhone string
|
|
EmergencyRelation string
|
|
EmploymentStatus int8
|
|
} {
|
|
var file models.BackendEmployeeFile
|
|
err := models.Orm.QueryTable(new(models.BackendEmployeeFile)).
|
|
Filter("employee_id", employeeID).
|
|
Filter("is_deleted", 0).
|
|
One(&file)
|
|
|
|
if err != nil {
|
|
// 档案不存在,返回空值
|
|
return struct {
|
|
WorkEmail string
|
|
HouseholdAddress string
|
|
CurrentAddress string
|
|
EmergencyContact string
|
|
EmergencyPhone string
|
|
EmergencyRelation string
|
|
EmploymentStatus int8
|
|
}{}
|
|
}
|
|
|
|
return struct {
|
|
WorkEmail string
|
|
HouseholdAddress string
|
|
CurrentAddress string
|
|
EmergencyContact string
|
|
EmergencyPhone string
|
|
EmergencyRelation string
|
|
EmploymentStatus int8
|
|
}{
|
|
WorkEmail: file.WorkEmail,
|
|
HouseholdAddress: file.HouseholdAddress,
|
|
CurrentAddress: file.CurrentAddress,
|
|
EmergencyContact: file.EmergencyContact,
|
|
EmergencyPhone: file.EmergencyPhone,
|
|
EmergencyRelation: file.EmergencyRelation,
|
|
EmploymentStatus: file.EmploymentStatus,
|
|
}
|
|
}
|
|
|
|
// --- Helper functions (package-level, shared with ERP controller) ---
|
|
|
|
func contactDerefString(v *string) string {
|
|
if v == nil {
|
|
return ""
|
|
}
|
|
return *v
|
|
}
|
|
|
|
func contactDerefUint64(v *uint64) uint64 {
|
|
if v == nil {
|
|
return 0
|
|
}
|
|
return *v
|
|
}
|
|
|
|
func contactStrPtr(v string) *string {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
return &v
|
|
}
|
|
|
|
func contactNullableString(v string) interface{} {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
return v
|
|
}
|
|
|
|
// --- Controller-level helpers ---
|
|
|
|
func (c *BackendErpContactController) contactParseJSONBody() map[string]interface{} {
|
|
body := map[string]interface{}{}
|
|
contentType := strings.ToLower(c.Ctx.Input.Header("Content-Type"))
|
|
if !strings.Contains(contentType, "json") {
|
|
return body
|
|
}
|
|
if len(c.Ctx.Input.RequestBody) == 0 {
|
|
return body
|
|
}
|
|
_ = json.Unmarshal(c.Ctx.Input.RequestBody, &body)
|
|
return body
|
|
}
|
|
|
|
func (c *BackendErpContactController) contactGetStringValue(body map[string]interface{}, keys ...string) (string, bool) {
|
|
for _, key := range keys {
|
|
if v, ok := body[key]; ok {
|
|
switch val := v.(type) {
|
|
case string:
|
|
return val, true
|
|
case float64:
|
|
return strconv.FormatFloat(val, 'f', -1, 64), true
|
|
case bool:
|
|
return strconv.FormatBool(val), true
|
|
default:
|
|
b, _ := json.Marshal(v)
|
|
return strings.TrimSpace(strings.Trim(strings.ReplaceAll(strings.ReplaceAll(string(b), "\n", ""), "\r", ""), "\"")), true
|
|
}
|
|
}
|
|
if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil {
|
|
_ = c.Ctx.Request.ParseMultipartForm(32 << 20)
|
|
}
|
|
if val := c.GetString(key); val != "" {
|
|
return val, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (c *BackendErpContactController) contactGetIntValue(body map[string]interface{}, keys ...string) (int, bool) {
|
|
for _, key := range keys {
|
|
if v, ok := body[key]; ok {
|
|
switch val := v.(type) {
|
|
case float64:
|
|
return int(val), true
|
|
case int:
|
|
return val, true
|
|
case string:
|
|
if strings.TrimSpace(val) == "" {
|
|
return 0, true
|
|
}
|
|
parsed, err := strconv.Atoi(strings.TrimSpace(val))
|
|
return parsed, err == nil
|
|
}
|
|
}
|
|
if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil {
|
|
_ = c.Ctx.Request.ParseMultipartForm(32 << 20)
|
|
}
|
|
if val := c.GetString(key); val != "" {
|
|
parsed, err := strconv.Atoi(strings.TrimSpace(val))
|
|
return parsed, err == nil
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (c *BackendErpContactController) contactGetUintValue(body map[string]interface{}, keys ...string) (uint, bool) {
|
|
v, ok := c.contactGetIntValue(body, keys...)
|
|
if !ok || v < 0 {
|
|
return 0, ok
|
|
}
|
|
return uint(v), true
|
|
}
|
|
|
|
func (c *BackendErpContactController) contactGetUint64Value(body map[string]interface{}, keys ...string) (uint64, bool) {
|
|
for _, key := range keys {
|
|
if v, ok := body[key]; ok {
|
|
switch val := v.(type) {
|
|
case float64:
|
|
if val < 0 {
|
|
return 0, false
|
|
}
|
|
return uint64(val), true
|
|
case int:
|
|
if val < 0 {
|
|
return 0, false
|
|
}
|
|
return uint64(val), true
|
|
case string:
|
|
if strings.TrimSpace(val) == "" {
|
|
return 0, true
|
|
}
|
|
parsed, err := strconv.ParseUint(strings.TrimSpace(val), 10, 64)
|
|
return parsed, err == nil
|
|
}
|
|
}
|
|
if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil {
|
|
_ = c.Ctx.Request.ParseMultipartForm(32 << 20)
|
|
}
|
|
if val := c.GetString(key); val != "" {
|
|
parsed, err := strconv.ParseUint(strings.TrimSpace(val), 10, 64)
|
|
return parsed, err == nil
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (c *BackendErpContactController) contactPathUint64(name string) (uint64, bool) {
|
|
id, err := strconv.ParseUint(c.Ctx.Input.Param(name), 10, 64)
|
|
return id, err == nil && id > 0
|
|
}
|
|
|
|
func (c *BackendErpContactController) contactJsonOK(data interface{}) {
|
|
resp := map[string]interface{}{"code": 200, "msg": "success"}
|
|
if data != nil {
|
|
resp["data"] = data
|
|
}
|
|
c.Data["json"] = resp
|
|
_ = c.ServeJSON()
|
|
}
|
|
|
|
func (c *BackendErpContactController) contactJsonError(code int, msg string) {
|
|
c.Data["json"] = map[string]interface{}{"code": code, "msg": msg}
|
|
_ = c.ServeJSON()
|
|
}
|
|
|
|
// contactFirstNonEmptyPtr 按优先级取第一个非空字符串(去空格后判断),全部为空时返回 nil。
|
|
// 用于通讯录"档案字段 > 员工字段"的取值优先级,避免用空字符串覆盖已有数据。
|
|
func contactFirstNonEmptyPtr(values ...*string) *string {
|
|
for _, v := range values {
|
|
if v == nil {
|
|
continue
|
|
}
|
|
if s := strings.TrimSpace(*v); s != "" {
|
|
return &s
|
|
}
|
|
}
|
|
return nil
|
|
} |