更新若干事项
This commit is contained in:
1 parent
68cdaf38fa
commit
9898962836
30 files changed
+7715
No files matched your search
@@ -0,0 +1,647 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
const scBoardMaxDays = 62
|
||||
|
||||
// scEntryView 看板单元格里的排班信息。
|
||||
type scEntryView struct {
|
||||
ID uint64 `json:"id"`
|
||||
PlanID uint64 `json:"plan_id"`
|
||||
ShiftID uint64 `json:"shift_id"`
|
||||
ShiftName string `json:"shift_name"`
|
||||
ShortName string `json:"short_name"`
|
||||
Color string `json:"color"`
|
||||
WorkStart string `json:"work_start"`
|
||||
WorkEnd string `json:"work_end"`
|
||||
Source int8 `json:"source"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
type scBoardRow struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Account string `json:"account"`
|
||||
OrgID uint64 `json:"org_id"`
|
||||
OrgName string `json:"org_name"`
|
||||
Position string `json:"position"`
|
||||
Days map[string]scEntryView `json:"days"`
|
||||
}
|
||||
|
||||
type scDateCell struct {
|
||||
Date string `json:"date"`
|
||||
Weekday int `json:"weekday"`
|
||||
IsToday bool `json:"is_today"`
|
||||
}
|
||||
|
||||
func (c *BackendOaSchedulingController) scEntryQuery(tid uint64) orm.QuerySeter {
|
||||
return models.Orm.QueryTable(new(models.OaScheduleEntry)).
|
||||
Filter("tid", tid).
|
||||
Filter("is_deleted", 0)
|
||||
}
|
||||
|
||||
// Board 排班看板:按员工 × 日期返回区间内的排班矩阵。
|
||||
// GET /backend/oa/scheduling/board?start_date=&end_date=&org_id=&include_child=1&keyword=
|
||||
func (c *BackendOaSchedulingController) Board() {
|
||||
tid, _, _, ok := c.scOperator()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
startDate := strings.TrimSpace(c.GetString("start_date"))
|
||||
endDate := strings.TrimSpace(c.GetString("end_date"))
|
||||
if startDate == "" || endDate == "" {
|
||||
// 默认展示本周(周一至周日)
|
||||
now := time.Now()
|
||||
weekday := int(now.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 7
|
||||
}
|
||||
monday := now.AddDate(0, 0, -(weekday - 1))
|
||||
startDate = monday.Format("2006-01-02")
|
||||
endDate = monday.AddDate(0, 0, 6).Format("2006-01-02")
|
||||
}
|
||||
dates, msg := scDateSpan(startDate, endDate)
|
||||
if msg != "" {
|
||||
c.scFail(400, 400, msg)
|
||||
return
|
||||
}
|
||||
if len(dates) > scBoardMaxDays {
|
||||
c.scFail(400, 400, fmt.Sprintf("看板一次最多展示 %d 天", scBoardMaxDays))
|
||||
return
|
||||
}
|
||||
|
||||
orgID := uint64(0)
|
||||
if raw := strings.TrimSpace(c.GetString("org_id")); raw != "" {
|
||||
if parsed, err := strconvParseUint(raw); err == nil {
|
||||
orgID = parsed
|
||||
}
|
||||
}
|
||||
includeChild := c.GetString("include_child") != "0"
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
onlyScheduled := c.GetString("only_scheduled") == "1"
|
||||
shiftFilter := uint64(0)
|
||||
if raw := strings.TrimSpace(c.GetString("shift_id")); raw != "" {
|
||||
if parsed, err := strconvParseUint(raw); err == nil {
|
||||
shiftFilter = parsed
|
||||
}
|
||||
}
|
||||
|
||||
employees, err := c.scEmployeesByOrg(tid, orgID, includeChild)
|
||||
if err != nil {
|
||||
c.scFail(500, 500, "查询员工失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
index := c.scLoadOrgIndex(tid)
|
||||
rows := make([]scBoardRow, 0, len(employees))
|
||||
employeeIDs := make([]uint64, 0, len(employees))
|
||||
for _, employee := range employees {
|
||||
if keyword != "" && !strings.Contains(employee.Name, keyword) && !strings.Contains(employee.Account, keyword) {
|
||||
continue
|
||||
}
|
||||
orgID := scEmployeeDepartmentID(employee)
|
||||
position := ""
|
||||
if employee.Position != nil {
|
||||
position = *employee.Position
|
||||
}
|
||||
rows = append(rows, scBoardRow{
|
||||
ID: employee.ID,
|
||||
Name: employee.Name,
|
||||
Account: employee.Account,
|
||||
OrgID: orgID,
|
||||
OrgName: index.name(orgID),
|
||||
Position: position,
|
||||
Days: map[string]scEntryView{},
|
||||
})
|
||||
employeeIDs = append(employeeIDs, uint64(employee.ID))
|
||||
}
|
||||
|
||||
truncated := false
|
||||
const boardMaxEmployees = 500
|
||||
if len(rows) > boardMaxEmployees {
|
||||
rows = rows[:boardMaxEmployees]
|
||||
employeeIDs = employeeIDs[:boardMaxEmployees]
|
||||
truncated = true
|
||||
}
|
||||
|
||||
if len(employeeIDs) > 0 {
|
||||
var entries []models.OaScheduleEntry
|
||||
_, err = c.scEntryQuery(tid).
|
||||
Filter("employee_id__in", employeeIDs).
|
||||
Filter("schedule_date__gte", dates[0]).
|
||||
Filter("schedule_date__lte", dates[len(dates)-1]).
|
||||
All(&entries)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.scFail(500, 500, "查询排班失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rowIndex := map[uint]*scBoardRow{}
|
||||
for i := range rows {
|
||||
rowIndex[rows[i].ID] = &rows[i]
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if shiftFilter > 0 && entry.ShiftID != shiftFilter {
|
||||
continue
|
||||
}
|
||||
row, hit := rowIndex[entry.EmployeeID]
|
||||
if !hit {
|
||||
continue
|
||||
}
|
||||
row.Days[normalizeOaDate(entry.ScheduleDate)] = scEntryView{
|
||||
ID: entry.ID,
|
||||
PlanID: entry.PlanID,
|
||||
ShiftID: entry.ShiftID,
|
||||
ShiftName: entry.ShiftName,
|
||||
ShortName: entry.ShortName,
|
||||
Color: entry.Color,
|
||||
WorkStart: entry.WorkStart,
|
||||
WorkEnd: entry.WorkEnd,
|
||||
Source: entry.Source,
|
||||
Note: entry.Note,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if onlyScheduled {
|
||||
filtered := rows[:0]
|
||||
for _, row := range rows {
|
||||
if len(row.Days) > 0 {
|
||||
filtered = append(filtered, row)
|
||||
}
|
||||
}
|
||||
rows = filtered
|
||||
}
|
||||
|
||||
today := time.Now().Format("2006-01-02")
|
||||
dateCells := make([]scDateCell, 0, len(dates))
|
||||
for _, date := range dates {
|
||||
parsed, _ := scParseDate(date)
|
||||
weekday := int(parsed.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 7
|
||||
}
|
||||
dateCells = append(dateCells, scDateCell{Date: date, Weekday: weekday, IsToday: date == today})
|
||||
}
|
||||
|
||||
// 汇总:排班人次、临时调配数、未排班人数、今日在岗、班次分布
|
||||
entryCount, tempCount, unassigned := 0, 0, 0
|
||||
todayOnDuty := 0
|
||||
shiftCounter := map[string]int{}
|
||||
shiftColor := map[string]string{}
|
||||
for _, row := range rows {
|
||||
if len(row.Days) == 0 {
|
||||
unassigned++
|
||||
}
|
||||
for date, entry := range row.Days {
|
||||
entryCount++
|
||||
if entry.Source == models.OaEntrySourceTemp {
|
||||
tempCount++
|
||||
}
|
||||
if date == today {
|
||||
todayOnDuty++
|
||||
}
|
||||
name := entry.ShiftName
|
||||
if name == "" {
|
||||
name = "未命名班次"
|
||||
}
|
||||
shiftCounter[name]++
|
||||
shiftColor[name] = entry.Color
|
||||
}
|
||||
}
|
||||
shiftDist := make([]map[string]interface{}, 0, len(shiftCounter))
|
||||
for name, count := range shiftCounter {
|
||||
shiftDist = append(shiftDist, map[string]interface{}{
|
||||
"name": name,
|
||||
"count": count,
|
||||
"color": shiftColor[name],
|
||||
})
|
||||
}
|
||||
sort.Slice(shiftDist, func(i, j int) bool {
|
||||
return shiftDist[i]["count"].(int) > shiftDist[j]["count"].(int)
|
||||
})
|
||||
|
||||
var shifts []models.OaShift
|
||||
_, _ = c.shiftQuery(tid).Filter("status", 1).OrderBy("sort", "id").All(&shifts)
|
||||
if shifts == nil {
|
||||
shifts = []models.OaShift{}
|
||||
}
|
||||
|
||||
c.scOK(map[string]interface{}{
|
||||
"start_date": startDate,
|
||||
"end_date": endDate,
|
||||
"dates": dateCells,
|
||||
"employees": rows,
|
||||
"shifts": shifts,
|
||||
"truncated": truncated,
|
||||
"summary": map[string]interface{}{
|
||||
"employee_count": len(rows),
|
||||
"entry_count": entryCount,
|
||||
"temp_count": tempCount,
|
||||
"unassigned": unassigned,
|
||||
"today_on_duty": todayOnDuty,
|
||||
"shift_dist": shiftDist,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func strconvParseUint(value string) (uint64, error) {
|
||||
return strconv.ParseUint(strings.TrimSpace(value), 10, 64)
|
||||
}
|
||||
|
||||
// scEntryTargets 解析临时排班/批量清除的目标员工与日期。
|
||||
func (c *BackendOaSchedulingController) scEntryTargets(tid uint64, body map[string]interface{}) ([]models.BackendEmployee, []string, string) {
|
||||
employeeIDs := scUintSlice(body, "employee_ids")
|
||||
if len(employeeIDs) == 0 {
|
||||
return nil, nil, "请选择员工"
|
||||
}
|
||||
if len(employeeIDs) > scPlanMaxEmployees {
|
||||
return nil, nil, fmt.Sprintf("单次最多操作 %d 名员工", scPlanMaxEmployees)
|
||||
}
|
||||
|
||||
startDate := scStringValue(body, "start_date", "schedule_date", "date")
|
||||
endDate := scStringValue(body, "end_date")
|
||||
if endDate == "" {
|
||||
endDate = startDate
|
||||
}
|
||||
dates, msg := scDateSpan(startDate, endDate)
|
||||
if msg != "" {
|
||||
return nil, nil, msg
|
||||
}
|
||||
workdays := scWorkdays(dates, scWeekdaySet(body))
|
||||
if len(workdays) == 0 {
|
||||
return nil, nil, "所选日期区间内没有符合条件的日期"
|
||||
}
|
||||
|
||||
var employees []models.BackendEmployee
|
||||
_, err := c.scBaseEmployeeQuery(tid).Filter("id__in", employeeIDs).All(&employees)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
return nil, nil, "查询员工失败: " + err.Error()
|
||||
}
|
||||
if len(employees) == 0 {
|
||||
return nil, nil, "员工不存在或已离职"
|
||||
}
|
||||
if len(employees)*len(workdays) > scPlanMaxEntries {
|
||||
return nil, nil, fmt.Sprintf("本次将产生 %d 条排班,超过单次 %d 条上限", len(employees)*len(workdays), scPlanMaxEntries)
|
||||
}
|
||||
return employees, workdays, ""
|
||||
}
|
||||
|
||||
// SaveEntries 临时排班:给一个或多个员工在指定日期/星期安排班次。
|
||||
// shift_id=0 时表示清除这些日期的排班。
|
||||
// POST /backend/oa/scheduling/entries/save
|
||||
func (c *BackendOaSchedulingController) SaveEntries() {
|
||||
tid, uid, username, ok := c.scOperator()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
body := c.scBody()
|
||||
employees, workdays, msg := c.scEntryTargets(tid, body)
|
||||
if msg != "" {
|
||||
c.scFail(400, 400, msg)
|
||||
return
|
||||
}
|
||||
|
||||
shiftID := scUintValue(body, "shift_id")
|
||||
var shift *models.OaShift
|
||||
if shiftID > 0 {
|
||||
loaded, found := c.scLoadShift(tid, shiftID)
|
||||
if !found {
|
||||
c.scFail(400, 400, "班次不存在或已停用")
|
||||
return
|
||||
}
|
||||
shift = loaded
|
||||
}
|
||||
|
||||
note := scStringValue(body, "note")
|
||||
overwrite := true
|
||||
if _, has := body["overwrite"]; has {
|
||||
overwrite = scBoolValue(body, "overwrite")
|
||||
}
|
||||
|
||||
empIDs := make([]uint64, 0, len(employees))
|
||||
for _, employee := range employees {
|
||||
empIDs = append(empIDs, uint64(employee.ID))
|
||||
}
|
||||
var existing []models.OaScheduleEntry
|
||||
_, err := c.scEntryQuery(tid).
|
||||
Filter("employee_id__in", empIDs).
|
||||
Filter("schedule_date__gte", workdays[0]).
|
||||
Filter("schedule_date__lte", workdays[len(workdays)-1]).
|
||||
All(&existing)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.scFail(500, 500, "查询已有排班失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingMap := map[string]*models.OaScheduleEntry{}
|
||||
for i := range existing {
|
||||
key := fmt.Sprintf("%d|%s", existing[i].EmployeeID, normalizeOaDate(existing[i].ScheduleDate))
|
||||
existingMap[key] = &existing[i]
|
||||
}
|
||||
|
||||
index := c.scLoadOrgIndex(tid)
|
||||
stats := map[string]int{"created": 0, "updated": 0, "cleared": 0, "skipped": 0}
|
||||
now := time.Now()
|
||||
|
||||
for _, employee := range employees {
|
||||
orgID := scEmployeeDepartmentID(employee)
|
||||
position := ""
|
||||
if employee.Position != nil {
|
||||
position = *employee.Position
|
||||
}
|
||||
for _, date := range workdays {
|
||||
key := fmt.Sprintf("%d|%s", employee.ID, date)
|
||||
row := existingMap[key]
|
||||
|
||||
// shift_id=0:清除排班
|
||||
if shift == nil {
|
||||
if row == nil {
|
||||
stats["skipped"]++
|
||||
continue
|
||||
}
|
||||
oldShiftID, oldShiftName := row.ShiftID, row.ShiftName
|
||||
row.IsDeleted = row.ID
|
||||
row.DeleteTime = &now
|
||||
if _, updateErr := models.Orm.Update(row, "is_deleted", "delete_time"); updateErr != nil {
|
||||
c.scFail(500, 500, "清除排班失败: "+updateErr.Error())
|
||||
return
|
||||
}
|
||||
c.scWriteChange(models.Orm, tid, row.ID, row.EmployeeID, row.EmployeeName, date,
|
||||
models.OaChangeClear, oldShiftID, oldShiftName, 0, "", note, uid, username)
|
||||
delete(existingMap, key)
|
||||
stats["cleared"]++
|
||||
continue
|
||||
}
|
||||
|
||||
if row != nil {
|
||||
if !overwrite {
|
||||
stats["skipped"]++
|
||||
continue
|
||||
}
|
||||
oldShiftID, oldShiftName := row.ShiftID, row.ShiftName
|
||||
row.ShiftID = shift.ID
|
||||
row.ShiftName = shift.Name
|
||||
row.ShortName = shift.ShortName
|
||||
row.Color = shift.Color
|
||||
row.WorkStart = shift.WorkStart
|
||||
row.WorkEnd = shift.WorkEnd
|
||||
row.Source = models.OaEntrySourceTemp
|
||||
row.Note = note
|
||||
row.CreatedBy = uid
|
||||
row.CreatedByName = username
|
||||
if _, updateErr := models.Orm.Update(row); updateErr != nil {
|
||||
c.scFail(500, 500, "调整排班失败: "+updateErr.Error())
|
||||
return
|
||||
}
|
||||
c.scWriteChange(models.Orm, tid, row.ID, row.EmployeeID, row.EmployeeName, date,
|
||||
models.OaChangeShift, oldShiftID, oldShiftName, shift.ID, shift.Name, note, uid, username)
|
||||
stats["updated"]++
|
||||
continue
|
||||
}
|
||||
|
||||
entry := &models.OaScheduleEntry{
|
||||
Tid: int(tid),
|
||||
EmployeeID: employee.ID,
|
||||
EmployeeName: employee.Name,
|
||||
EmployeeAccount: employee.Account,
|
||||
OrgID: orgID,
|
||||
OrgName: index.name(orgID),
|
||||
Position: position,
|
||||
ScheduleDate: date,
|
||||
ShiftID: shift.ID,
|
||||
ShiftName: shift.Name,
|
||||
ShortName: shift.ShortName,
|
||||
Color: shift.Color,
|
||||
WorkStart: shift.WorkStart,
|
||||
WorkEnd: shift.WorkEnd,
|
||||
Source: models.OaEntrySourceTemp,
|
||||
Note: note,
|
||||
CreatedBy: uid,
|
||||
CreatedByName: username,
|
||||
}
|
||||
if _, insertErr := models.Orm.Insert(entry); insertErr != nil {
|
||||
c.scFail(500, 500, "新增排班失败: "+insertErr.Error())
|
||||
return
|
||||
}
|
||||
existingMap[key] = entry
|
||||
c.scWriteChange(models.Orm, tid, entry.ID, entry.EmployeeID, entry.EmployeeName, date,
|
||||
models.OaChangeManual, 0, "", shift.ID, shift.Name, note, uid, username)
|
||||
stats["created"]++
|
||||
}
|
||||
}
|
||||
|
||||
stats["entry_count"] = stats["created"] + stats["updated"] + stats["cleared"]
|
||||
c.scOK(stats)
|
||||
}
|
||||
|
||||
// SwapEntry 单条调班:把某个员工某天的班次换成另一个班次。
|
||||
// POST /backend/oa/scheduling/entries/swap/:id
|
||||
func (c *BackendOaSchedulingController) SwapEntry() {
|
||||
tid, uid, username, ok := c.scOperator()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
id, valid := c.scPathID(":id")
|
||||
if !valid {
|
||||
c.scFail(400, 400, "无效的排班ID")
|
||||
return
|
||||
}
|
||||
body := c.scBody()
|
||||
shiftID := scUintValue(body, "shift_id")
|
||||
if shiftID == 0 {
|
||||
c.scFail(400, 400, "请选择调换后的班次")
|
||||
return
|
||||
}
|
||||
shift, found := c.scLoadShift(tid, shiftID)
|
||||
if !found {
|
||||
c.scFail(400, 400, "班次不存在或已停用")
|
||||
return
|
||||
}
|
||||
|
||||
var entry models.OaScheduleEntry
|
||||
if err := c.scEntryQuery(tid).Filter("id", id).One(&entry); err != nil {
|
||||
c.scFail(404, 404, "排班记录不存在")
|
||||
return
|
||||
}
|
||||
if entry.ShiftID == shift.ID {
|
||||
c.scFail(400, 400, "该排班已经是目标班次")
|
||||
return
|
||||
}
|
||||
|
||||
reason := scStringValue(body, "reason")
|
||||
oldShiftID, oldShiftName := entry.ShiftID, entry.ShiftName
|
||||
entry.ShiftID = shift.ID
|
||||
entry.ShiftName = shift.Name
|
||||
entry.ShortName = shift.ShortName
|
||||
entry.Color = shift.Color
|
||||
entry.WorkStart = shift.WorkStart
|
||||
entry.WorkEnd = shift.WorkEnd
|
||||
entry.Source = models.OaEntrySourceTemp
|
||||
if reason != "" {
|
||||
entry.Note = reason
|
||||
}
|
||||
if _, err := models.Orm.Update(&entry); err != nil {
|
||||
c.scFail(500, 500, "调班失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.scWriteChange(models.Orm, tid, entry.ID, entry.EmployeeID, entry.EmployeeName, normalizeOaDate(entry.ScheduleDate),
|
||||
models.OaChangeShift, oldShiftID, oldShiftName, shift.ID, shift.Name, reason, uid, username)
|
||||
|
||||
c.scOK(map[string]interface{}{"entry": entry, "old_shift_name": oldShiftName})
|
||||
}
|
||||
|
||||
// ClearEntries 批量清除排班:按明细 ID,或按员工 + 日期区间。
|
||||
// POST /backend/oa/scheduling/entries/clear
|
||||
func (c *BackendOaSchedulingController) ClearEntries() {
|
||||
tid, uid, username, ok := c.scOperator()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
body := c.scBody()
|
||||
entryIDs := scUintSlice(body, "entry_ids")
|
||||
|
||||
var entries []models.OaScheduleEntry
|
||||
if len(entryIDs) > 0 {
|
||||
_, err := c.scEntryQuery(tid).Filter("id__in", entryIDs).All(&entries)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.scFail(500, 500, "查询排班失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
employees, workdays, msg := c.scEntryTargets(tid, body)
|
||||
if msg != "" {
|
||||
c.scFail(400, 400, msg)
|
||||
return
|
||||
}
|
||||
empIDs := make([]uint64, 0, len(employees))
|
||||
for _, employee := range employees {
|
||||
empIDs = append(empIDs, uint64(employee.ID))
|
||||
}
|
||||
_, err := c.scEntryQuery(tid).
|
||||
Filter("employee_id__in", empIDs).
|
||||
Filter("schedule_date__in", workdays).
|
||||
All(&entries)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.scFail(500, 500, "查询排班失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
c.scOK(map[string]interface{}{"cleared": 0})
|
||||
return
|
||||
}
|
||||
if len(entries) > scPlanMaxEntries {
|
||||
c.scFail(400, 400, fmt.Sprintf("单次最多清除 %d 条排班", scPlanMaxEntries))
|
||||
return
|
||||
}
|
||||
|
||||
reason := scStringValue(body, "reason")
|
||||
now := time.Now()
|
||||
tx, err := orm.NewOrm().Begin()
|
||||
if err != nil {
|
||||
c.scFail(500, 500, "清除排班失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
for i := range entries {
|
||||
entry := &entries[i]
|
||||
oldShiftID, oldShiftName := entry.ShiftID, entry.ShiftName
|
||||
entry.IsDeleted = entry.ID
|
||||
entry.DeleteTime = &now
|
||||
if _, err := tx.Update(entry, "is_deleted", "delete_time"); err != nil {
|
||||
_ = tx.Rollback()
|
||||
c.scFail(500, 500, "清除排班失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.scWriteChange(tx, tid, entry.ID, entry.EmployeeID, entry.EmployeeName, normalizeOaDate(entry.ScheduleDate),
|
||||
models.OaChangeClear, oldShiftID, oldShiftName, 0, "", reason, uid, username)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
c.scFail(500, 500, "清除排班失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.scOK(map[string]interface{}{"cleared": len(entries)})
|
||||
}
|
||||
|
||||
// ChangeList 排班变更记录(最近 100 条),用于追溯临时调配。
|
||||
// GET /backend/oa/scheduling/changes?start_date=&end_date=&employee_id=
|
||||
func (c *BackendOaSchedulingController) ChangeList() {
|
||||
tid, _, _, ok := c.scOperator()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
qs := models.Orm.QueryTable(new(models.OaScheduleChange)).Filter("tid", tid)
|
||||
if start := strings.TrimSpace(c.GetString("start_date")); isValidOaDate(start) {
|
||||
qs = qs.Filter("schedule_date__gte", start)
|
||||
}
|
||||
if end := strings.TrimSpace(c.GetString("end_date")); isValidOaDate(end) {
|
||||
qs = qs.Filter("schedule_date__lte", end)
|
||||
}
|
||||
if raw := strings.TrimSpace(c.GetString("employee_id")); raw != "" {
|
||||
if employeeID, err := strconvParseUint(raw); err == nil {
|
||||
qs = qs.Filter("employee_id", employeeID)
|
||||
}
|
||||
}
|
||||
var rows []models.OaScheduleChange
|
||||
_, err := qs.OrderBy("-id").Limit(100).All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.scFail(500, 500, "查询变更记录失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if rows == nil {
|
||||
rows = []models.OaScheduleChange{}
|
||||
}
|
||||
for i := range rows {
|
||||
rows[i].ScheduleDate = normalizeOaDate(rows[i].ScheduleDate)
|
||||
}
|
||||
c.scOK(map[string]interface{}{"list": rows})
|
||||
}
|
||||
|
||||
// ScheduleEmployeeOptions 排班可选员工(供“指定员工”范围选择)。
|
||||
// GET /backend/oa/scheduling/employees?org_id=&include_child=1&keyword=
|
||||
func (c *BackendOaSchedulingController) ScheduleEmployeeOptions() {
|
||||
tid, _, _, ok := c.scOperator()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
orgID := uint64(0)
|
||||
if raw := strings.TrimSpace(c.GetString("org_id")); raw != "" {
|
||||
if parsed, err := strconvParseUint(raw); err == nil {
|
||||
orgID = parsed
|
||||
}
|
||||
}
|
||||
includeChild := c.GetString("include_child") != "0"
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
|
||||
employees, err := c.scEmployeesByOrg(tid, orgID, includeChild)
|
||||
if err != nil {
|
||||
c.scFail(500, 500, "查询员工失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
list := c.scEmployeeDTOs(tid, employees)
|
||||
if keyword != "" {
|
||||
filtered := make([]schedulingEmployee, 0, len(list))
|
||||
for _, item := range list {
|
||||
if strings.Contains(item.Name, keyword) || strings.Contains(item.Account, keyword) {
|
||||
filtered = append(filtered, item)
|
||||
}
|
||||
}
|
||||
list = filtered
|
||||
}
|
||||
if list == nil {
|
||||
list = []schedulingEmployee{}
|
||||
}
|
||||
c.scOK(map[string]interface{}{"list": list, "total": len(list)})
|
||||
}
|
||||
Reference in new issue
Block a user