增加ai办公,考勤管理
This commit is contained in:
1 parent
eb9b7beff5
commit
31ae07175d
46 files changed
+9250
-296
No files matched your search
@@ -0,0 +1,68 @@
|
||||
-- =============================================================
|
||||
-- OA 考勤打卡 —— 表结构
|
||||
--
|
||||
-- 对应代码:
|
||||
-- models/oa_attendance.go
|
||||
-- controllers/backend_oa_attendance.go (打卡核心 + 后台考勤记录)
|
||||
-- controllers/app_oa_attendance.go (移动端 /api/oa/attendance/*)
|
||||
--
|
||||
-- 执行顺序:
|
||||
-- 1) 本脚本:创建考勤表
|
||||
-- 2) oa_attendance_menus.sql:注册「OA → 排班考勤 → 考勤记录 / 考勤打卡」菜单
|
||||
--
|
||||
-- 设计要点:
|
||||
-- 1. 一人一天一条:签到与签退写同一行,统计与列表都按「人 × 日期」聚合。
|
||||
-- 2. 打卡时联动排班明细(yz_backend_oa_schedule_entry),把班次名称与
|
||||
-- 上下班时间快照进本表;后续调班不影响历史考勤判定。
|
||||
-- 3. 匹配不上排班(含员工档案缺失)不阻断打卡:match_status=2,
|
||||
-- clock_in_status/clock_out_status=3(无排班打卡),提醒文案落在
|
||||
-- clock_in_note / clock_out_note,后台可据此筛选与备注。
|
||||
-- 4. 唯一键 (tid, uid, attend_date):uid 是登录身份,保证员工档案未建
|
||||
-- 也能先打卡(此时 employee_id=0,后台按账号/姓名检索)。
|
||||
-- =============================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `yz_backend_oa_attendance` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '租户ID',
|
||||
`uid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '登录身份ID(打卡主体)',
|
||||
`employee_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '员工ID(yz_backend_employee.id),0=未绑定员工档案',
|
||||
`employee_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '员工姓名快照',
|
||||
`employee_account` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '员工账号快照',
|
||||
`org_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属组织ID',
|
||||
`org_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '组织名称快照',
|
||||
`position` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '职位快照',
|
||||
`attend_date` DATE NOT NULL COMMENT '考勤日期',
|
||||
`entry_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '排班明细ID,0=未匹配排班',
|
||||
`shift_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '班次ID快照',
|
||||
`shift_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '班次名称快照',
|
||||
`short_name` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '班次简称快照',
|
||||
`color` VARCHAR(20) NOT NULL DEFAULT '#409EFF' COMMENT '班次颜色快照',
|
||||
`work_start` VARCHAR(8) NOT NULL DEFAULT '' COMMENT '上班时间快照 HH:mm',
|
||||
`work_end` VARCHAR(8) NOT NULL DEFAULT '' COMMENT '下班时间快照 HH:mm',
|
||||
`match_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=未知 1=已匹配排班 2=未匹配排班',
|
||||
`clock_in_time` DATETIME DEFAULT NULL COMMENT '签到时间',
|
||||
`clock_in_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=未打卡 1=正常 2=迟到 3=无排班打卡 4=不在班次时段',
|
||||
`clock_in_note` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '签到提醒/异常说明',
|
||||
`clock_out_time` DATETIME DEFAULT NULL COMMENT '签退时间',
|
||||
`clock_out_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=未打卡 1=正常 2=早退 3=无排班打卡 4=不在班次时段',
|
||||
`clock_out_note` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '签退提醒/异常说明',
|
||||
`location` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '打卡地点(预留)',
|
||||
`remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '管理员备注/异常说明',
|
||||
`source` TINYINT NOT NULL DEFAULT 1 COMMENT '1=移动端打卡 2=后台补录',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_tid_uid_date` (`tid`, `uid`, `attend_date`),
|
||||
KEY `idx_tid_date` (`tid`, `attend_date`),
|
||||
KEY `idx_tid_emp_date` (`tid`, `employee_id`, `attend_date`),
|
||||
KEY `idx_tid_org_date` (`tid`, `org_id`, `attend_date`),
|
||||
KEY `idx_tid_status` (`tid`, `clock_in_status`, `clock_out_status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='OA-考勤打卡记录';
|
||||
|
||||
-- ---------------- 校验 ----------------
|
||||
SELECT TABLE_NAME, TABLE_COMMENT
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'yz_backend_oa_attendance';
|
||||
@@ -0,0 +1,223 @@
|
||||
-- =============================================================================
|
||||
-- 人员管理(yz_backend_employee) 与 用户管理(yz_system_tenant_user) 老数据对齐
|
||||
--
|
||||
-- 背景:员工表早期建的是「账号全局唯一」索引 uk_account(account),多租户后同一账号
|
||||
-- 在不同企业只能存在一份员工档案;再叠加历史手工录入的人员(如 hero001 这类旧账号),
|
||||
-- 导致「OA 设置 → 人员管理」与「基本设置 → 用户管理」两边数据对不上。
|
||||
--
|
||||
-- 处理内容:
|
||||
-- 1. 索引调整为 (tid, account) 唯一 ← 必须最先执行,否则第 3 步会撞全局唯一键
|
||||
-- 2. 员工 tid 为空/0:按唯一账号补 tid
|
||||
-- 3. 同一企业内「用户缺员工 + 存在同名无账号老员工」:把老员工账号改成用户账号
|
||||
-- (保留员工 ID,排班 / 日报 / 人事档案的历史引用不受影响)
|
||||
-- 4. 用户仍然没有员工:按用户资料补建员工记录(与「用户管理 → 同步员工」同规则)
|
||||
-- 5. 复核查询 + 仍需人工确认的清单
|
||||
--
|
||||
-- 反向缺口(「人员管理有、用户管理没有」)不在这里处理:补账号要先在统一认证建身份
|
||||
-- 并生成 argon2id 密码哈希,SQL 做不到,用 Go 工具:
|
||||
-- go run scripts/syncemployeeusers/main.go -tid <企业ID> -apply
|
||||
--
|
||||
-- 特性:可重复执行(幂等),第 2 次执行不会产生任何变更。
|
||||
-- 执行前请务必备份:mysqldump -u root -p go-platform > backup_$(date +%F).sql
|
||||
-- 执行后请重启 backend 服务:services/employee_sync.go 已改为按 (tid, account) 匹配员工。
|
||||
-- =============================================================================
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 0. 执行前预检(只看不改)
|
||||
-- -----------------------------------------------------------------------------
|
||||
SELECT '执行前:用户/员工数量' AS section;
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM yz_system_tenant_user WHERE delete_time IS NULL) AS user_rows,
|
||||
(SELECT COUNT(*) FROM yz_backend_employee WHERE delete_time IS NULL) AS employee_rows,
|
||||
(SELECT COUNT(*) FROM yz_backend_employee WHERE delete_time IS NULL AND (tid IS NULL OR tid = 0)) AS employee_tid_empty,
|
||||
(SELECT COUNT(*) FROM yz_system_tenant_user u
|
||||
WHERE u.delete_time IS NULL AND u.account IS NOT NULL AND TRIM(u.account) <> ''
|
||||
AND NOT EXISTS (SELECT 1 FROM yz_backend_employee e
|
||||
WHERE e.delete_time IS NULL AND e.tid = u.tid AND TRIM(e.account) = TRIM(u.account))
|
||||
) AS user_without_employee;
|
||||
|
||||
-- 若下面这条 > 0,需要先人工处理重复的 (tid, account),否则第 1 步加唯一索引会失败
|
||||
SELECT '重复的 (tid, account)' AS section;
|
||||
SELECT tid, TRIM(account) AS account, COUNT(*) AS cnt
|
||||
FROM yz_backend_employee
|
||||
WHERE delete_time IS NULL
|
||||
GROUP BY tid, TRIM(account)
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 1. 索引:账号全局唯一 → 企业内唯一
|
||||
-- 关键:不先放开全局唯一,第 3 步把 hero001 改成 hero920103 会因另一企业已占用而失败
|
||||
-- -----------------------------------------------------------------------------
|
||||
SET @has_old := (SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yz_backend_employee'
|
||||
AND INDEX_NAME = 'uk_account');
|
||||
SET @sql := IF(@has_old > 0, 'ALTER TABLE yz_backend_employee DROP INDEX uk_account', 'DO 0');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has_plain := (SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yz_backend_employee'
|
||||
AND INDEX_NAME = 'idx_tid_account');
|
||||
SET @sql := IF(@has_plain > 0, 'ALTER TABLE yz_backend_employee DROP INDEX idx_tid_account', 'DO 0');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @has_uk := (SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yz_backend_employee'
|
||||
AND INDEX_NAME = 'uk_tid_account' AND NON_UNIQUE = 0);
|
||||
SET @sql := IF(@has_uk = 0,
|
||||
'ALTER TABLE yz_backend_employee ADD UNIQUE KEY uk_tid_account (tid, account)',
|
||||
'DO 0');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2. 员工 tid 为空/0:按账号唯一匹配到企业用户后补 tid
|
||||
-- -----------------------------------------------------------------------------
|
||||
UPDATE yz_backend_employee e
|
||||
JOIN (
|
||||
SELECT TRIM(account) AS account, MIN(tid) AS tid
|
||||
FROM yz_system_tenant_user
|
||||
WHERE delete_time IS NULL AND account IS NOT NULL AND TRIM(account) <> ''
|
||||
GROUP BY TRIM(account)
|
||||
HAVING COUNT(DISTINCT tid) = 1
|
||||
) u ON u.account = TRIM(e.account)
|
||||
SET e.tid = u.tid,
|
||||
e.update_time = NOW()
|
||||
WHERE e.delete_time IS NULL
|
||||
AND (e.tid IS NULL OR e.tid = 0);
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 3. 同企业内同名老员工账号对齐
|
||||
-- 条件:① 员工账号在本企业没有对应用户(历史手工人员)
|
||||
-- ② 该用户在本企业还没有员工
|
||||
-- ③ 按姓名能唯一匹配到一个用户
|
||||
-- 只改 account,不动 id,历史引用全部保留
|
||||
-- -----------------------------------------------------------------------------
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_emp_align;
|
||||
CREATE TEMPORARY TABLE tmp_emp_align (
|
||||
emp_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||
tid BIGINT NOT NULL,
|
||||
old_account VARCHAR(64) NOT NULL,
|
||||
new_account VARCHAR(64) NOT NULL
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
INSERT INTO tmp_emp_align (emp_id, tid, old_account, new_account)
|
||||
SELECT e.id, e.tid, TRIM(e.account), MIN(TRIM(u.account))
|
||||
FROM yz_backend_employee e
|
||||
JOIN yz_system_tenant_user u
|
||||
ON u.tid = e.tid
|
||||
AND u.delete_time IS NULL
|
||||
AND u.account IS NOT NULL
|
||||
AND TRIM(u.account) <> ''
|
||||
AND TRIM(u.name) = TRIM(e.name)
|
||||
LEFT JOIN yz_system_tenant_user ux
|
||||
ON ux.tid = e.tid
|
||||
AND ux.delete_time IS NULL
|
||||
AND TRIM(ux.account) = TRIM(e.account)
|
||||
LEFT JOIN yz_backend_employee ex
|
||||
ON ex.tid = e.tid
|
||||
AND ex.delete_time IS NULL
|
||||
AND TRIM(ex.account) = TRIM(u.account)
|
||||
WHERE e.delete_time IS NULL
|
||||
AND e.tid IS NOT NULL
|
||||
AND e.tid > 0
|
||||
AND ux.id IS NULL -- 员工账号在本企业没有用户
|
||||
AND ex.id IS NULL -- 用户在本企业还没有员工
|
||||
GROUP BY e.id, e.tid, TRIM(e.account)
|
||||
HAVING COUNT(DISTINCT TRIM(u.account)) = 1; -- 同名用户不唯一时不动,留待人工
|
||||
|
||||
UPDATE yz_backend_employee e
|
||||
JOIN tmp_emp_align t ON t.emp_id = e.id
|
||||
SET e.account = t.new_account,
|
||||
e.update_time = NOW();
|
||||
|
||||
SELECT '第 3 步:对齐的老员工账号' AS section, emp_id, tid, old_account, new_account
|
||||
FROM tmp_emp_align;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_emp_align;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4. 用户仍然没有员工:按用户资料补建员工记录
|
||||
-- 与 services/EnsureEmployeeForTenantUser 的规则一致(账号、姓名、性别、生日、
|
||||
-- 部门、手机、邮箱、启用状态),密码留空(登录走统一认证,不校验员工密码)
|
||||
-- -----------------------------------------------------------------------------
|
||||
INSERT INTO yz_backend_employee
|
||||
(tid, account, password, name, gender, birthday, department, phone, email,
|
||||
account_status, create_time, update_time)
|
||||
SELECT
|
||||
u.tid,
|
||||
TRIM(u.account),
|
||||
'',
|
||||
COALESCE(NULLIF(TRIM(u.name), ''), TRIM(u.account)),
|
||||
IFNULL(u.sex, 0),
|
||||
STR_TO_DATE(LEFT(TRIM(IFNULL(u.birth, '')), 10), '%Y-%m-%d'),
|
||||
CASE WHEN IFNULL(u.org_id, 0) > 0 THEN CAST(u.org_id AS CHAR) ELSE NULL END,
|
||||
NULLIF(TRIM(IFNULL(u.phone, '')), ''),
|
||||
NULLIF(TRIM(IFNULL(u.email, '')), ''),
|
||||
IF(IFNULL(u.status, 1) = 0, 0, 1),
|
||||
NOW(), NOW()
|
||||
FROM yz_system_tenant_user u
|
||||
LEFT JOIN yz_backend_employee e
|
||||
ON e.delete_time IS NULL
|
||||
AND e.tid = u.tid
|
||||
AND TRIM(e.account) = TRIM(u.account)
|
||||
WHERE u.delete_time IS NULL
|
||||
AND u.account IS NOT NULL
|
||||
AND TRIM(u.account) <> ''
|
||||
AND e.id IS NULL;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 5. 复核
|
||||
-- -----------------------------------------------------------------------------
|
||||
SELECT '执行后:用户/员工数量' AS section;
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM yz_system_tenant_user WHERE delete_time IS NULL) AS user_rows,
|
||||
(SELECT COUNT(*) FROM yz_backend_employee WHERE delete_time IS NULL) AS employee_rows,
|
||||
(SELECT COUNT(*) FROM yz_backend_employee WHERE delete_time IS NULL AND (tid IS NULL OR tid = 0)) AS employee_tid_empty,
|
||||
(SELECT COUNT(*) FROM yz_system_tenant_user u
|
||||
WHERE u.delete_time IS NULL AND u.account IS NOT NULL AND TRIM(u.account) <> ''
|
||||
AND NOT EXISTS (SELECT 1 FROM yz_backend_employee e
|
||||
WHERE e.delete_time IS NULL AND e.tid = u.tid AND TRIM(e.account) = TRIM(u.account))
|
||||
) AS user_without_employee;
|
||||
|
||||
-- 按企业对比:用户数 / 员工数 / 本企业无账号的员工数
|
||||
SELECT
|
||||
u.tid,
|
||||
u.user_cnt,
|
||||
e.emp_cnt,
|
||||
e.emp_without_user
|
||||
FROM (
|
||||
SELECT tid, COUNT(*) AS user_cnt
|
||||
FROM yz_system_tenant_user
|
||||
WHERE delete_time IS NULL
|
||||
GROUP BY tid
|
||||
) u
|
||||
LEFT JOIN (
|
||||
SELECT x.tid,
|
||||
COUNT(*) AS emp_cnt,
|
||||
SUM(CASE WHEN ux.id IS NULL THEN 1 ELSE 0 END) AS emp_without_user
|
||||
FROM yz_backend_employee x
|
||||
LEFT JOIN yz_system_tenant_user ux
|
||||
ON ux.tid = x.tid
|
||||
AND ux.delete_time IS NULL
|
||||
AND TRIM(ux.account) = TRIM(x.account)
|
||||
WHERE x.delete_time IS NULL
|
||||
GROUP BY x.tid
|
||||
) e ON e.tid = u.tid
|
||||
ORDER BY u.tid;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 6. 仍需人工确认:员工在本企业没有登录账号(老数据,脚本不删除也不改)
|
||||
-- 处理方式二选一:
|
||||
-- a) 在「用户管理」用相同账号添加用户 → 会自动关联到已有员工档案,不会重复建档
|
||||
-- b) 在「人员管理」把这些人员设为离职(account_status = 2)
|
||||
-- -----------------------------------------------------------------------------
|
||||
SELECT '仍无登录账号的员工' AS section,
|
||||
e.tid, e.id, TRIM(e.account) AS account, TRIM(e.name) AS name,
|
||||
TRIM(IFNULL(e.department, '')) AS department
|
||||
FROM yz_backend_employee e
|
||||
LEFT JOIN yz_system_tenant_user u
|
||||
ON u.tid = e.tid
|
||||
AND u.delete_time IS NULL
|
||||
AND TRIM(u.account) = TRIM(e.account)
|
||||
WHERE e.delete_time IS NULL
|
||||
AND u.id IS NULL
|
||||
ORDER BY e.tid, e.id;
|
||||
@@ -0,0 +1,122 @@
|
||||
-- ============================================================
|
||||
-- OA AI 办公 —— 菜单配置(yz_system_menu)
|
||||
-- 幂等脚本:按 path 判重,重复执行不会产生重复菜单
|
||||
-- 执行:mysql -h <host> -P <port> -u <user> -p <db> < oa_ai_menus.sql
|
||||
--
|
||||
-- 挂载位置:
|
||||
-- OA → 办公 → AI办公(页面,/apps/oa/ai)
|
||||
-- OA → 基础设置 → AI设置(页面,/basic-settings/ai)
|
||||
--
|
||||
-- 对应页面:
|
||||
-- backend/src/views/apps/oa/ai/index.vue AI 广场(场景对话)
|
||||
-- backend/src/views/basicSettings/ai/index.vue AI 设置(租户级接口 + Key)
|
||||
--
|
||||
-- 说明:
|
||||
-- 1. AI 接入配置是租户级的(yz_backend_ai_provider.user_id = 0),
|
||||
-- 由「AI设置」页统一维护,保存后全租户共用;
|
||||
-- 2. 「AI办公」是 Web 页面(platforms=["web"]),移动端暂不提供;
|
||||
-- 3. 租户端角色(cid=2)的 rights 是显式菜单 ID 列表,只插菜单不授权
|
||||
-- 会被过滤掉,因此本脚本把菜单 ID 追加到角色 rights。
|
||||
-- ============================================================
|
||||
|
||||
SET @oa_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- 「办公」目录:优先同名菜单,其次 /apps/oa/personal;都没有则回落 OA 根
|
||||
SET @office_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE delete_time IS NULL
|
||||
AND type = 1
|
||||
AND (title = '办公' OR title = '个人办公' OR path = '/apps/oa/personal')
|
||||
ORDER BY (title = '办公') DESC, (path = '/apps/oa/personal') DESC, sort, id
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- OA 基础设置目录(AI 设置挂这里)
|
||||
SET @basic_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa/normalsetting' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
SET @office_parent := IFNULL(@office_id, @oa_id);
|
||||
SET @basic_parent := IFNULL(@basic_id, @office_parent);
|
||||
|
||||
-- ---------- AI办公(页面,挂在办公下) ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, platforms, type, creater, remark)
|
||||
SELECT @office_parent, 'AI办公', '/apps/oa/ai', '/apps/oa/ai/index.vue',
|
||||
'MagicStick', 91, 1, 1, '[2]', '["web"]', 2, 'system',
|
||||
'AI 广场:选择接入与模型进行场景对话,支持图片输入、系统提示词与多会话管理'
|
||||
FROM DUAL
|
||||
WHERE @office_parent IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/oa/ai' AND delete_time IS NULL) x);
|
||||
|
||||
SET @ai_page_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa/ai' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
UPDATE yz_system_menu
|
||||
SET pid = IFNULL(@office_parent, pid),
|
||||
sort = 91,
|
||||
platforms = '["web"]',
|
||||
is_visible = 1
|
||||
WHERE path = '/apps/oa/ai' AND delete_time IS NULL;
|
||||
|
||||
-- ---------- AI设置(页面,挂在 OA 基础设置下) ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, platforms, type, creater, remark)
|
||||
SELECT @basic_parent, 'AI设置', '/basic-settings/ai', '/basicSettings/ai/index.vue',
|
||||
'Setting', 9, 1, 1, '[2]', '["web"]', 2, 'system',
|
||||
'租户级 AI 接入配置:填写接口地址与 API Key,支持 OpenAI 兼容与 Anthropic,可测试连通性'
|
||||
FROM DUAL
|
||||
WHERE @basic_parent IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/basic-settings/ai' AND delete_time IS NULL) x);
|
||||
|
||||
SET @ai_settings_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/basic-settings/ai' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
UPDATE yz_system_menu
|
||||
SET pid = IFNULL(@basic_parent, pid),
|
||||
sort = 9,
|
||||
platforms = '["web"]',
|
||||
is_visible = 1
|
||||
WHERE path = '/basic-settings/ai' AND delete_time IS NULL;
|
||||
|
||||
-- ---------- 角色授权:把菜单追加到所有租户端角色的 rights ----------
|
||||
-- 只处理 rights 非空(显式授权)的角色;rights 为空表示全权限,无需追加。
|
||||
UPDATE yz_system_admin_role
|
||||
SET rights = CAST(JSON_ARRAY_APPEND(CAST(rights AS JSON), '$', @ai_page_id) AS CHAR)
|
||||
WHERE cid = 2
|
||||
AND rights IS NOT NULL AND TRIM(rights) <> '' AND JSON_VALID(rights)
|
||||
AND @ai_page_id IS NOT NULL
|
||||
AND NOT JSON_CONTAINS(CAST(rights AS JSON), CAST(@ai_page_id AS JSON));
|
||||
|
||||
UPDATE yz_system_admin_role
|
||||
SET rights = CAST(JSON_ARRAY_APPEND(CAST(rights AS JSON), '$', @ai_settings_id) AS CHAR)
|
||||
WHERE cid = 2
|
||||
AND rights IS NOT NULL AND TRIM(rights) <> '' AND JSON_VALID(rights)
|
||||
AND @ai_settings_id IS NOT NULL
|
||||
AND NOT JSON_CONTAINS(CAST(rights AS JSON), CAST(@ai_settings_id AS JSON));
|
||||
|
||||
-- ---------- 结果校验 ----------
|
||||
SELECT id, pid, title, path, component_path, platforms, type, sort, is_visible
|
||||
FROM yz_system_menu
|
||||
WHERE delete_time IS NULL
|
||||
AND (path = '/apps/oa/ai' OR path = '/basic-settings/ai')
|
||||
ORDER BY pid, sort;
|
||||
|
||||
SELECT id, name, tenant_id,
|
||||
JSON_CONTAINS(CAST(rights AS JSON), CAST(@ai_page_id AS JSON)) AS has_ai_page,
|
||||
JSON_CONTAINS(CAST(rights AS JSON), CAST(@ai_settings_id AS JSON)) AS has_ai_settings
|
||||
FROM yz_system_admin_role
|
||||
WHERE cid = 2 AND rights IS NOT NULL AND TRIM(rights) <> ''
|
||||
ORDER BY tenant_id, id;
|
||||
@@ -0,0 +1,160 @@
|
||||
-- ============================================================
|
||||
-- OA 考勤管理 —— 菜单配置(yz_system_menu)
|
||||
-- 幂等脚本:按 path 判重,重复执行不会产生重复菜单
|
||||
-- 执行:mysql -h <host> -P <port> -u <user> -p <db> < oa_attendance_menus.sql
|
||||
--
|
||||
-- 挂载位置:OA → 办公(个人办公),直接是页面,不再套「排班考勤」目录
|
||||
-- OA
|
||||
-- └─ 办公(/apps/oa/personal,找不到时回落到 OA 根)
|
||||
-- ├─ 工作排班(Web,/apps/oa/scheduling/board)
|
||||
-- ├─ 考勤管理(Web,/apps/oa/attendance) ← 本脚本
|
||||
-- └─ 考勤打卡(App,/apps/oa/attendance/clock) ← 本脚本
|
||||
--
|
||||
-- 页面:
|
||||
-- backend/src/views/apps/oa/attendance/index.vue 考勤管理(考勤汇总 + 考勤明细)
|
||||
-- uniapp/pages/oa/attendance/index.vue 考勤打卡(移动端)
|
||||
--
|
||||
-- 说明:
|
||||
-- 1. 「考勤管理」是直接页面(type=2),点菜单即打开,不再有子菜单;
|
||||
-- 2. 打卡动作在移动端完成,Web 端只做考勤汇总 / 明细 / 缺卡名单 / 备注,
|
||||
-- 因此「考勤管理」platforms 为 ["web"];
|
||||
-- 3. 「考勤打卡」platforms 为 ["app"],通过 uniapp 菜单跳转原生页面
|
||||
-- (uniapp/utils/nav.js 的 NATIVE_RULES 按「考勤」匹配);
|
||||
-- 4. 历史版本创建的「排班考勤」目录不再使用,脚本会把它隐藏(保留数据);
|
||||
-- 其下的「工作排班」直接上移到「办公」;
|
||||
-- 5. 租户端角色(cid=2)的 rights 是显式菜单 ID 列表,只插菜单不授权
|
||||
-- 会被过滤掉,因此本脚本把菜单 ID 追加到角色 rights。
|
||||
-- ============================================================
|
||||
|
||||
SET @oa_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- 「办公」目录:优先同名菜单,其次 /apps/oa/personal;都没有则回落 OA 根
|
||||
SET @office_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE delete_time IS NULL
|
||||
AND type = 1
|
||||
AND (title = '办公' OR title = '个人办公' OR path = '/apps/oa/personal')
|
||||
ORDER BY (title = '办公') DESC, (path = '/apps/oa/personal') DESC, sort, id
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
SET @parent_id := IFNULL(@office_id, @oa_id);
|
||||
|
||||
-- ---------- 考勤管理(Web 页面,直接挂在办公下) ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, platforms, type, creater, remark)
|
||||
SELECT @parent_id, '考勤管理', '/apps/oa/attendance', '/apps/oa/attendance/index.vue',
|
||||
'Stopwatch', 89, 1, 1, '[2]', '["web"]', 2, 'system',
|
||||
'移动端签到签退数据,支持月/季/年日历汇总、全员考勤卡、缺卡名单与备注'
|
||||
FROM DUAL
|
||||
WHERE @parent_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/oa/attendance' AND delete_time IS NULL) x);
|
||||
|
||||
-- 已存在则纠正为「直接页面」:改名、上移、确保是页面与 Web 端可见
|
||||
UPDATE yz_system_menu
|
||||
SET title = '考勤管理',
|
||||
pid = IFNULL(@parent_id, pid),
|
||||
sort = 89,
|
||||
type = 2,
|
||||
component_path = '/apps/oa/attendance/index.vue',
|
||||
platforms = '["web"]',
|
||||
is_visible = 1
|
||||
WHERE path = '/apps/oa/attendance' AND delete_time IS NULL;
|
||||
|
||||
SET @records_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa/attendance' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- ---------- 考勤打卡(移动端页面,同样直接挂在办公下) ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, platforms, type, creater, remark)
|
||||
SELECT @parent_id, '考勤打卡', '/apps/oa/attendance/clock', NULL,
|
||||
'AlarmClock', 90, 1, 1, '[2]', '["app"]', 2, 'system',
|
||||
'小程序签到/签退,联动工作排班;未匹配排班时提醒但允许打卡'
|
||||
FROM DUAL
|
||||
WHERE @parent_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/oa/attendance/clock' AND delete_time IS NULL) x);
|
||||
|
||||
UPDATE yz_system_menu
|
||||
SET pid = IFNULL(@parent_id, pid),
|
||||
sort = 90,
|
||||
platforms = '["app"]',
|
||||
is_visible = 1
|
||||
WHERE path = '/apps/oa/attendance/clock' AND delete_time IS NULL;
|
||||
|
||||
SET @clock_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa/attendance/clock' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- ---------- 工作排班上移:不再保留「排班考勤」目录 ----------
|
||||
UPDATE yz_system_menu
|
||||
SET pid = IFNULL(@parent_id, pid), sort = 88
|
||||
WHERE path = '/apps/oa/scheduling/board'
|
||||
AND delete_time IS NULL
|
||||
AND @parent_id IS NOT NULL
|
||||
AND (pid IS NULL OR pid <> @parent_id);
|
||||
|
||||
-- 历史的「排班考勤」目录:不再使用,隐藏(保留数据,避免影响已有角色授权)
|
||||
-- 仅当目录下已无可见子菜单时才隐藏,避免误伤租户自行添加的子菜单
|
||||
UPDATE yz_system_menu
|
||||
SET is_visible = 0
|
||||
WHERE path = '/apps/oa/scheduling'
|
||||
AND type = 1
|
||||
AND delete_time IS NULL
|
||||
AND id NOT IN (
|
||||
SELECT pid FROM (
|
||||
SELECT pid FROM yz_system_menu
|
||||
WHERE delete_time IS NULL AND is_visible = 1 AND pid > 0
|
||||
) child
|
||||
);
|
||||
|
||||
SET @board_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa/scheduling/board' AND delete_time IS NULL
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- ---------- 角色授权:把菜单追加到所有租户端角色的 rights ----------
|
||||
-- 只处理 rights 非空(显式授权)的角色;rights 为空表示全权限,无需追加。
|
||||
UPDATE yz_system_admin_role
|
||||
SET rights = CAST(JSON_ARRAY_APPEND(CAST(rights AS JSON), '$', @records_id) AS CHAR)
|
||||
WHERE cid = 2
|
||||
AND rights IS NOT NULL AND TRIM(rights) <> '' AND JSON_VALID(rights)
|
||||
AND @records_id IS NOT NULL
|
||||
AND NOT JSON_CONTAINS(CAST(rights AS JSON), CAST(@records_id AS JSON));
|
||||
|
||||
UPDATE yz_system_admin_role
|
||||
SET rights = CAST(JSON_ARRAY_APPEND(CAST(rights AS JSON), '$', @clock_id) AS CHAR)
|
||||
WHERE cid = 2
|
||||
AND rights IS NOT NULL AND TRIM(rights) <> '' AND JSON_VALID(rights)
|
||||
AND @clock_id IS NOT NULL
|
||||
AND NOT JSON_CONTAINS(CAST(rights AS JSON), CAST(@clock_id AS JSON));
|
||||
|
||||
UPDATE yz_system_admin_role
|
||||
SET rights = CAST(JSON_ARRAY_APPEND(CAST(rights AS JSON), '$', @board_id) AS CHAR)
|
||||
WHERE cid = 2
|
||||
AND rights IS NOT NULL AND TRIM(rights) <> '' AND JSON_VALID(rights)
|
||||
AND @board_id IS NOT NULL
|
||||
AND NOT JSON_CONTAINS(CAST(rights AS JSON), CAST(@board_id AS JSON));
|
||||
|
||||
-- ---------- 结果校验 ----------
|
||||
SELECT id, pid, title, path, component_path, platforms, type, sort, is_visible
|
||||
FROM yz_system_menu
|
||||
WHERE delete_time IS NULL
|
||||
AND (path LIKE '/apps/oa/attendance%' OR path LIKE '/apps/oa/scheduling%')
|
||||
ORDER BY pid, sort;
|
||||
|
||||
SELECT id, name, tenant_id,
|
||||
JSON_CONTAINS(CAST(rights AS JSON), CAST(@records_id AS JSON)) AS has_attendance,
|
||||
JSON_CONTAINS(CAST(rights AS JSON), CAST(@clock_id AS JSON)) AS has_clock
|
||||
FROM yz_system_admin_role
|
||||
WHERE cid = 2 AND rights IS NOT NULL AND TRIM(rights) <> ''
|
||||
ORDER BY tenant_id, id;
|
||||
@@ -3,10 +3,12 @@
|
||||
-- 幂等脚本:按 path 判重,重复执行不会产生重复菜单
|
||||
-- 执行:mysql -h <host> -P <port> -u <user> -p <db> < oa_scheduling_menus.sql
|
||||
--
|
||||
-- 挂载位置:OA 应用根(path = '/apps/oa')
|
||||
-- 挂载位置:OA → 办公(个人办公),直接是页面,不再套「排班考勤」目录
|
||||
-- OA
|
||||
-- └─ 排班考勤(目录,/apps/oa/scheduling)
|
||||
-- └─ 工作排班(页面,/apps/oa/scheduling/board)
|
||||
-- └─ 办公
|
||||
-- ├─ 工作排班(页面,/apps/oa/scheduling/board) ← 本脚本
|
||||
-- ├─ 考勤管理(页面,/apps/oa/attendance) ← oa_attendance_menus.sql
|
||||
-- └─ 考勤打卡(App,/apps/oa/attendance/clock) ← oa_attendance_menus.sql
|
||||
--
|
||||
-- 对应页面:backend/src/views/apps/oa/scheduling/index.vue
|
||||
-- 同一页面内完成:班次管理、排班方案(公司/部门/小组/指定员工)、
|
||||
@@ -14,11 +16,11 @@
|
||||
--
|
||||
-- 说明:
|
||||
-- 1. 排班看板是 Web 管理端功能,platforms 显式为 ["web"],
|
||||
-- 移动端菜单不出现(后续若开放员工查看排班,再补 APP 端页面)。
|
||||
-- 2. 租户端角色(cid=2)的 rights 是显式菜单 ID 列表,菜单接口会按
|
||||
-- rights 过滤。只插菜单不授权,会出现「目录可见但页面被过滤」,
|
||||
-- 因此本脚本同时把两个菜单 ID 追加到角色 rights。
|
||||
-- 目录与页面都必须授权,缺一不可。
|
||||
-- 移动端菜单不出现;
|
||||
-- 2. 历史版本创建的「排班考勤」目录不再使用,脚本会把它隐藏(保留数据);
|
||||
-- 3. 租户端角色(cid=2)的 rights 是显式菜单 ID 列表,菜单接口会按
|
||||
-- rights 过滤。只插菜单不授权,会出现「菜单存在但被过滤」,
|
||||
-- 因此本脚本同时把菜单 ID 追加到角色 rights。
|
||||
-- ============================================================
|
||||
|
||||
SET @oa_id := (
|
||||
@@ -27,32 +29,51 @@ SET @oa_id := (
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- ---------- 排班考勤(目录) ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, platforms, type, creater, remark)
|
||||
SELECT @oa_id, '排班考勤', '/apps/oa/scheduling', NULL,
|
||||
'Calendar', 88, 1, 1, '[2]', '["web"]', 1, 'system',
|
||||
'按公司、部门、小组或指定员工制定排班方案,支持临时调配'
|
||||
FROM DUAL
|
||||
WHERE @oa_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/oa/scheduling' AND delete_time IS NULL) x);
|
||||
|
||||
SET @scheduling_id := (
|
||||
-- 「办公」目录:优先同名菜单,其次 /apps/oa/personal;都没有则回落 OA 根
|
||||
SET @office_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa/scheduling' AND delete_time IS NULL
|
||||
WHERE delete_time IS NULL
|
||||
AND type = 1
|
||||
AND (title = '办公' OR title = '个人办公' OR path = '/apps/oa/personal')
|
||||
ORDER BY (title = '办公') DESC, (path = '/apps/oa/personal') DESC, sort, id
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- ---------- 工作排班(页面) ----------
|
||||
SET @parent_id := IFNULL(@office_id, @oa_id);
|
||||
|
||||
-- ---------- 工作排班(页面,直接挂在办公下) ----------
|
||||
INSERT INTO yz_system_menu
|
||||
(pid, title, path, component_path, icon, sort, status, is_visible, views, platforms, type, creater, remark)
|
||||
SELECT @scheduling_id, '工作排班', '/apps/oa/scheduling/board', '/apps/oa/scheduling/index.vue',
|
||||
'Clock', 1, 1, 1, '[2]', '["web"]', 2, 'system',
|
||||
SELECT @parent_id, '工作排班', '/apps/oa/scheduling/board', '/apps/oa/scheduling/index.vue',
|
||||
'Clock', 88, 1, 1, '[2]', '["web"]', 2, 'system',
|
||||
'排班看板:按周查看排班,支持方案发布、单条调班与批量临时排班'
|
||||
FROM DUAL
|
||||
WHERE @scheduling_id IS NOT NULL
|
||||
WHERE @parent_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM yz_system_menu WHERE path = '/apps/oa/scheduling/board' AND delete_time IS NULL) x);
|
||||
|
||||
-- 已存在则上移到「办公」,并纠正为 Web 页面
|
||||
UPDATE yz_system_menu
|
||||
SET pid = IFNULL(@parent_id, pid),
|
||||
sort = 88,
|
||||
type = 2,
|
||||
platforms = '["web"]',
|
||||
is_visible = 1
|
||||
WHERE path = '/apps/oa/scheduling/board' AND delete_time IS NULL;
|
||||
|
||||
-- 历史的「排班考勤」目录:不再使用,隐藏(保留数据)
|
||||
-- 仅当目录下已无可见子菜单时才隐藏,避免误伤租户自行添加的子菜单
|
||||
UPDATE yz_system_menu
|
||||
SET is_visible = 0
|
||||
WHERE path = '/apps/oa/scheduling'
|
||||
AND type = 1
|
||||
AND delete_time IS NULL
|
||||
AND id NOT IN (
|
||||
SELECT pid FROM (
|
||||
SELECT pid FROM yz_system_menu
|
||||
WHERE delete_time IS NULL AND is_visible = 1 AND pid > 0
|
||||
) child
|
||||
);
|
||||
|
||||
SET @board_id := (
|
||||
SELECT id FROM yz_system_menu
|
||||
WHERE path = '/apps/oa/scheduling/board' AND delete_time IS NULL
|
||||
@@ -61,16 +82,6 @@ SET @board_id := (
|
||||
|
||||
-- ---------- 角色授权:把菜单追加到所有租户端角色的 rights ----------
|
||||
-- 只处理 rights 非空(显式授权)的角色;rights 为空表示全权限,无需追加。
|
||||
|
||||
UPDATE yz_system_admin_role
|
||||
SET rights = CAST(JSON_ARRAY_APPEND(CAST(rights AS JSON), '$', @scheduling_id) AS CHAR)
|
||||
WHERE cid = 2
|
||||
AND rights IS NOT NULL
|
||||
AND TRIM(rights) <> ''
|
||||
AND JSON_VALID(rights)
|
||||
AND @scheduling_id IS NOT NULL
|
||||
AND NOT JSON_CONTAINS(CAST(rights AS JSON), CAST(@scheduling_id AS JSON));
|
||||
|
||||
UPDATE yz_system_admin_role
|
||||
SET rights = CAST(JSON_ARRAY_APPEND(CAST(rights AS JSON), '$', @board_id) AS CHAR)
|
||||
WHERE cid = 2
|
||||
@@ -81,13 +92,13 @@ WHERE cid = 2
|
||||
AND NOT JSON_CONTAINS(CAST(rights AS JSON), CAST(@board_id AS JSON));
|
||||
|
||||
-- ---------- 结果校验 ----------
|
||||
SELECT id, pid, title, path, component_path, platforms, type, sort
|
||||
SELECT id, pid, title, path, component_path, platforms, type, sort, is_visible
|
||||
FROM yz_system_menu
|
||||
WHERE delete_time IS NULL AND path LIKE '/apps/oa/scheduling%'
|
||||
WHERE delete_time IS NULL
|
||||
AND (path LIKE '/apps/oa/scheduling%' OR path = '/apps/oa/attendance')
|
||||
ORDER BY pid, sort;
|
||||
|
||||
SELECT id, name, tenant_id,
|
||||
JSON_CONTAINS(CAST(rights AS JSON), CAST(@scheduling_id AS JSON)) AS has_dir,
|
||||
JSON_CONTAINS(CAST(rights AS JSON), CAST(@board_id AS JSON)) AS has_board
|
||||
FROM yz_system_admin_role
|
||||
WHERE cid = 2 AND rights IS NOT NULL AND TRIM(rights) <> ''
|
||||
|
||||
Reference in new issue
Block a user