更新角色管理相关内容
This commit is contained in:
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -289,14 +290,103 @@ func (c *BackendOaScheduleController) Stats() {
|
||||
}
|
||||
|
||||
type oaSchedulePayload struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
ScheduleDate string `json:"schedule_date"`
|
||||
StartTime string `json:"start_time"`
|
||||
EndTime string `json:"end_time"`
|
||||
AllDay int8 `json:"all_day"`
|
||||
Color string `json:"color"`
|
||||
Priority int8 `json:"priority"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
ScheduleDate string `json:"schedule_date"`
|
||||
StartTime string `json:"start_time"`
|
||||
EndTime string `json:"end_time"`
|
||||
AllDay int8 `json:"all_day"`
|
||||
Color string `json:"color"`
|
||||
Priority int8 `json:"priority"`
|
||||
// 提醒绑定:开启后会在提醒工具生成一条提醒任务,并把其 ID 回写到 ReminderID。
|
||||
ReminderEnabled bool `json:"reminder_enabled"`
|
||||
ReminderTime string `json:"reminder_time"`
|
||||
RemindChannels []string `json:"remind_channels"`
|
||||
AdvanceMinutes int `json:"advance_minutes"`
|
||||
}
|
||||
|
||||
// oaReminderChannels 允许的提醒渠道集合。
|
||||
var oaReminderChannels = map[string]bool{
|
||||
"SMS": true, "EMAIL": true, "BARK": true, "SITE_MSG": true,
|
||||
}
|
||||
|
||||
// createOaScheduleReminder 为 OA 日程创建一条绑定的提醒任务。
|
||||
// 复用提醒工具的数据模型(yz_backend_schedule + yz_backend_schedule_reminder):
|
||||
// 生成一条 BackendSchedule,再按渠道逐条生成提醒记录,返回 BackendSchedule.ID
|
||||
// 作为 OA 日程的 reminder_id。EMAIL/BARK 渠道会附带确认回执令牌与重复发送配置。
|
||||
func createOaScheduleReminder(tid int, userID uint64, title, content, reminderTime string, channels []string, advanceMinutes int) (uint64, error) {
|
||||
if strings.TrimSpace(reminderTime) == "" {
|
||||
return 0, fmt.Errorf("提醒时间不能为空")
|
||||
}
|
||||
schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", reminderTime, time.Local)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("提醒时间格式不合法")
|
||||
}
|
||||
if advanceMinutes < 0 {
|
||||
advanceMinutes = 0
|
||||
}
|
||||
title = strings.TrimSpace(title)
|
||||
if title == "" {
|
||||
title = "日程提醒"
|
||||
}
|
||||
|
||||
bs := models.BackendSchedule{
|
||||
Tid: tid,
|
||||
Title: title,
|
||||
Content: content,
|
||||
ScheduleTime: schedTime,
|
||||
UserID: userID,
|
||||
}
|
||||
schedID, err := models.Orm.Insert(&bs)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("创建提醒任务失败: %v", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, ch := range channels {
|
||||
ch = strings.ToUpper(strings.TrimSpace(ch))
|
||||
if !oaReminderChannels[ch] {
|
||||
continue
|
||||
}
|
||||
firstSend := schedTime.Add(-time.Duration(advanceMinutes) * time.Minute)
|
||||
reminder := models.BackendScheduleReminder{
|
||||
Tid: tid,
|
||||
ScheduleID: uint64(schedID),
|
||||
RemindChannel: ch,
|
||||
AdvanceMinutes: advanceMinutes,
|
||||
NextRemindTime: firstSend,
|
||||
ReceiverUserID: userID,
|
||||
RemindStatus: 0,
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
if ch == "EMAIL" || ch == "BARK" {
|
||||
token := backendGenerateToken()
|
||||
reminder.AckToken = &token
|
||||
reminder.RepeatIntervalMinutes = 10
|
||||
reminder.MaxSendCount = 5
|
||||
} else {
|
||||
reminder.RepeatIntervalMinutes = 0
|
||||
reminder.MaxSendCount = 1
|
||||
}
|
||||
if _, err := models.Orm.Insert(&reminder); err != nil {
|
||||
return 0, fmt.Errorf("创建提醒失败: %v", err)
|
||||
}
|
||||
}
|
||||
return uint64(schedID), nil
|
||||
}
|
||||
|
||||
// deleteBoundReminder 软删除 OA 日程绑定的提醒任务及其提醒记录。
|
||||
// 删除日程或关闭提醒时使用,避免提醒工具里残留孤立任务。
|
||||
func deleteBoundReminder(scheduleID uint64) {
|
||||
if scheduleID == 0 {
|
||||
return
|
||||
}
|
||||
_, _ = models.Orm.QueryTable(new(models.BackendSchedule)).
|
||||
Filter("id", scheduleID).Delete()
|
||||
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
|
||||
Filter("schedule_id", scheduleID).
|
||||
Update(map[string]interface{}{"IsDeleted": 1, "UpdateTime": time.Now()})
|
||||
}
|
||||
|
||||
// oaScheduleImage 日程相关图片(截图/照片),只存文件引用,不存二进制。
|
||||
@@ -393,6 +483,29 @@ func (c *BackendOaScheduleController) parseOaSchedulePayload() (oaSchedulePayloa
|
||||
if payload.Color == "" {
|
||||
payload.Color = "#409EFF"
|
||||
}
|
||||
|
||||
// 提醒绑定校验:开启提醒时必须选择渠道且提醒时间合法。
|
||||
if payload.ReminderEnabled {
|
||||
validChannels := make([]string, 0, len(payload.RemindChannels))
|
||||
for _, ch := range payload.RemindChannels {
|
||||
ch = strings.ToUpper(strings.TrimSpace(ch))
|
||||
if oaReminderChannels[ch] {
|
||||
validChannels = append(validChannels, ch)
|
||||
}
|
||||
}
|
||||
payload.RemindChannels = validChannels
|
||||
if len(validChannels) == 0 {
|
||||
c.oasJsonErr(400, 400, "请至少选择一个提醒渠道")
|
||||
return payload, false
|
||||
}
|
||||
if _, err := time.ParseInLocation("2006-01-02 15:04:05", payload.ReminderTime, time.Local); err != nil {
|
||||
c.oasJsonErr(400, 400, "提醒时间格式不合法,支持 YYYY-MM-DD HH:mm:ss")
|
||||
return payload, false
|
||||
}
|
||||
if payload.AdvanceMinutes < 0 {
|
||||
payload.AdvanceMinutes = 0
|
||||
}
|
||||
}
|
||||
return payload, true
|
||||
}
|
||||
|
||||
@@ -432,6 +545,30 @@ func (c *BackendOaScheduleController) Create() {
|
||||
return
|
||||
}
|
||||
|
||||
// 开启提醒:生成绑定的提醒任务,并把其 ID 回写到日程。
|
||||
if payload.ReminderEnabled {
|
||||
rid, rerr := createOaScheduleReminder(
|
||||
claims.TenantId,
|
||||
uint64(claims.UserID),
|
||||
payload.Title,
|
||||
payload.Content,
|
||||
payload.ReminderTime,
|
||||
payload.RemindChannels,
|
||||
payload.AdvanceMinutes,
|
||||
)
|
||||
if rerr != nil {
|
||||
// 回滚已创建的日程,避免产生孤立日程
|
||||
_, _ = models.Orm.QueryTable(new(models.OaSchedule)).Filter("id", item.ID).Delete()
|
||||
c.oasJsonErr(500, 500, rerr.Error())
|
||||
return
|
||||
}
|
||||
item.ReminderID = rid
|
||||
if _, err := models.Orm.Update(item, "ReminderID"); err != nil {
|
||||
c.oasJsonErr(500, 500, "保存失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.oasOk(item)
|
||||
}
|
||||
|
||||
@@ -477,9 +614,34 @@ func (c *BackendOaScheduleController) Update() {
|
||||
item.Priority = payload.Priority
|
||||
item.UpdateTime = &now
|
||||
|
||||
// 重新绑定提醒:先创建新提醒,成功后再删除旧提醒(关闭提醒则清掉绑定)。
|
||||
// 顺序反过来可避免"新提醒创建失败却删掉了原提醒"导致提醒丢失。
|
||||
if payload.ReminderEnabled {
|
||||
rid, rerr := createOaScheduleReminder(
|
||||
claims.TenantId,
|
||||
uint64(claims.UserID),
|
||||
payload.Title,
|
||||
payload.Content,
|
||||
payload.ReminderTime,
|
||||
payload.RemindChannels,
|
||||
payload.AdvanceMinutes,
|
||||
)
|
||||
if rerr != nil {
|
||||
c.oasJsonErr(500, 500, rerr.Error())
|
||||
return
|
||||
}
|
||||
if item.ReminderID > 0 {
|
||||
deleteBoundReminder(item.ReminderID)
|
||||
}
|
||||
item.ReminderID = rid
|
||||
} else if item.ReminderID > 0 {
|
||||
deleteBoundReminder(item.ReminderID)
|
||||
item.ReminderID = 0
|
||||
}
|
||||
|
||||
if _, err := models.Orm.Update(&item,
|
||||
"Title", "Content", "ScheduleDate", "StartTime", "EndTime",
|
||||
"AllDay", "Color", "Priority", "UpdateTime"); err != nil {
|
||||
"AllDay", "Color", "Priority", "ReminderID", "UpdateTime"); err != nil {
|
||||
c.oasJsonErr(500, 500, "保存失败")
|
||||
return
|
||||
}
|
||||
@@ -555,21 +717,31 @@ func (c *BackendOaScheduleController) Delete() {
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.OaSchedule)).
|
||||
var item models.OaSchedule
|
||||
err = models.Orm.QueryTable(new(models.OaSchedule)).
|
||||
Filter("id", id).
|
||||
Filter("is_deleted", 0).
|
||||
Filter("tid", claims.TenantId).
|
||||
Filter("user_id", claims.UserID).
|
||||
Update(map[string]interface{}{
|
||||
"IsDeleted": int8(1),
|
||||
"DeleteTime": now,
|
||||
"UpdateTime": now,
|
||||
})
|
||||
if err != nil || n == 0 {
|
||||
One(&item)
|
||||
if err != nil {
|
||||
c.oasJsonErr(404, 404, "日程不存在或已删除")
|
||||
return
|
||||
}
|
||||
|
||||
// 先删除绑定的提醒任务,避免提醒工具里残留孤立任务
|
||||
if item.ReminderID > 0 {
|
||||
deleteBoundReminder(item.ReminderID)
|
||||
}
|
||||
|
||||
item.IsDeleted = 1
|
||||
item.DeleteTime = &now
|
||||
item.UpdateTime = &now
|
||||
if _, err := models.Orm.Update(&item, "IsDeleted", "DeleteTime", "UpdateTime"); err != nil {
|
||||
c.oasJsonErr(500, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.oasOk(nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -138,9 +138,11 @@ func (c *BackendRoleController) CreateRole() {
|
||||
status = *p.Status
|
||||
}
|
||||
rights := normalizeRights(p.Rights)
|
||||
// 租户端创建的角色一律标记为“自定义角色”,与系统/平台预置角色区分,并按租户隔离
|
||||
role := &models.AdminRole{
|
||||
TenantID: tid,
|
||||
Cid: 2,
|
||||
IsCustom: 1,
|
||||
Name: p.Name,
|
||||
Status: status,
|
||||
Rights: rights,
|
||||
|
||||
@@ -7,6 +7,7 @@ type AdminRole struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
TenantID uint64 `orm:"column(tenant_id);default(0)" json:"tenant_id"` // 租户ID:0=平台/全局,>0=具体租户(仅 cid=2 租户角色使用)
|
||||
Cid uint8 `orm:"column(cid);default(1)" json:"cid"` // 1平台角色 2租户角色
|
||||
IsCustom uint8 `orm:"column(is_custom);default(0)" json:"is_custom"` // 是否租户自定义角色:0=系统/平台预置 1=租户在租户端自建(数据按租户隔离)
|
||||
Name string `orm:"column(name);size(32)" json:"name"`
|
||||
Status uint8 `orm:"column(status);default(1)" json:"status"`
|
||||
Rights *string `orm:"column(rights);type(text);null" json:"rights"`
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
)
|
||||
|
||||
var adminRoleTenantColOnce sync.Once
|
||||
var adminRoleCustomColOnce sync.Once
|
||||
|
||||
// EnsureAdminRoleTenantColumn 为角色表补齐 tenant_id 列(历史库可能早于租户隔离特性建表)。
|
||||
// 字段已存在时 MySQL 报 duplicate column,统一忽略,不阻断主流程。
|
||||
@@ -22,6 +23,20 @@ func EnsureAdminRoleTenantColumn() {
|
||||
})
|
||||
}
|
||||
|
||||
// EnsureAdminRoleCustomColumn 为角色表补齐 is_custom 列(历史库可能早于自定义角色特性建表)。
|
||||
// 字段已存在时 MySQL 报 duplicate column,统一忽略,不阻断主流程。
|
||||
func EnsureAdminRoleCustomColumn() {
|
||||
if Orm == nil {
|
||||
return
|
||||
}
|
||||
adminRoleCustomColOnce.Do(func() {
|
||||
_, _ = Orm.Raw(`
|
||||
ALTER TABLE yz_system_admin_role
|
||||
ADD COLUMN is_custom tinyint unsigned NOT NULL DEFAULT 0 COMMENT '自定义角色 0-系统/平台预置 1-租户自建'
|
||||
`).Exec()
|
||||
})
|
||||
}
|
||||
|
||||
// EnsureDefaultTenantRoles 保证指定租户(cid=2)的默认角色齐全:
|
||||
// - 租户管理员:全权限(rights 为空,见 permission.go 空权限即放行)
|
||||
// - 部门负责人
|
||||
@@ -33,8 +48,9 @@ func EnsureDefaultTenantRoles(tid uint64) {
|
||||
if Orm == nil || tid == 0 {
|
||||
return
|
||||
}
|
||||
// 确保 tenant_id 列存在(兼容老表)
|
||||
// 确保 tenant_id / is_custom 列存在(兼容老表)
|
||||
EnsureAdminRoleTenantColumn()
|
||||
EnsureAdminRoleCustomColumn()
|
||||
|
||||
defaults := []string{"租户管理员", "部门负责人", "员工"}
|
||||
for _, name := range defaults {
|
||||
|
||||
@@ -123,4 +123,8 @@ func Init(_ string) {
|
||||
|
||||
// 创建全局 Ormer
|
||||
Orm = orm.NewOrm()
|
||||
|
||||
// 补齐历史库缺失的列(已存在时 MySQL 报 duplicate column,统一忽略)
|
||||
EnsureAdminRoleTenantColumn()
|
||||
EnsureAdminRoleCustomColumn()
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ type OaSchedule struct {
|
||||
CarryOver int8 `orm:"column(carry_over);default(0)" json:"carry_over"`
|
||||
CarryCount int `orm:"column(carry_count);default(0)" json:"carry_count"`
|
||||
CarryToDate string `orm:"column(carry_to_date);size(10);default()" json:"carry_to_date"`
|
||||
// ReminderID 绑定到提醒工具的提醒任务(yz_backend_schedule.id),0 表示未开启提醒。
|
||||
// 用于把 OA 日程与提醒任务关联,从而在日程详情回显提醒状态(如"提醒已关闭")。
|
||||
ReminderID uint64 `orm:"column(reminder_id);default(0)" json:"reminder_id"`
|
||||
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"`
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"update_time"`
|
||||
@@ -62,6 +65,7 @@ func EnsureOaScheduleCarryColumns() error {
|
||||
"ALTER TABLE yz_backend_oa_schedule ADD COLUMN carry_count int NOT NULL DEFAULT 0 COMMENT '累计顺延次数'",
|
||||
"ALTER TABLE yz_backend_oa_schedule ADD COLUMN carry_to_date varchar(10) NOT NULL DEFAULT '' COMMENT '顺延目标日期(YYYY-MM-DD)'",
|
||||
"ALTER TABLE yz_backend_oa_schedule ADD COLUMN images text NULL COMMENT '相关图片JSON数组 [{id,url,name}]'",
|
||||
"ALTER TABLE yz_backend_oa_schedule ADD COLUMN reminder_id bigint unsigned NOT NULL DEFAULT 0 COMMENT '绑定的提醒任务ID(对应yz_backend_schedule.id) 0-无提醒'",
|
||||
}
|
||||
for _, sql := range alters {
|
||||
if _, e := Orm.Raw(sql).Exec(); e != nil {
|
||||
|
||||
Reference in New Issue
Block a user