优化排班功能
This commit is contained in:
1 parent
8acbe732ff
commit
8ba15d87b3
13 files changed
+2463
-565
No files matched your search
@@ -400,7 +400,11 @@ func (c *BackendOaSchedulingController) SaveEntries() {
|
||||
row.Note = note
|
||||
row.CreatedBy = uid
|
||||
row.CreatedByName = username
|
||||
if _, updateErr := models.Orm.Update(row); updateErr != nil {
|
||||
// 指定字段更新:DATE 列从库里读回 string 会带上
|
||||
// "00:00:00 +0800 CST" 后缀,全字段 Update 会写回导致 MySQL 1292
|
||||
if _, updateErr := models.Orm.Update(row,
|
||||
"shift_id", "shift_name", "short_name", "color", "work_start", "work_end",
|
||||
"source", "note", "created_by", "created_by_name"); updateErr != nil {
|
||||
c.scFail(500, 500, "调整排班失败: "+updateErr.Error())
|
||||
return
|
||||
}
|
||||
@@ -445,6 +449,205 @@ func (c *BackendOaSchedulingController) SaveEntries() {
|
||||
c.scOK(stats)
|
||||
}
|
||||
|
||||
// SaveEntryCells 批量按格子编辑排班:cells 为任意「员工 × 日期」组合(排班板多选)。
|
||||
// shift_id>0 时把这些格子设为该班次(已有排班视为调班,无排班则新增临时排班);
|
||||
// shift_id=0 时清除这些格子的排班(休息 / 请假)。
|
||||
// POST /backend/oa/scheduling/entries/save-cells
|
||||
func (c *BackendOaSchedulingController) SaveEntryCells() {
|
||||
tid, uid, username, ok := c.scOperator()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
body := c.scBody()
|
||||
|
||||
type scCellTarget struct {
|
||||
employeeID uint
|
||||
date string
|
||||
}
|
||||
rawCells, _ := body["cells"].([]interface{})
|
||||
seen := map[string]bool{}
|
||||
cells := make([]scCellTarget, 0, len(rawCells))
|
||||
for _, raw := range rawCells {
|
||||
item, ok := raw.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
employeeID := uint(scUintValue(item, "employee_id"))
|
||||
date := normalizeOaDate(scStringValue(item, "schedule_date", "date"))
|
||||
if employeeID == 0 || date == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := time.Parse("2006-01-02", date); err != nil {
|
||||
c.scFail(400, 400, "存在无效的日期: "+date)
|
||||
return
|
||||
}
|
||||
key := fmt.Sprintf("%d|%s", employeeID, date)
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
cells = append(cells, scCellTarget{employeeID: employeeID, date: date})
|
||||
}
|
||||
if len(cells) == 0 {
|
||||
c.scFail(400, 400, "请先选择要编辑的格子")
|
||||
return
|
||||
}
|
||||
if len(cells) > scPlanMaxEntries {
|
||||
c.scFail(400, 400, fmt.Sprintf("单次最多编辑 %d 个格子", scPlanMaxEntries))
|
||||
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
|
||||
}
|
||||
|
||||
// 排班来源:normal=正常排班(不显示临时标记),temp=临时调整(默认,显示「临」角标)
|
||||
entrySource := models.OaEntrySourceTemp
|
||||
if scStringValue(body, "source") == "normal" {
|
||||
entrySource = models.OaEntrySourcePlan
|
||||
}
|
||||
|
||||
empIDSet := map[uint]bool{}
|
||||
empIDs := make([]uint64, 0, len(cells))
|
||||
dates := make([]string, 0, len(cells))
|
||||
for _, cell := range cells {
|
||||
if !empIDSet[cell.employeeID] {
|
||||
empIDSet[cell.employeeID] = true
|
||||
empIDs = append(empIDs, uint64(cell.employeeID))
|
||||
}
|
||||
dates = append(dates, cell.date)
|
||||
}
|
||||
var employees []models.BackendEmployee
|
||||
if _, err := c.scBaseEmployeeQuery(tid).Filter("id__in", empIDs).All(&employees); err != nil && err != orm.ErrNoRows {
|
||||
c.scFail(500, 500, "查询员工失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
employeeMap := map[uint]*models.BackendEmployee{}
|
||||
for i := range employees {
|
||||
employeeMap[employees[i].ID] = &employees[i]
|
||||
}
|
||||
|
||||
var existing []models.OaScheduleEntry
|
||||
if _, err := c.scEntryQuery(tid).
|
||||
Filter("employee_id__in", empIDs).
|
||||
Filter("schedule_date__in", dates).
|
||||
All(&existing); 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)
|
||||
note := scStringValue(body, "note")
|
||||
stats := map[string]int{"created": 0, "updated": 0, "cleared": 0, "skipped": 0}
|
||||
now := time.Now()
|
||||
|
||||
for _, cell := range cells {
|
||||
employee, found := employeeMap[cell.employeeID]
|
||||
if !found {
|
||||
stats["skipped"]++
|
||||
continue
|
||||
}
|
||||
key := fmt.Sprintf("%d|%s", cell.employeeID, cell.date)
|
||||
row := existingMap[key]
|
||||
orgID := scEmployeeDepartmentID(*employee)
|
||||
position := ""
|
||||
if employee.Position != nil {
|
||||
position = *employee.Position
|
||||
}
|
||||
|
||||
// 清除排班(休息 / 请假):只处理已有排班的格子
|
||||
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, cell.date,
|
||||
models.OaChangeClear, oldShiftID, oldShiftName, 0, "", note, uid, username)
|
||||
stats["cleared"]++
|
||||
continue
|
||||
}
|
||||
|
||||
// 已有排班:调整为目标班次(调班)
|
||||
if row != nil {
|
||||
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 = entrySource
|
||||
row.Note = note
|
||||
row.CreatedBy = uid
|
||||
row.CreatedByName = username
|
||||
// 指定字段更新:DATE 列从库里读回 string 会带上
|
||||
// "00:00:00 +0800 CST" 后缀,全字段 Update 会写回导致 MySQL 1292
|
||||
if _, updateErr := models.Orm.Update(row,
|
||||
"shift_id", "shift_name", "short_name", "color", "work_start", "work_end",
|
||||
"source", "note", "created_by", "created_by_name"); updateErr != nil {
|
||||
c.scFail(500, 500, "调整排班失败: "+updateErr.Error())
|
||||
return
|
||||
}
|
||||
c.scWriteChange(models.Orm, tid, row.ID, row.EmployeeID, row.EmployeeName, cell.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: cell.date,
|
||||
ShiftID: shift.ID,
|
||||
ShiftName: shift.Name,
|
||||
ShortName: shift.ShortName,
|
||||
Color: shift.Color,
|
||||
WorkStart: shift.WorkStart,
|
||||
WorkEnd: shift.WorkEnd,
|
||||
Source: entrySource,
|
||||
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, cell.date,
|
||||
models.OaChangeManual, 0, "", shift.ID, shift.Name, note, uid, username)
|
||||
stats["created"]++
|
||||
}
|
||||
|
||||
stats["cell_count"] = len(cells)
|
||||
c.scOK(stats)
|
||||
}
|
||||
|
||||
// SwapEntry 单条调班:把某个员工某天的班次换成另一个班次。
|
||||
// POST /backend/oa/scheduling/entries/swap/:id
|
||||
func (c *BackendOaSchedulingController) SwapEntry() {
|
||||
|
||||
Reference in new issue
Block a user