增加档案功能
This commit is contained in:
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -68,15 +69,25 @@ func efIsValidDate(s string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// efNormalizeDate 将 ORM 读出的日期值统一为 "YYYY-MM-DD"。
|
||||
// DSN 开启 parseTime=True 后 DATE 列会被驱动解析为 time.Time,
|
||||
// 赋给 string 字段后是 Go 默认格式,取前 10 位(与 OA 日程一致)。
|
||||
func efNormalizeDate(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) >= 10 {
|
||||
return s[:10]
|
||||
// efNormalizeDate 将可空 DATE 列统一输出为 "YYYY-MM-DD"。
|
||||
func efNormalizeDate(value *time.Time) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
return value.Format("2006-01-02")
|
||||
}
|
||||
|
||||
// efDatePointer 将前端可选日期转为可空 DATE 值;空字符串明确保存为 SQL NULL。
|
||||
func efDatePointer(value string) *time.Time {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
date, err := time.ParseInLocation("2006-01-02", value, time.Local)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &date
|
||||
}
|
||||
|
||||
// efQuery 档案查询基座:强制附加租户过滤与未删除条件。
|
||||
@@ -204,6 +215,48 @@ type employeeFileDTO struct {
|
||||
Nation string `json:"nation"`
|
||||
HomeAddress string `json:"home_address"`
|
||||
AccountStatus int8 `json:"account_status"`
|
||||
HireDate string `json:"hire_date"`
|
||||
RegularDate string `json:"regular_date"`
|
||||
LeaveDate string `json:"leave_date"`
|
||||
Completeness int `json:"completeness"`
|
||||
}
|
||||
|
||||
// efFileCompleteness 计算档案资料完整度。仅统计应由档案模块维护的有效字段:
|
||||
// 离职日期仅在离职时要求,转正日期仅在正式员工时要求,备注为可选项不计入。
|
||||
func efFileCompleteness(file models.BackendEmployeeFile) int {
|
||||
items := []bool{
|
||||
strings.TrimSpace(file.IDCard) != "",
|
||||
strings.TrimSpace(file.PoliticalStatus) != "",
|
||||
file.MaritalStatus > 0,
|
||||
strings.TrimSpace(file.NativePlace) != "",
|
||||
strings.TrimSpace(file.HouseholdAddress) != "",
|
||||
strings.TrimSpace(file.CurrentAddress) != "",
|
||||
strings.TrimSpace(file.WorkEmail) != "",
|
||||
file.HireDate != nil,
|
||||
strings.TrimSpace(file.EmergencyContact) != "",
|
||||
strings.TrimSpace(file.EmergencyPhone) != "",
|
||||
strings.TrimSpace(file.EmergencyRelation) != "",
|
||||
strings.TrimSpace(file.EducationPhoto) != "",
|
||||
strings.TrimSpace(file.IDCardFront) != "",
|
||||
strings.TrimSpace(file.IDCardBack) != "",
|
||||
}
|
||||
if file.EmploymentStatus == 2 {
|
||||
items = append(items, file.RegularDate != nil)
|
||||
}
|
||||
if file.EmploymentStatus == 3 {
|
||||
items = append(items, file.LeaveDate != nil)
|
||||
}
|
||||
|
||||
filled := 0
|
||||
for _, item := range items {
|
||||
if item {
|
||||
filled++
|
||||
}
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return 0
|
||||
}
|
||||
return filled * 100 / len(items)
|
||||
}
|
||||
|
||||
// efAssembleDTOList 档案列表批量组装员工展示信息。
|
||||
@@ -230,6 +283,7 @@ func (c *BackendEmployeeFileController) efAssembleDTOList(tid int, files []model
|
||||
|
||||
for _, f := range files {
|
||||
dto := employeeFileDTO{BackendEmployeeFile: f}
|
||||
dto.Completeness = efFileCompleteness(f)
|
||||
dto.HireDate = efNormalizeDate(f.HireDate)
|
||||
dto.RegularDate = efNormalizeDate(f.RegularDate)
|
||||
dto.LeaveDate = efNormalizeDate(f.LeaveDate)
|
||||
@@ -386,11 +440,6 @@ func (c *BackendEmployeeFileController) Detail() {
|
||||
if records == nil {
|
||||
records = []models.BackendEmployeeFileRecord{}
|
||||
}
|
||||
for i := range records {
|
||||
records[i].StartDate = efNormalizeDate(records[i].StartDate)
|
||||
records[i].EndDate = efNormalizeDate(records[i].EndDate)
|
||||
}
|
||||
|
||||
list := c.efAssembleDTOList(tid, []models.BackendEmployeeFile{file})
|
||||
c.efOk(map[string]interface{}{"file": list[0], "records": records})
|
||||
}
|
||||
@@ -507,6 +556,11 @@ func (c *BackendEmployeeFileController) Create() {
|
||||
fileNo = "EF" + time.Now().Format("20060102150405")
|
||||
}
|
||||
|
||||
// 证照资料只能在建档完成后,于档案详情中维护;创建接口不接受附件。
|
||||
payload.IDCardFront = ""
|
||||
payload.IDCardBack = ""
|
||||
payload.EducationPhoto = ""
|
||||
|
||||
now := time.Now()
|
||||
item := &models.BackendEmployeeFile{
|
||||
Tid: tid,
|
||||
@@ -525,9 +579,9 @@ func (c *BackendEmployeeFileController) Create() {
|
||||
EmergencyPhone: payload.EmergencyPhone,
|
||||
EmergencyRelation: payload.EmergencyRelation,
|
||||
WorkEmail: payload.WorkEmail,
|
||||
HireDate: strings.TrimSpace(payload.HireDate),
|
||||
RegularDate: strings.TrimSpace(payload.RegularDate),
|
||||
LeaveDate: strings.TrimSpace(payload.LeaveDate),
|
||||
HireDate: efDatePointer(payload.HireDate),
|
||||
RegularDate: efDatePointer(payload.RegularDate),
|
||||
LeaveDate: efDatePointer(payload.LeaveDate),
|
||||
EmploymentStatus: payload.EmploymentStatus,
|
||||
Remark: payload.Remark,
|
||||
IsDeleted: 0,
|
||||
@@ -535,7 +589,8 @@ func (c *BackendEmployeeFileController) Create() {
|
||||
UpdateTime: &now,
|
||||
}
|
||||
if _, err := models.Orm.Insert(item); err != nil {
|
||||
c.efErr(500, 500, "保存失败")
|
||||
log.Printf("员工档案创建失败: tid=%d employee_id=%d err=%v", tid, payload.EmployeeID, err)
|
||||
c.efErr(500, 500, "保存失败,请检查员工档案表字段及日期数据")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -572,9 +627,7 @@ func (c *BackendEmployeeFileController) Update() {
|
||||
now := time.Now()
|
||||
item.FileNo = payload.FileNo
|
||||
item.IDCard = payload.IDCard
|
||||
item.IDCardFront = payload.IDCardFront
|
||||
item.IDCardBack = payload.IDCardBack
|
||||
item.EducationPhoto = payload.EducationPhoto
|
||||
// 证照字段由 UpdateCertificates 独立维护,基础资料保存不得覆盖已有附件。
|
||||
item.PoliticalStatus = payload.PoliticalStatus
|
||||
item.MaritalStatus = payload.MaritalStatus
|
||||
item.NativePlace = payload.NativePlace
|
||||
@@ -584,20 +637,81 @@ func (c *BackendEmployeeFileController) Update() {
|
||||
item.EmergencyPhone = payload.EmergencyPhone
|
||||
item.EmergencyRelation = payload.EmergencyRelation
|
||||
item.WorkEmail = payload.WorkEmail
|
||||
item.HireDate = strings.TrimSpace(payload.HireDate)
|
||||
item.RegularDate = strings.TrimSpace(payload.RegularDate)
|
||||
item.LeaveDate = strings.TrimSpace(payload.LeaveDate)
|
||||
item.HireDate = efDatePointer(payload.HireDate)
|
||||
item.RegularDate = efDatePointer(payload.RegularDate)
|
||||
item.LeaveDate = efDatePointer(payload.LeaveDate)
|
||||
item.EmploymentStatus = payload.EmploymentStatus
|
||||
item.Remark = payload.Remark
|
||||
item.UpdateTime = &now
|
||||
|
||||
if _, err := models.Orm.Update(&item,
|
||||
"FileNo", "IDCard", "IDCardFront", "IDCardBack", "EducationPhoto",
|
||||
"PoliticalStatus", "MaritalStatus", "NativePlace",
|
||||
"FileNo", "IDCard", "PoliticalStatus", "MaritalStatus", "NativePlace",
|
||||
"HouseholdAddress", "CurrentAddress", "EmergencyContact", "EmergencyPhone",
|
||||
"EmergencyRelation", "WorkEmail", "HireDate", "RegularDate", "LeaveDate",
|
||||
"EmploymentStatus", "Remark", "UpdateTime"); err != nil {
|
||||
c.efErr(500, 500, "保存失败")
|
||||
log.Printf("员工档案更新失败: tid=%d file_id=%d err=%v", tid, id, err)
|
||||
c.efErr(500, 500, "保存失败,请检查员工档案表字段及日期数据")
|
||||
return
|
||||
}
|
||||
|
||||
c.efOk(item)
|
||||
}
|
||||
|
||||
// employeeFileCertificatesPayload 证照资料单独更新请求体。
|
||||
// 该接口仅在档案详情中使用,避免上传附件时覆盖基础资料。
|
||||
type employeeFileCertificatesPayload struct {
|
||||
IDCardFront string `json:"id_card_front"`
|
||||
IDCardBack string `json:"id_card_back"`
|
||||
EducationPhoto string `json:"education_photo"`
|
||||
}
|
||||
|
||||
// UpdateCertificates POST /backend/oa/employeefile/certificates/:id
|
||||
// 仅更新学历照片、身份证正反面;档案必须已创建,不能通过建档接口上传。
|
||||
func (c *BackendEmployeeFileController) UpdateCertificates() {
|
||||
claims, err := c.efClaims()
|
||||
if err != nil {
|
||||
c.efErr(401, 401, "未登录或无权限")
|
||||
return
|
||||
}
|
||||
tid := claims.TenantId
|
||||
|
||||
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.efErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
|
||||
var payload employeeFileCertificatesPayload
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil || json.Unmarshal(raw, &payload) != nil {
|
||||
c.efErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
for label, url := range map[string]string{
|
||||
"身份证正面": payload.IDCardFront,
|
||||
"身份证反面": payload.IDCardBack,
|
||||
"学历照片": payload.EducationPhoto,
|
||||
} {
|
||||
if len(url) > 500 {
|
||||
c.efErr(400, 400, label+"地址过长")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var item models.BackendEmployeeFile
|
||||
if err := c.efQuery(tid).Filter("id", id).One(&item); err != nil {
|
||||
c.efErr(404, 404, "档案不存在")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
item.IDCardFront = strings.TrimSpace(payload.IDCardFront)
|
||||
item.IDCardBack = strings.TrimSpace(payload.IDCardBack)
|
||||
item.EducationPhoto = strings.TrimSpace(payload.EducationPhoto)
|
||||
item.UpdateTime = &now
|
||||
if _, err := models.Orm.Update(&item,
|
||||
"IDCardFront", "IDCardBack", "EducationPhoto", "UpdateTime"); err != nil {
|
||||
c.efErr(500, 500, "证照保存失败")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -743,8 +857,8 @@ func (c *BackendEmployeeFileController) CreateRecord() {
|
||||
Title: payload.Title,
|
||||
SubTitle: payload.SubTitle,
|
||||
Extra: payload.Extra,
|
||||
StartDate: payload.StartDate,
|
||||
EndDate: payload.EndDate,
|
||||
StartDate: efDatePointer(payload.StartDate),
|
||||
EndDate: efDatePointer(payload.EndDate),
|
||||
Description: payload.Description,
|
||||
AttachmentURL: payload.AttachmentURL,
|
||||
Sort: payload.Sort,
|
||||
@@ -795,8 +909,8 @@ func (c *BackendEmployeeFileController) UpdateRecord() {
|
||||
item.Title = payload.Title
|
||||
item.SubTitle = payload.SubTitle
|
||||
item.Extra = payload.Extra
|
||||
item.StartDate = payload.StartDate
|
||||
item.EndDate = payload.EndDate
|
||||
item.StartDate = efDatePointer(payload.StartDate)
|
||||
item.EndDate = efDatePointer(payload.EndDate)
|
||||
item.Description = payload.Description
|
||||
item.AttachmentURL = payload.AttachmentURL
|
||||
item.Sort = payload.Sort
|
||||
|
||||
Reference in New Issue
Block a user