@@ -0,0 +1,846 @@
package controllers
import (
"encoding/json"
"io"
"regexp"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// BackendEmployeeFileController 员工档案管理(OA 人事档案)。
// 档案与员工(yz_backend_employee)按 tid + employee_id 一对一,
// 子记录表统一存放教育经历 / 工作经历 / 合同信息 / 证照附件。
// 数据按 JWT 中的租户隔离。
type BackendEmployeeFileController struct {
beego . Controller
}
func ( c * BackendEmployeeFileController ) efClaims ( ) ( * jwtutil . Claims , error ) {
auth := c . Ctx . Request . Header . Get ( "Authorization" )
if auth == "" {
return nil , orm . ErrNoRows
}
parts := strings . SplitN ( auth , " " , 2 )
if len ( parts ) != 2 || parts [ 0 ] != "Bearer" {
return nil , orm . ErrNoRows
}
claims , err := jwtutil . ParseToken ( parts [ 1 ] )
if err != nil {
return nil , err
}
if claims . UserType != "backend" {
return nil , orm . ErrNoRows
}
return claims , nil
}
func ( c * BackendEmployeeFileController ) efOk ( data interface { } ) {
c . Data [ "json" ] = map [ string ] interface { } { "code" : 200 , "msg" : "success" , "data" : data }
_ = c . ServeJSON ( )
}
func ( c * BackendEmployeeFileController ) efErr ( httpStatus , bizCode int , msg string ) {
c . Ctx . Output . SetStatus ( httpStatus )
c . Data [ "json" ] = map [ string ] interface { } { "code" : bizCode , "msg" : msg }
_ = c . ServeJSON ( )
}
var efDatePattern = regexp . MustCompile ( ` ^\d { 4}-\d { 2}-\d { 2}$ ` )
// efIsValidDate 校验 "YYYY-MM-DD" 日期字符串;允许为空(可空字段)。
func efIsValidDate ( s string ) bool {
s = strings . TrimSpace ( s )
if s == "" {
return true
}
if ! efDatePattern . MatchString ( s ) {
return false
}
_ , err := time . ParseInLocation ( "2006-01-02" , s , time . Local )
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 ]
}
return s
}
// efQuery 档案查询基座:强制附加租户过滤与未删除条件。
func ( c * BackendEmployeeFileController ) efQuery ( tid int ) orm . QuerySeter {
return models . Orm . QueryTable ( new ( models . BackendEmployeeFile ) ) .
Filter ( "tid" , tid ) .
Filter ( "is_deleted" , 0 )
}
// efEmployeeQuery 员工查询基座(与组织架构控制器保持同一过滤口径)。
func efEmployeeQuery ( tid int ) orm . QuerySeter {
return models . Orm . QueryTable ( new ( models . BackendEmployee ) ) .
Filter ( "tid" , tid ) .
Filter ( "delete_time__isnull" , true )
}
// efOrgNameMap 组织ID -> 名称映射,用于展示员工的隶属单位/部门名称。
func ( c * BackendEmployeeFileController ) efOrgNameMap ( tid int ) map [ uint64 ] string {
var rows [ ] models . BackendOrganization
if _ , err := models . Orm . QueryTable ( new ( models . BackendOrganization ) ) .
Filter ( "tid" , tid ) .
Filter ( "delete_time__isnull" , true ) .
All ( & rows , "ID" , "OrgName" ) ; err != nil {
return map [ uint64 ] string { }
}
names := make ( map [ uint64 ] string , len ( rows ) )
for _ , row := range rows {
names [ row . ID ] = row . OrgName
}
return names
}
// efOrgNameByID 组织ID字符串转名称;非数字时原样返回(手输文本兜底)。
func efOrgNameByID ( names map [ uint64 ] string , id string ) string {
if id == "" {
return ""
}
if v , err := strconv . ParseUint ( id , 10 , 64 ) ; err == nil {
return names [ v ]
}
return id
}
// efSubtreeOrgIDs 收集组织节点及其全部下级的ID(BFS,含环保护)。
func ( c * BackendEmployeeFileController ) efSubtreeOrgIDs ( tid int , rootID uint64 ) [ ] uint64 {
var rows [ ] models . BackendOrganization
if _ , err := models . Orm . QueryTable ( new ( models . BackendOrganization ) ) .
Filter ( "tid" , tid ) .
Filter ( "delete_time__isnull" , true ) .
All ( & rows , "ID" , "ParentID" ) ; err != nil {
return [ ] uint64 { rootID }
}
childrenOf := map [ uint64 ] [ ] uint64 { }
for _ , row := range rows {
childrenOf [ row . ParentID ] = append ( childrenOf [ row . ParentID ] , row . ID )
}
result := [ ] uint64 { rootID }
queue := [ ] uint64 { rootID }
visited := map [ uint64 ] bool { rootID : true }
for len ( queue ) > 0 {
current := queue [ 0 ]
queue = queue [ 1 : ]
for _ , child := range childrenOf [ current ] {
if visited [ child ] {
continue
}
visited [ child ] = true
result = append ( result , child )
queue = append ( queue , child )
}
}
return result
}
// efEmployeeIDsInOrgs 取部门集合内的员工ID(档案按员工过滤时使用)。
func ( c * BackendEmployeeFileController ) efEmployeeIDsInOrgs ( tid int , orgIDs [ ] uint64 ) [ ] uint64 {
values := make ( [ ] string , 0 , len ( orgIDs ) )
for _ , id := range orgIDs {
values = append ( values , strconv . FormatUint ( id , 10 ) )
}
var rows [ ] models . BackendEmployee
if _ , err := efEmployeeQuery ( tid ) . Filter ( "department__in" , values ) . All ( & rows , "ID" ) ; err != nil {
return nil
}
ids := make ( [ ] uint64 , 0 , len ( rows ) )
for _ , row := range rows {
ids = append ( ids , uint64 ( row . ID ) )
}
return ids
}
// efEmployeeIDsByKeyword 取姓名/账号/手机命中的员工ID(档案关键词搜索联动员工字段)。
func ( c * BackendEmployeeFileController ) efEmployeeIDsByKeyword ( tid int , keyword string ) [ ] uint64 {
cond := orm . NewCondition ( ) .
Or ( "name__icontains" , keyword ) .
Or ( "account__icontains" , keyword ) .
Or ( "phone__icontains" , keyword )
var rows [ ] models . BackendEmployee
if _ , err := efEmployeeQuery ( tid ) . SetCond ( cond ) . All ( & rows , "ID" ) ; err != nil {
return nil
}
ids := make ( [ ] uint64 , 0 , len ( rows ) )
for _ , row := range rows {
ids = append ( ids , uint64 ( row . ID ) )
}
return ids
}
// employeeFileDTO 档案 + 关联员工展示信息(JSON 平铺,前端表格/详情直接用)。
type employeeFileDTO struct {
models . BackendEmployeeFile
EmployeeName string ` json:"employee_name" `
EmployeeAccount string ` json:"employee_account" `
Gender int8 ` json:"gender" `
Birthday string ` json:"birthday" `
AffiliateUnit string ` json:"affiliate_unit" `
AffiliateUnitName string ` json:"affiliate_unit_name" `
Department string ` json:"department" `
DepartmentName string ` json:"department_name" `
Position string ` json:"position" `
Phone string ` json:"phone" `
Email string ` json:"email" `
Education string ` json:"education" `
Nation string ` json:"nation" `
HomeAddress string ` json:"home_address" `
AccountStatus int8 ` json:"account_status" `
}
// efAssembleDTOList 档案列表批量组装员工展示信息。
func ( c * BackendEmployeeFileController ) efAssembleDTOList ( tid int , files [ ] models . BackendEmployeeFile ) [ ] employeeFileDTO {
list := make ( [ ] employeeFileDTO , 0 , len ( files ) )
if len ( files ) == 0 {
return list
}
empIDs := make ( [ ] uint64 , 0 , len ( files ) )
for _ , f := range files {
empIDs = append ( empIDs , f . EmployeeID )
}
var emps [ ] models . BackendEmployee
if _ , err := efEmployeeQuery ( tid ) . Filter ( "id__in" , empIDs ) . All ( & emps ) ; err != nil {
emps = nil
}
empByID := make ( map [ uint64 ] models . BackendEmployee , len ( emps ) )
for _ , e := range emps {
empByID [ uint64 ( e . ID ) ] = e
}
orgNames := c . efOrgNameMap ( tid )
for _ , f := range files {
dto := employeeFileDTO { BackendEmployeeFile : f }
dto . HireDate = efNormalizeDate ( f . HireDate )
dto . RegularDate = efNormalizeDate ( f . RegularDate )
dto . LeaveDate = efNormalizeDate ( f . LeaveDate )
if emp , ok := empByID [ f . EmployeeID ] ; ok {
birthday := ""
if emp . Birthday != nil {
birthday = emp . Birthday . Format ( "2006-01-02" )
}
affiliate := derefString ( emp . AffiliateUnit )
department := derefString ( emp . Department )
dto . EmployeeName = emp . Name
dto . EmployeeAccount = emp . Account
dto . Gender = emp . Gender
dto . Birthday = birthday
dto . AffiliateUnit = affiliate
dto . AffiliateUnitName = efOrgNameByID ( orgNames , affiliate )
dto . Department = department
dto . DepartmentName = efOrgNameByID ( orgNames , department )
dto . Position = derefString ( emp . Position )
dto . Phone = derefString ( emp . Phone )
dto . Email = derefString ( emp . Email )
dto . Education = derefString ( emp . Education )
dto . Nation = derefString ( emp . Nation )
dto . HomeAddress = derefString ( emp . HomeAddress )
dto . AccountStatus = emp . AccountStatus
}
list = append ( list , dto )
}
return list
}
// List GET /backend/oa/employeefile/list
// 分页查询档案,支持 keyword(档案编号/身份证/员工姓名/账号/手机)、
// status(在职状态)、org_id(部门,含下级)筛选。
func ( c * BackendEmployeeFileController ) List ( ) {
claims , err := c . efClaims ( )
if err != nil {
c . efErr ( 401 , 401 , "未登录或无权限" )
return
}
tid := claims . TenantId
page , _ := c . GetInt ( "page" , 1 )
pageSize , _ := c . GetInt ( "pageSize" , 10 )
keyword := strings . TrimSpace ( c . GetString ( "keyword" ) )
statusRaw := strings . TrimSpace ( c . GetString ( "status" ) )
orgID , _ := c . GetUint64 ( "org_id" )
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 200 {
pageSize = 10
}
qs := c . efQuery ( tid )
if orgID > 0 {
empIDs := c . efEmployeeIDsInOrgs ( tid , c . efSubtreeOrgIDs ( tid , orgID ) )
if len ( empIDs ) == 0 {
c . efOk ( map [ string ] interface { } { "list" : [ ] employeeFileDTO { } , "total" : 0 } )
return
}
qs = qs . Filter ( "employee_id__in" , empIDs )
}
if statusRaw != "" {
status , convErr := strconv . Atoi ( statusRaw )
if convErr != nil || status < 1 || status > 3 {
c . efErr ( 400 , 400 , "在职状态参数无效" )
return
}
qs = qs . Filter ( "employment_status" , int8 ( status ) )
}
if keyword != "" {
// 关键词同时命中档案自身字段(编号/身份证)与员工字段(姓名/账号/手机)
empIDs := c . efEmployeeIDsByKeyword ( tid , keyword )
cond := orm . NewCondition ( ) .
Or ( "file_no__icontains" , keyword ) .
Or ( "id_card__icontains" , keyword )
if len ( empIDs ) > 0 {
cond = cond . Or ( "employee_id__in" , empIDs )
}
qs = qs . SetCond ( orm . NewCondition ( ) . AndCond ( cond ) )
}
total , _ := qs . Count ( )
var files [ ] models . BackendEmployeeFile
if _ , err := qs . OrderBy ( "-id" ) . Limit ( pageSize ) . Offset ( ( page - 1 ) * pageSize ) . All ( & files ) ; err != nil && err != orm . ErrNoRows {
c . efErr ( 500 , 500 , "查询失败" )
return
}
c . efOk ( map [ string ] interface { } {
"list" : c . efAssembleDTOList ( tid , files ) ,
"total" : total ,
} )
}
// Stats GET /backend/oa/employeefile/stats
// 返回档案总数与在职状态分布,供页面顶部统计卡片展示。
func ( c * BackendEmployeeFileController ) Stats ( ) {
claims , err := c . efClaims ( )
if err != nil {
c . efErr ( 401 , 401 , "未登录或无权限" )
return
}
tid := claims . TenantId
total , _ := c . efQuery ( tid ) . Count ( )
probation , _ := c . efQuery ( tid ) . Filter ( "employment_status" , 1 ) . Count ( )
active , _ := c . efQuery ( tid ) . Filter ( "employment_status" , 2 ) . Count ( )
resigned , _ := c . efQuery ( tid ) . Filter ( "employment_status" , 3 ) . Count ( )
c . efOk ( map [ string ] interface { } {
"total" : total ,
"probation" : probation ,
"active" : active ,
"resigned" : resigned ,
} )
}
// Detail GET /backend/oa/employeefile/detail/:id
// 返回档案详情(含员工展示信息)与全部子记录(教育/工作/合同/证照)。
func ( c * BackendEmployeeFileController ) Detail ( ) {
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 file models . BackendEmployeeFile
if err := c . efQuery ( tid ) . Filter ( "id" , id ) . One ( & file ) ; err != nil {
c . efErr ( 404 , 404 , "档案不存在" )
return
}
var records [ ] models . BackendEmployeeFileRecord
if _ , err := models . Orm . QueryTable ( new ( models . BackendEmployeeFileRecord ) ) .
Filter ( "tid" , tid ) .
Filter ( "is_deleted" , 0 ) .
Filter ( "employee_id" , file . EmployeeID ) .
OrderBy ( "sort" , "id" ) .
All ( & records ) ; err != nil && err != orm . ErrNoRows {
c . efErr ( 500 , 500 , "查询档案记录失败" )
return
}
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 } )
}
// employeeFilePayload 建档/编辑档案请求体。
type employeeFilePayload struct {
EmployeeID uint64 ` json:"employee_id" `
FileNo string ` json:"file_no" `
IDCard string ` json:"id_card" `
IDCardFront string ` json:"id_card_front" `
IDCardBack string ` json:"id_card_back" `
EducationPhoto string ` json:"education_photo" `
PoliticalStatus string ` json:"political_status" `
MaritalStatus int8 ` json:"marital_status" `
NativePlace string ` json:"native_place" `
HouseholdAddress string ` json:"household_address" `
CurrentAddress string ` json:"current_address" `
EmergencyContact string ` json:"emergency_contact" `
EmergencyPhone string ` json:"emergency_phone" `
EmergencyRelation string ` json:"emergency_relation" `
WorkEmail string ` json:"work_email" `
HireDate string ` json:"hire_date" `
RegularDate string ` json:"regular_date" `
LeaveDate string ` json:"leave_date" `
EmploymentStatus int8 ` json:"employment_status" `
Remark string ` json:"remark" `
}
// efParsePayload 读取并校验建档/编辑档案的请求体;校验失败时直接输出错误响应。
func ( c * BackendEmployeeFileController ) efParsePayload ( ) ( employeeFilePayload , bool ) {
var payload employeeFilePayload
raw , err := io . ReadAll ( c . Ctx . Request . Body )
if err != nil || json . Unmarshal ( raw , & payload ) != nil {
c . efErr ( 400 , 400 , "参数错误" )
return payload , false
}
payload . FileNo = strings . TrimSpace ( payload . FileNo )
payload . IDCard = strings . TrimSpace ( payload . IDCard )
payload . WorkEmail = strings . TrimSpace ( payload . WorkEmail )
if payload . IDCard != "" && len ( payload . IDCard ) > 30 {
c . efErr ( 400 , 400 , "身份证号过长" )
return payload , false
}
// 证照附件地址长度限制(与列宽一致)
for label , url := range map [ string ] string {
"身份证正面" : payload . IDCardFront ,
"身份证反面" : payload . IDCardBack ,
"学历照片" : payload . EducationPhoto ,
} {
if len ( url ) > 500 {
c . efErr ( 400 , 400 , label + "地址过长" )
return payload , false
}
}
if payload . WorkEmail != "" && ! strings . Contains ( payload . WorkEmail , "@" ) {
c . efErr ( 400 , 400 , "工作邮箱格式无效" )
return payload , false
}
for label , value := range map [ string ] string {
"入职日期" : payload . HireDate ,
"转正日期" : payload . RegularDate ,
"离职日期" : payload . LeaveDate ,
} {
if ! efIsValidDate ( value ) {
c . efErr ( 400 , 400 , label + "格式无效" )
return payload , false
}
}
if payload . EmploymentStatus < 1 || payload . EmploymentStatus > 3 {
payload . EmploymentStatus = 2
}
if payload . MaritalStatus < 0 || payload . MaritalStatus > 4 {
payload . MaritalStatus = 0
}
return payload , true
}
// Create POST /backend/oa/employeefile/create
// 为员工建档;一人一档,重复建档会被唯一约束与前置校验拦截。
func ( c * BackendEmployeeFileController ) Create ( ) {
claims , err := c . efClaims ( )
if err != nil {
c . efErr ( 401 , 401 , "未登录或无权限" )
return
}
tid := claims . TenantId
payload , ok := c . efParsePayload ( )
if ! ok {
return
}
if payload . EmployeeID == 0 {
c . efErr ( 400 , 400 , "请选择员工" )
return
}
// 员工必须存在
var emp models . BackendEmployee
if err := efEmployeeQuery ( tid ) . Filter ( "id" , payload . EmployeeID ) . One ( & emp ) ; err != nil {
c . efErr ( 404 , 404 , "员工不存在" )
return
}
// 一人一档
dup , _ := c . efQuery ( tid ) . Filter ( "employee_id" , payload . EmployeeID ) . Count ( )
if dup > 0 {
c . efErr ( 400 , 400 , "该员工已建档,请勿重复创建" )
return
}
// 档案编号留空自动生成:EF + 时间戳
fileNo := payload . FileNo
if fileNo == "" {
fileNo = "EF" + time . Now ( ) . Format ( "20060102150405" )
}
now := time . Now ( )
item := & models . BackendEmployeeFile {
Tid : tid ,
EmployeeID : payload . EmployeeID ,
FileNo : fileNo ,
IDCard : payload . IDCard ,
IDCardFront : payload . IDCardFront ,
IDCardBack : payload . IDCardBack ,
EducationPhoto : payload . EducationPhoto ,
PoliticalStatus : payload . PoliticalStatus ,
MaritalStatus : payload . MaritalStatus ,
NativePlace : payload . NativePlace ,
HouseholdAddress : payload . HouseholdAddress ,
CurrentAddress : payload . CurrentAddress ,
EmergencyContact : payload . EmergencyContact ,
EmergencyPhone : payload . EmergencyPhone ,
EmergencyRelation : payload . EmergencyRelation ,
WorkEmail : payload . WorkEmail ,
HireDate : strings . TrimSpace ( payload . HireDate ) ,
RegularDate : strings . TrimSpace ( payload . RegularDate ) ,
LeaveDate : strings . TrimSpace ( payload . LeaveDate ) ,
EmploymentStatus : payload . EmploymentStatus ,
Remark : payload . Remark ,
IsDeleted : 0 ,
CreateTime : now ,
UpdateTime : & now ,
}
if _ , err := models . Orm . Insert ( item ) ; err != nil {
c . efErr ( 500 , 500 , "保存失败" )
return
}
c . efOk ( item )
}
// Update POST /backend/oa/employeefile/update/:id
// 更新档案基本资料;员工归属(employee_id)不允许修改。
func ( c * BackendEmployeeFileController ) Update ( ) {
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
}
payload , ok := c . efParsePayload ( )
if ! ok {
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 . FileNo = payload . FileNo
item . IDCard = payload . IDCard
item . IDCardFront = payload . IDCardFront
item . IDCardBack = payload . IDCardBack
item . EducationPhoto = payload . EducationPhoto
item . PoliticalStatus = payload . PoliticalStatus
item . MaritalStatus = payload . MaritalStatus
item . NativePlace = payload . NativePlace
item . HouseholdAddress = payload . HouseholdAddress
item . CurrentAddress = payload . CurrentAddress
item . EmergencyContact = payload . EmergencyContact
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 . EmploymentStatus = payload . EmploymentStatus
item . Remark = payload . Remark
item . UpdateTime = & now
if _ , err := models . Orm . Update ( & item ,
"FileNo" , "IDCard" , "IDCardFront" , "IDCardBack" , "EducationPhoto" ,
"PoliticalStatus" , "MaritalStatus" , "NativePlace" ,
"HouseholdAddress" , "CurrentAddress" , "EmergencyContact" , "EmergencyPhone" ,
"EmergencyRelation" , "WorkEmail" , "HireDate" , "RegularDate" , "LeaveDate" ,
"EmploymentStatus" , "Remark" , "UpdateTime" ) ; err != nil {
c . efErr ( 500 , 500 , "保存失败" )
return
}
c . efOk ( item )
}
// Delete DELETE /backend/oa/employeefile/delete/:id 软删除档案。
func ( c * BackendEmployeeFileController ) Delete ( ) {
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
}
now := time . Now ( )
n , err := c . efQuery ( tid ) . Filter ( "id" , id ) .
Update ( map [ string ] interface { } {
"IsDeleted" : int8 ( 1 ) ,
"DeleteTime" : now ,
"UpdateTime" : now ,
} )
if err != nil || n == 0 {
c . efErr ( 404 , 404 , "档案不存在或已删除" )
return
}
c . efOk ( nil )
}
// FiledEmployees GET /backend/oa/employeefile/filedEmployees
// 返回已建档的员工ID列表,前端用于过滤出可建档(未建档)员工。
func ( c * BackendEmployeeFileController ) FiledEmployees ( ) {
claims , err := c . efClaims ( )
if err != nil {
c . efErr ( 401 , 401 , "未登录或无权限" )
return
}
tid := claims . TenantId
var files [ ] models . BackendEmployeeFile
if _ , err := c . efQuery ( tid ) . All ( & files , "EmployeeID" ) ; err != nil {
c . efErr ( 500 , 500 , "查询失败" )
return
}
ids := make ( [ ] uint64 , 0 , len ( files ) )
for _ , f := range files {
ids = append ( ids , f . EmployeeID )
}
c . efOk ( ids )
}
// employeeFileRecordPayload 档案子记录请求体。
// type: 1-教育经历 2-工作经历 3-合同信息 4-证照附件。
type employeeFileRecordPayload struct {
EmployeeID uint64 ` json:"employee_id" `
Type int8 ` json:"type" `
Title string ` json:"title" `
SubTitle string ` json:"sub_title" `
Extra string ` json:"extra" `
StartDate string ` json:"start_date" `
EndDate string ` json:"end_date" `
Description string ` json:"description" `
AttachmentURL string ` json:"attachment_url" `
Sort int ` json:"sort" `
}
// efParseRecordPayload 读取并校验子记录请求体;校验失败时直接输出错误响应。
func ( c * BackendEmployeeFileController ) efParseRecordPayload ( ) ( employeeFileRecordPayload , bool ) {
var payload employeeFileRecordPayload
raw , err := io . ReadAll ( c . Ctx . Request . Body )
if err != nil || json . Unmarshal ( raw , & payload ) != nil {
c . efErr ( 400 , 400 , "参数错误" )
return payload , false
}
if payload . EmployeeID == 0 {
c . efErr ( 400 , 400 , "参数缺少员工" )
return payload , false
}
if payload . Type < 1 || payload . Type > 4 {
c . efErr ( 400 , 400 , "记录类型无效" )
return payload , false
}
payload . Title = strings . TrimSpace ( payload . Title )
if payload . Title == "" {
c . efErr ( 400 , 400 , "请输入名称" )
return payload , false
}
if len ( payload . Title ) > 100 {
c . efErr ( 400 , 400 , "名称不能超过100字" )
return payload , false
}
payload . StartDate = strings . TrimSpace ( payload . StartDate )
payload . EndDate = strings . TrimSpace ( payload . EndDate )
if ! efIsValidDate ( payload . StartDate ) || ! efIsValidDate ( payload . EndDate ) {
c . efErr ( 400 , 400 , "日期格式无效" )
return payload , false
}
if payload . StartDate != "" && payload . EndDate != "" && payload . EndDate < payload . StartDate {
c . efErr ( 400 , 400 , "结束日期不能早于开始日期" )
return payload , false
}
if len ( payload . AttachmentURL ) > 500 {
c . efErr ( 400 , 400 , "附件地址过长" )
return payload , false
}
return payload , true
}
// CreateRecord POST /backend/oa/employeefile/record/create 新增子记录。
func ( c * BackendEmployeeFileController ) CreateRecord ( ) {
claims , err := c . efClaims ( )
if err != nil {
c . efErr ( 401 , 401 , "未登录或无权限" )
return
}
tid := claims . TenantId
payload , ok := c . efParseRecordPayload ( )
if ! ok {
return
}
// 档案必须存在(记录挂在员工上,但以建档为前提)
exists , _ := c . efQuery ( tid ) . Filter ( "employee_id" , payload . EmployeeID ) . Count ( )
if exists == 0 {
c . efErr ( 404 , 404 , "档案不存在,请先建档" )
return
}
now := time . Now ( )
item := & models . BackendEmployeeFileRecord {
Tid : tid ,
EmployeeID : payload . EmployeeID ,
Type : payload . Type ,
Title : payload . Title ,
SubTitle : payload . SubTitle ,
Extra : payload . Extra ,
StartDate : payload . StartDate ,
EndDate : payload . EndDate ,
Description : payload . Description ,
AttachmentURL : payload . AttachmentURL ,
Sort : payload . Sort ,
IsDeleted : 0 ,
CreateTime : now ,
UpdateTime : & now ,
}
if _ , err := models . Orm . Insert ( item ) ; err != nil {
c . efErr ( 500 , 500 , "保存失败" )
return
}
c . efOk ( item )
}
// UpdateRecord POST /backend/oa/employeefile/record/update/:id 更新子记录。
func ( c * BackendEmployeeFileController ) UpdateRecord ( ) {
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
}
payload , ok := c . efParseRecordPayload ( )
if ! ok {
return
}
var item models . BackendEmployeeFileRecord
if err := models . Orm . QueryTable ( new ( models . BackendEmployeeFileRecord ) ) .
Filter ( "tid" , tid ) .
Filter ( "is_deleted" , 0 ) .
Filter ( "id" , id ) .
One ( & item ) ; err != nil {
c . efErr ( 404 , 404 , "记录不存在" )
return
}
now := time . Now ( )
item . Type = payload . Type
item . Title = payload . Title
item . SubTitle = payload . SubTitle
item . Extra = payload . Extra
item . StartDate = payload . StartDate
item . EndDate = payload . EndDate
item . Description = payload . Description
item . AttachmentURL = payload . AttachmentURL
item . Sort = payload . Sort
item . UpdateTime = & now
if _ , err := models . Orm . Update ( & item ,
"Type" , "Title" , "SubTitle" , "Extra" , "StartDate" , "EndDate" ,
"Description" , "AttachmentURL" , "Sort" , "UpdateTime" ) ; err != nil {
c . efErr ( 500 , 500 , "保存失败" )
return
}
c . efOk ( item )
}
// DeleteRecord DELETE /backend/oa/employeefile/record/delete/:id 软删除子记录。
func ( c * BackendEmployeeFileController ) DeleteRecord ( ) {
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
}
now := time . Now ( )
n , err := models . Orm . QueryTable ( new ( models . BackendEmployeeFileRecord ) ) .
Filter ( "tid" , tid ) .
Filter ( "is_deleted" , 0 ) .
Filter ( "id" , id ) .
Update ( map [ string ] interface { } {
"IsDeleted" : int8 ( 1 ) ,
"DeleteTime" : now ,
"UpdateTime" : now ,
} )
if err != nil || n == 0 {
c . efErr ( 404 , 404 , "记录不存在或已删除" )
return
}
c . efOk ( nil )
}