From e235b3b7f5a7db97fa8a71dc4a638d918af22c0e Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期四, 17 九月 2026 13:33:18 +0800
Subject: [PATCH] feat(sql): 迁移脚本补齐审批配置地基 + 采购计划/订单变更,并加订单审批测试
---
scripts/test-purchase-order-api.js | 284 ++++++++++++++++++
sql/mysql/xgdl_review_audit_pending_changes.sql | 271 ++++++++++++++++-
scripts/test-purchase-order-ui.js | 333 ++++++++++++++++++++++
3 files changed, 869 insertions(+), 19 deletions(-)
diff --git a/scripts/test-purchase-order-api.js b/scripts/test-purchase-order-api.js
new file mode 100644
index 0000000..2441f34
--- /dev/null
+++ b/scripts/test-purchase-order-api.js
@@ -0,0 +1,284 @@
+/**
+ * 閲囪喘璁㈠崟鐘舵�佹満瀹℃壒 - 鎺ュ彛绾ц嚜鍔ㄥ寲娴嬭瘯
+ *
+ * 鑷缓娴嬭瘯鍗曟嵁锛堟瘡娆℃柊寤轰竴寮狅級锛屼笉渚濊禆鍘嗗彶鏁版嵁鐘舵�侊紝鍙噸澶嶆墽琛屻��
+ * 瑕嗙洊锛氭彁浜�/瀹℃牳/椹冲洖/閲嶆柊鎻愪氦/閫氳繃鐨勫畬鏁撮摼璺� + 鍏ㄩ儴鏈嶅姟绔牎楠屽垎鏀��
+ *
+ * 鐢ㄦ硶锛歯ode scripts/test-purchase-order-api.js
+ * 鍙�夌幆澧冨彉閲忥細TEST_USER / TEST_PASS / PRODUCT_ID / PRODUCT_UNIT_ID
+ */
+const BASE = process.env.TEST_BASE || 'http://127.0.0.1:48080/admin-api';
+const USER = process.env.TEST_USER || 'admin';
+const PASS = process.env.TEST_PASS || 'admin123';
+const BIZ_TYPE = 'erp_purchase_order_approve';
+// 鐗╂枡鍙栬嚜 mdm_item锛堟祴璇曞簱涓� id=57銆屾祴璇曘�嶏紝鍗曚綅 id=9锛�
+const PRODUCT_ID = Number(process.env.PRODUCT_ID || 57);
+const PRODUCT_UNIT_ID = Number(process.env.PRODUCT_UNIT_ID || 9);
+
+const results = [];
+let TOKEN = '';
+
+function ok(name, extra = '') {
+ results.push({ pass: true, name });
+ console.log(` 鉁� ${name}${extra ? ' 鈥� ' + extra : ''}`);
+}
+function fail(name, extra = '') {
+ results.push({ pass: false, name, extra });
+ console.log(` 鉂� ${name}${extra ? ' 鈥� ' + extra : ''}`);
+}
+
+async function api(method, path, body) {
+ const res = await fetch(BASE + path, {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'tenant-id': '1',
+ ...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}),
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ let json = null;
+ try {
+ json = await res.json();
+ } catch {
+ /* 闈� JSON */
+ }
+ return { status: res.status, json };
+}
+
+async function login() {
+ console.log('\n[0] 鐧诲綍');
+ const { json } = await api('POST', '/system/auth/login', { username: USER, password: PASS });
+ if (!json || json.code !== 0 || !json.data?.accessToken) {
+ fail('鐧诲綍澶辫触', JSON.stringify(json));
+ process.exit(1);
+ }
+ TOKEN = json.data.accessToken;
+ ok('鐧诲綍鎴愬姛');
+}
+
+/** 纭繚鐩爣涓氬姟绫诲瀷宸插惎鐢ㄤ笖瀹℃壒浜哄惈 admin(id=1)锛岃繑鍥為厤缃璞� */
+async function ensureConfig(userIds) {
+ const { json } = await api('GET', `/system/approval-config/page?pageNo=1&pageSize=100&bizType=${BIZ_TYPE}`);
+ const row = (json?.data?.list || []).find((x) => x.bizType === BIZ_TYPE);
+ if (!row) {
+ fail('鏈壘鍒板鎵归厤缃瀛愭暟鎹�', BIZ_TYPE);
+ return null;
+ }
+ const save = await api('POST', '/system/approval-config/save', {
+ id: row.id,
+ bizType: row.bizType,
+ bizName: row.bizName,
+ approvalEnabled: true,
+ remark: row.remark,
+ userIds,
+ });
+ if (!save.json || save.json.code !== 0) {
+ fail('淇濆瓨瀹℃壒閰嶇疆澶辫触', JSON.stringify(save.json));
+ return null;
+ }
+ const again = await api('GET', `/system/approval-config/page?pageNo=1&pageSize=100&bizType=${BIZ_TYPE}`);
+ return (again.json?.data?.list || []).find((x) => x.bizType === BIZ_TYPE) || row;
+}
+
+/**
+ * 閲囪喘璁㈠崟鐨勪緵搴斿晢鏍¢獙璧� SRM锛圗rpSupplierServiceImpl.validateSupplier -> srmSupplierApi锛夛紝
+ * 浣嗕緵搴斿晢澧炲垹鏀规煡涓庡墠绔笅鎷夊張璇诲啓 erp_supplier锛屼袱澶勬暟鎹簮涓嶄竴鑷淬��
+ * 鍥犳娴嬭瘯鏁版嵁蹇呴』寤哄湪 SRM 渚э紝鍚﹀垯涓嬪崟蹇呯劧鎶ャ�屼緵搴斿晢涓嶅瓨鍦ㄣ�嶃��
+ */
+async function getSupplierId() {
+ const { json } = await api('GET', '/srm/supplier/simple-list');
+ const list = json?.data || [];
+ if (list.length > 0) return list[0].id;
+ const seq = Date.now().toString().slice(-6);
+ const created = await api('POST', '/srm/supplier/create', {
+ code: `TESTSUP${seq}`,
+ name: `鑷姩鍖栨祴璇曚緵搴斿晢${seq}`,
+ status: 0,
+ remark: '鐢� test-purchase-order-api.js 鍒涘缓',
+ });
+ if (created.json?.code !== 0) {
+ fail('鍒涘缓 SRM 渚涘簲鍟嗗け璐�', JSON.stringify(created.json));
+ return null;
+ }
+ return created.json.data;
+}
+
+async function createOrder(supplierId) {
+ const body = {
+ supplierId,
+ orderTime: '2026-09-16 10:00:00',
+ discountPercent: 0,
+ depositPrice: 0,
+ remark: '鑷姩鍖栨祴璇曢噰璐鍗�',
+ items: [
+ {
+ productId: PRODUCT_ID,
+ productUnitId: PRODUCT_UNIT_ID,
+ productPrice: 100,
+ count: 5,
+ taxPercent: 13,
+ remark: '娴嬭瘯椤�',
+ },
+ ],
+ };
+ const { json } = await api('POST', '/purchase/order/create', body);
+ return json;
+}
+
+async function getOrder(id) {
+ const { json } = await api('GET', `/purchase/order/get?id=${id}`);
+ return json?.data;
+}
+
+(async () => {
+ await login();
+
+ console.log('\n[1] 瀹℃壒閰嶇疆');
+ const cfg = await ensureConfig([1]);
+ if (!cfg) process.exit(1);
+ ok('閲囪喘璁㈠崟瀹℃壒宸插惎鐢�', `bizName=${cfg.bizName}`);
+
+ const ap = await api('GET', `/purchase/order/approver-ids`);
+ if (ap.json?.code === 0 && (ap.json.data || []).includes(1)) ok('approver-ids 鍚� admin(1)', JSON.stringify(ap.json.data));
+ else fail('approver-ids 寮傚父', JSON.stringify(ap.json));
+
+ console.log('\n[2] 鏂板缓閲囪喘璁㈠崟');
+ const supplierId = await getSupplierId();
+ if (!supplierId) {
+ console.log(' 鏃犳硶鍑嗗渚涘簲鍟嗭紝缁堟');
+ process.exit(1);
+ }
+ ok('渚涘簲鍟嗗氨缁�', `supplierId=${supplierId}`);
+
+ const created = await createOrder(supplierId);
+ if (!created || created.code !== 0) {
+ fail('鍒涘缓閲囪喘璁㈠崟澶辫触', JSON.stringify(created));
+ process.exit(1);
+ }
+ const id = created.data;
+ ok('鍒涘缓閲囪喘璁㈠崟鎴愬姛', `id=${id}`);
+
+ const o0 = await getOrder(id);
+ if (o0?.status === 0) ok('鏂板崟鍒濆鐘舵�佷负鏈彁浜�(0)', `no=${o0.no}`);
+ else fail('鏂板崟鍒濆鐘舵�佷笉鏄� 0', `status=${o0?.status}`);
+
+ console.log('\n[3] BPM 娈嬬暀鎺ュ彛搴斿凡涓嬬嚎');
+ const legacy = await api('GET', '/purchase/order/approve-process-list');
+ if (legacy.status === 404 || (legacy.json && legacy.json.code !== 0)) {
+ ok('approve-process-list 宸茬Щ闄�', `HTTP ${legacy.status} code=${legacy.json?.code}`);
+ } else {
+ fail('approve-process-list 浠嶅彲鐢�', JSON.stringify(legacy.json).slice(0, 120));
+ }
+
+ console.log('\n[4] 鏈彁浜や笉鑳藉鏍�');
+ const early = await api('PUT', '/purchase/order/audit', { id, pass: true, reviewRemark: '鎶㈣窇' });
+ if (early.json?.code === 1_030_101_016) ok('姝g‘鎷掔粷锛氶潪瀹℃壒涓笉鍙鏍�', `code=${early.json.code}`);
+ else fail('鏈寜棰勬湡鎷掔粷', JSON.stringify(early.json));
+
+ console.log('\n[5] 鎻愪氦 鈫� 瀹℃壒涓�');
+ const sub = await api('PUT', `/purchase/order/submit?id=${id}`);
+ if (sub.json?.code === 0) ok('鎻愪氦鎴愬姛', JSON.stringify(sub.json));
+ else fail('鎻愪氦澶辫触', JSON.stringify(sub.json));
+ const o1 = await getOrder(id);
+ if (o1?.status === 10) ok('鐘舵�佸凡缃负瀹℃壒涓�(10)');
+ else fail('鎻愪氦鍚庣姸鎬佷笉瀵�', `status=${o1?.status}`);
+
+ console.log('\n[6] 瀹℃壒涓笉鍙慨鏀�');
+ const upd = await api('PUT', '/purchase/order/update', {
+ id,
+ supplierId,
+ orderTime: '2026-09-16 10:00:00',
+ remark: '瀹℃壒涓敼鍗�',
+ items: [{ productId: PRODUCT_ID, productUnitId: PRODUCT_UNIT_ID, productPrice: 100, count: 5, taxPercent: 13 }],
+ });
+ if (upd.json?.code === 1_030_101_019) ok('姝g‘鎷掔粷锛氬鎵逛腑涓嶅彲淇敼', `code=${upd.json.code}`);
+ else fail('瀹℃壒涓眳鐒跺彲淇敼', JSON.stringify(upd.json));
+
+ console.log('\n[7] 闈炲鎵逛汉涓嶅彲瀹℃牳');
+ await ensureConfig([111]); // 涓存椂鎶婂鎵逛汉鏀规垚銆屾祴璇曠敤鎴枫��
+ const notAp = await api('PUT', '/purchase/order/audit', { id, pass: true, reviewRemark: '瓒婃潈' });
+ if (notAp.json?.code === 1_002_030_005) ok('姝g‘鎷掔粷锛氬綋鍓嶇敤鎴蜂笉鏄鎵逛汉', `code=${notAp.json.code}`);
+ else fail('瓒婃潈瀹℃牳鏈鎷︽埅', JSON.stringify(notAp.json));
+ await ensureConfig([1]); // 杩樺師
+ const back = await api('GET', '/purchase/order/approver-ids');
+ if ((back.json?.data || []).includes(1)) ok('瀹℃壒浜哄凡杩樺師涓� admin');
+ else fail('瀹℃壒浜鸿繕鍘熷け璐�', JSON.stringify(back.json?.data));
+
+ console.log('\n[8] 椹冲洖蹇呴』濉師鍥�');
+ const noWhy = await api('PUT', '/purchase/order/audit', { id, pass: false });
+ if (noWhy.json?.code === 1_030_101_017) ok('姝g‘鎷掔粷锛氱己椹冲洖鍘熷洜', `code=${noWhy.json.code}`);
+ else fail('缂哄師鍥犳湭琚嫤鎴�', JSON.stringify(noWhy.json));
+
+ console.log('\n[9] 瀹℃牳涓嶉�氳繃 鈫� 閫�鍥�');
+ const rej = await api('PUT', '/purchase/order/audit', { id, pass: false, reviewRemark: '浠锋牸鍋忛珮锛岄��鍥為噸璁�' });
+ if (rej.json?.code === 0) ok('椹冲洖鎴愬姛', JSON.stringify(rej.json));
+ else fail('椹冲洖澶辫触', JSON.stringify(rej.json));
+ const o2 = await getOrder(id);
+ if (o2?.status === 30) ok('鐘舵�佷负瀹℃牳涓嶉�氳繃(30)');
+ else fail('椹冲洖鍚庣姸鎬佷笉瀵�', `status=${o2?.status}`);
+ if (o2?.reviewerName && o2?.reviewRemark === '浠锋牸鍋忛珮锛岄��鍥為噸璁�') ok('瀹℃牳浜轰笌椹冲洖鍘熷洜宸茶惤搴�', `${o2.reviewerName} / ${o2.reviewRemark}`);
+ else fail('瀹℃牳淇℃伅鏈惤搴�', JSON.stringify({ rn: o2?.reviewerName, rr: o2?.reviewRemark }));
+
+ console.log('\n[10] 閫�鍥炲悗鍙慨鏀瑰苟閲嶆柊鎻愪氦');
+ const upd2 = await api('PUT', '/purchase/order/update', {
+ id,
+ supplierId,
+ orderTime: '2026-09-16 10:00:00',
+ remark: '宸查檷浠烽噸璁�',
+ items: [{ productId: PRODUCT_ID, productUnitId: PRODUCT_UNIT_ID, productPrice: 80, count: 5, taxPercent: 13 }],
+ });
+ if (upd2.json?.code === 0) ok('閫�鍥炲悗鍙慨鏀�', JSON.stringify(upd2.json));
+ else fail('閫�鍥炲悗浠嶄笉鍙慨鏀�', JSON.stringify(upd2.json));
+
+ const sub2 = await api('PUT', `/purchase/order/submit?id=${id}`);
+ if (sub2.json?.code === 0) ok('閲嶆柊鎻愪氦鎴愬姛');
+ else fail('閲嶆柊鎻愪氦澶辫触', JSON.stringify(sub2.json));
+ const o3 = await getOrder(id);
+ if (o3?.status === 10 && !o3?.reviewRemark && !o3?.reviewerName) ok('閲嶆柊鎻愪氦宸叉竻绌轰笂涓�杞鏍镐俊鎭�', `reviewRemark=${JSON.stringify(o3?.reviewRemark ?? null)}`);
+ else fail('閲嶆彁鍚庡鏍镐俊鎭畫鐣�', JSON.stringify({ st: o3?.status, rn: o3?.reviewerName, rr: o3?.reviewRemark }));
+
+ console.log('\n[11] 瀹℃牳閫氳繃');
+ const app = await api('PUT', '/purchase/order/audit', { id, pass: true, reviewRemark: '鍚屾剰閲囪喘' });
+ if (app.json?.code === 0) ok('瀹℃牳閫氳繃鎴愬姛', JSON.stringify(app.json));
+ else fail('瀹℃牳閫氳繃澶辫触', JSON.stringify(app.json));
+ const o4 = await getOrder(id);
+ if (o4?.status === 20) ok('鐘舵�佷负瀹℃牳閫氳繃(20)', `瀹℃牳浜�=${o4.reviewerName}`);
+ else fail('閫氳繃鍚庣姸鎬佷笉瀵�', `status=${o4?.status}`);
+
+ console.log('\n[12] 宸插鏍镐笉鍙慨鏀�');
+ const upd3 = await api('PUT', '/purchase/order/update', {
+ id,
+ supplierId,
+ orderTime: '2026-09-16 10:00:00',
+ remark: '瀹℃牳鍚庡啀鏀�',
+ items: [{ productId: PRODUCT_ID, productUnitId: PRODUCT_UNIT_ID, productPrice: 80, count: 5, taxPercent: 13 }],
+ });
+ if (upd3.json?.code === 1_030_101_005) ok('姝g‘鎷掔粷锛氬凡瀹℃牳涓嶅彲淇敼', `code=${upd3.json.code}`);
+ else fail('宸插鏍镐粛鍙慨鏀�', JSON.stringify(upd3.json));
+
+ console.log('\n[13] 鍏ㄧ▼涓嶅簲鍐嶅嚭鐜� 500 绯荤粺寮傚父');
+ const calls = [
+ ['GET', `/purchase/order/page?pageNo=1&pageSize=10`],
+ ['GET', `/purchase/order/approver-ids`],
+ ];
+ let bad500 = 0;
+ for (const [m, p] of calls) {
+ const r = await api(m, p);
+ if (r.json?.code === 500) {
+ bad500++;
+ console.log(` ${m} ${p} 鈫� 500`);
+ }
+ }
+ if (bad500 === 0) ok('鍏抽敭鎺ュ彛鏃� 500');
+ else fail(`浠嶆湁 ${bad500} 涓帴鍙h繑鍥� 500`);
+
+ console.log(`\n娴嬭瘯鍗曟嵁锛歟rp_purchase_order id=${id}锛�${o0?.no}锛塦);
+ const passed = results.filter((r) => r.pass).length;
+ console.log(`===== 姹囨�伙細${passed}/${results.length} 閫氳繃 =====`);
+ results.filter((r) => !r.pass).forEach((r) => console.log(` 澶辫触: ${r.name} ${r.extra}`));
+ process.exit(passed === results.length ? 0 : 1);
+})().catch((e) => {
+ console.error('鎵ц寮傚父:', e.message);
+ process.exit(1);
+});
diff --git a/scripts/test-purchase-order-ui.js b/scripts/test-purchase-order-ui.js
new file mode 100644
index 0000000..b256e65
--- /dev/null
+++ b/scripts/test-purchase-order-ui.js
@@ -0,0 +1,333 @@
+/**
+ * 閲囪喘璁㈠崟瀹℃壒 - 娴忚鍣ㄧ鍒扮娴嬭瘯
+ *
+ * 鍓嶇疆鏁版嵁鐢� admin-api 鐩存帴鍑嗗锛堜竴娆¤窇瀹岀暀涓嬶細1 寮犳湭鎻愪氦 + 1 寮犲鎵逛腑锛夛紝
+ * 涔嬪悗鍏ㄩ儴璧扮湡瀹炴祻瑙堝櫒浜や簰楠岃瘉鎸夐挳鍙鎬с�佸脊绐楁牎楠屼笌鐘舵�佹祦杞��
+ *
+ * 鐢ㄦ硶锛歯ode scripts/test-purchase-order-ui.js
+ */
+const { chromium } = require('playwright');
+const path = require('path');
+const fs = require('fs');
+
+const API = process.env.TEST_API || 'http://127.0.0.1:48080/admin-api';
+const BASE = process.env.TEST_BASE || 'http://localhost:5666';
+const USER = process.env.TEST_USER || 'admin';
+const PASS = process.env.TEST_PASS || 'admin123';
+const BIZ_TYPE = 'erp_purchase_order_approve';
+const PRODUCT_ID = Number(process.env.PRODUCT_ID || 57);
+const PRODUCT_UNIT_ID = Number(process.env.PRODUCT_UNIT_ID || 9);
+
+const SHOT_DIR = path.join(__dirname, 'shots');
+if (!fs.existsSync(SHOT_DIR)) fs.mkdirSync(SHOT_DIR, { recursive: true });
+
+const results = [];
+function ok(name, extra = '') {
+ results.push({ pass: true, name });
+ console.log(` 鉁� ${name}${extra ? ' 鈥� ' + extra : ''}`);
+}
+function fail(name, extra = '') {
+ results.push({ pass: false, name, extra });
+ console.log(` 鉂� ${name}${extra ? ' 鈥� ' + extra : ''}`);
+}
+
+let TOKEN = '';
+async function api(method, p, body) {
+ const res = await fetch(API + p, {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'tenant-id': '1',
+ ...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}),
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ try {
+ return await res.json();
+ } catch {
+ return null;
+ }
+}
+
+async function seed() {
+ console.log('\n[S] 鍑嗗娴嬭瘯鏁版嵁锛堟帴鍙o級');
+ const lg = await api('POST', '/system/auth/login', { username: USER, password: PASS });
+ if (!lg || lg.code !== 0) {
+ fail('鎺ュ彛鐧诲綍澶辫触', JSON.stringify(lg));
+ process.exit(1);
+ }
+ TOKEN = lg.data.accessToken;
+ ok('鎺ュ彛鐧诲綍鎴愬姛');
+
+ const page = await api('GET', `/system/approval-config/page?pageNo=1&pageSize=100&bizType=${BIZ_TYPE}`);
+ const cfg = (page?.data?.list || []).find((x) => x.bizType === BIZ_TYPE);
+ if (!cfg) {
+ fail('缂哄皯瀹℃壒閰嶇疆绉嶅瓙');
+ process.exit(1);
+ }
+ await api('POST', '/system/approval-config/save', {
+ id: cfg.id,
+ bizType: cfg.bizType,
+ bizName: cfg.bizName,
+ approvalEnabled: true,
+ remark: cfg.remark,
+ userIds: [1],
+ });
+ ok('瀹℃壒宸插惎鐢紝瀹℃壒浜�=admin');
+
+ // 姣忔璺戦兘鐢ㄥ敮涓�鍛藉悕鐨勪緵搴斿晢锛屼究浜庡湪鍒楄〃閲岀簿纭攣瀹氭湰娆$瀛愬崟鎹�
+ const seq = Date.now().toString().slice(-6);
+ const supplierName = `UIAUTOSUP${seq}`;
+ let supplierId = null;
+ const c = await api('POST', '/srm/supplier/create', {
+ code: `UIAUTOSUP${seq}`,
+ name: supplierName,
+ status: 0,
+ });
+ supplierId = c?.data;
+ if (!supplierId) {
+ const sup = await api('GET', '/srm/supplier/simple-list');
+ const hit = (sup?.data || []).find((x) => x.name === supplierName) || (sup?.data || [])[0];
+ supplierId = hit?.id;
+ }
+ if (!supplierId) {
+ fail('鏃犳硶鍑嗗渚涘簲鍟嗭紙娉ㄦ剰 ERP/SRM 鏁版嵁婧愪笉涓�鑷撮棶棰橈級', JSON.stringify(c));
+ process.exit(1);
+ }
+ ok('渚涘簲鍟嗗氨缁�', `supplierId=${supplierId} name=${supplierName}`);
+
+ const mk = async () => {
+ return api('POST', '/purchase/order/create', {
+ supplierId,
+ orderTime: '2026-09-16 10:00:00',
+ discountPercent: 0,
+ depositPrice: 0,
+ remark: supplierName,
+ items: [
+ {
+ productId: PRODUCT_ID,
+ productUnitId: PRODUCT_UNIT_ID,
+ productPrice: 100,
+ count: 5,
+ taxPercent: 13,
+ },
+ ],
+ });
+ };
+
+ const a = await mk();
+ const b = await mk();
+ if (a?.code !== 0 || b?.code !== 0) {
+ fail('鍒涘缓娴嬭瘯璁㈠崟澶辫触', JSON.stringify({ a, b }));
+ process.exit(1);
+ }
+ const draftId = a.data;
+ const approvingId = b.data;
+ const sub = await api('PUT', `/purchase/order/submit?id=${approvingId}`);
+ if (sub?.code !== 0) {
+ fail('棰勭疆銆屽鎵逛腑銆嶅崟鎹け璐�', JSON.stringify(sub));
+ process.exit(1);
+ }
+ ok('鏁版嵁灏辩华', `鑽夌 id=${draftId}锛屽鎵逛腑 id=${approvingId}`);
+ return { approvingId, draftId, supplierName };
+}
+
+async function shot(page, name) {
+ const p = path.join(SHOT_DIR, `${name}.png`);
+ await page.screenshot({ path: p, fullPage: true });
+ console.log(` 馃摳 ${p}`);
+}
+
+/**
+ * 鍙栨暟鎹涓庡浐瀹氬彸鍒楄锛屾寜绱㈠紩閰嶅銆�
+ * 娉ㄦ剰锛氭湰椤点�岀姸鎬併�嶅垪鏄� fixed:right锛屼笌鎿嶄綔鎸夐挳鍚屽鍥哄畾鍙冲垪瀹瑰櫒锛�
+ * 鍥犳 main 閲屼笉鍚姸鎬侊紝蹇呴』浠� fixed 鏂囨湰閲岃鐘舵�併��
+ */
+async function rowPairs(page) {
+ const dataRows = await page.locator('.vxe-table--main-wrapper .vxe-body--row').all();
+ const fixedRows = await page.locator('.vxe-table--fixed-right-wrapper .vxe-body--row').all();
+ const out = [];
+ const n = Math.min(dataRows.length, fixedRows.length);
+ for (let i = 0; i < n; i++) {
+ out.push({
+ main: (await dataRows[i].innerText()).replace(/\s+/g, ' '),
+ fixed: (await fixedRows[i].innerText()).replace(/\s+/g, ' ').trim(),
+ actionRow: fixedRows[i],
+ });
+ }
+ return out;
+}
+
+(async () => {
+ const seeded = await seed();
+
+ const browser = await chromium.launch({ headless: true, channel: 'chrome' });
+ const page = await browser.newPage({ viewport: { width: 1680, height: 950 } });
+
+ const apiErrors = [];
+ page.on('response', async (r) => {
+ if (!r.url().includes('/admin-api/')) return;
+ try {
+ const b = await r.json();
+ if (b && typeof b.code === 'number' && b.code !== 0) {
+ apiErrors.push({ url: r.url().replace(BASE, ''), code: b.code, msg: b.msg });
+ }
+ } catch {
+ /* ignore */
+ }
+ });
+
+ try {
+ // 1. 鐧诲綍
+ console.log('\n[1] 娴忚鍣ㄧ櫥褰�');
+ await page.goto(`${BASE}/auth/login`, { waitUntil: 'networkidle', timeout: 60000 });
+ await page.locator('input[placeholder="璇疯緭鍏ョ敤鎴峰悕"]').first().fill(USER);
+ await page.locator('input[type="password"]').first().fill(PASS);
+ await page.locator('button[aria-label="login"]').first().click();
+ await page.waitForURL((u) => !u.pathname.includes('/login'), { timeout: 30000 });
+ ok('鐧诲綍鎴愬姛');
+
+ // 2. 瀹℃壒閰嶇疆椤靛簲鑳界湅鍒伴噰璐鍗曞鏍�
+ console.log('\n[2] 瀹℃壒閰嶇疆椤�');
+ await page.goto(`${BASE}/system/approval-config`, { waitUntil: 'networkidle', timeout: 60000 });
+ await page.waitForTimeout(2500);
+ const cfgRow = page.locator('.vxe-body--row', { hasText: '閲囪喘璁㈠崟瀹℃牳' });
+ if ((await cfgRow.count()) > 0) {
+ const t = (await cfgRow.first().innerText()).replace(/\s+/g, ' ');
+ if (t.includes('宸插惎鐢�')) ok('閲囪喘璁㈠崟瀹℃壒鏄剧ず宸插惎鐢�', t.slice(0, 100));
+ else fail('閲囪喘璁㈠崟瀹℃壒鏈樉绀哄凡鍚敤', t.slice(0, 100));
+ if (t.includes('瓒呯骇绠$悊鍛�')) ok('瀹℃壒浜烘樉绀鸿秴绾х鐞嗗憳');
+ else fail('瀹℃壒浜烘湭鏄剧ず', t.slice(0, 100));
+ } else {
+ fail('閰嶇疆椤垫壘涓嶅埌銆岄噰璐鍗曞鏍搞�嶈');
+ }
+ await shot(page, 'po-01-config');
+
+ // 3. 閲囪喘璁㈠崟鍒楄〃鎸夐挳鍙鎬�
+ console.log('\n[3] 閲囪喘璁㈠崟鍒楄〃鎸夐挳');
+ await page.goto(`${BASE}/purchase/order`, { waitUntil: 'networkidle', timeout: 60000 });
+ await page.waitForTimeout(3000);
+ await shot(page, 'po-02-list');
+
+ const pairs = await rowPairs(page);
+ if (pairs.length === 0) {
+ fail('鍒楄〃鏃犳暟鎹');
+ throw new Error('empty grid');
+ }
+ // 鏈绉嶅瓙鍗曟嵁闈犲敮涓�渚涘簲鍟嗗悕瀹氫綅锛堝垪琛ㄤ笉灞曠ず澶囨敞鍒楋級
+ const mine = pairs.filter((p) => p.main.includes(seeded.supplierName));
+ if (mine.length < 2) {
+ fail('鏈兘鍦ㄥ垪琛ㄤ腑瀹氫綅鍒版湰娆$瀛愯鍗�', `鍛戒腑 ${mine.length} 琛� / 鍏� ${pairs.length} 琛宍);
+ pairs.forEach((p) => console.log(' ROW:', p.main.slice(0, 60), '||', p.fixed));
+ } else {
+ ok('鎸変緵搴斿晢鍚嶅畾浣嶅埌鏈绉嶅瓙鍗曟嵁', `${mine.length} 琛宍);
+ }
+ const draft = mine.find((p) => p.fixed.includes('鑽夌'));
+ const approving = mine.find((p) => p.fixed.includes('瀹℃壒涓�'));
+ if (draft) ok('鑽夌鍗曟覆鏌撲负銆岃崏绋裤��', draft.fixed);
+ else fail('鏈壘鍒拌崏绋跨姸鎬佺殑绉嶅瓙璁㈠崟', mine.map((p) => p.fixed).join(' | '));
+ if (approving) ok('瀹℃壒涓崟娓叉煋涓恒�屽鎵逛腑銆�', approving.fixed);
+ else fail('鏈壘鍒板鎵逛腑鐘舵�佺殑绉嶅瓙璁㈠崟', mine.map((p) => p.fixed).join(' | '));
+
+ if (draft) {
+ const hasSubmit = /鎻愪氦/.test(draft.fixed);
+ const hasAudit = draft.fixed.includes('瀹℃牳');
+ const hasEdit = /(淇敼|缂栬緫)/.test(draft.fixed);
+ if (hasSubmit && hasEdit && !hasAudit) ok('鑽夌琛岋細鏈夈�屾彁浜ゃ�嶃�屼慨鏀广�嶏紝鏃犲鏍告寜閽�', draft.fixed);
+ else fail('鑽夌琛屾寜閽笉绗�', draft.fixed);
+ }
+ if (approving) {
+ const hasBoth =
+ approving.fixed.includes('瀹℃牳涓嶉�氳繃') &&
+ /瀹℃牳(?!\s*涓嶉�氳繃)/.test(approving.fixed.replace('瀹℃牳涓嶉�氳繃', ''));
+ const noSubmitEdit = !/鎻愪氦/.test(approving.fixed) && !/(淇敼|缂栬緫)/.test(approving.fixed);
+ if (hasBoth && noSubmitEdit) ok('瀹℃壒涓锛氭湁銆屽鏍搞�嶃�屽鏍镐笉閫氳繃銆嶏紝鏃犳彁浜�/淇敼', approving.fixed);
+ else fail('瀹℃壒涓鎸夐挳涓嶇', approving.fixed);
+ }
+
+ // 4. 璧颁竴閬嶅鏍镐笉閫氳繃寮圭獥锛堝惈蹇呭~鏍¢獙锛�
+ if (approving) {
+ console.log('\n[4] 瀹℃牳涓嶉�氳繃寮圭獥');
+ await approving.actionRow.getByText('瀹℃牳涓嶉�氳繃', { exact: true }).first().click();
+ await page.waitForTimeout(1200);
+ const dialog = page.locator('[role="dialog"]').last();
+ if ((await dialog.count()) === 0) {
+ fail('鏈脊鍑哄鏍稿脊绐�');
+ } else {
+ const dlgText = (await dialog.innerText()).replace(/\s+/g, ' ');
+ ok('寮圭獥宸叉墦寮�', dlgText.slice(0, 120));
+ const btns = await dialog.locator('button').allInnerTexts();
+ console.log(' 寮圭獥鎸夐挳:', JSON.stringify(btns.map((b) => b.trim()).filter(Boolean)));
+
+ // 绌哄師鍥犵洿鎺ョ‘璁わ紝搴旇鍓嶇蹇呭~鎷︿綇锛堝脊绐椾笉鍏筹級
+ const confirmBtn = dialog
+ .locator('button')
+ .filter({ hasText: /纭甛s*璁纭甛s*瀹殀鎻怽s*浜OK/ })
+ .last();
+ await confirmBtn.click();
+ await page.waitForTimeout(1200);
+ const stillOpen = (await page.locator('[role="dialog"]').count()) > 0;
+ if (stillOpen) ok('鏈~鍘熷洜鏃惰蹇呭~鏍¢獙鎷︿笅');
+ else fail('鏈~鍘熷洜绔熸彁浜ゆ垚鍔燂紙鍓嶇鏍¢獙缂哄け锛�');
+
+ // 濉師鍥犲悗纭
+ await dialog.locator('textarea').first().fill('UI娴嬭瘯锛氬崟浠峰亸楂橈紝閫�鍥為噸璁�');
+ const resp = page
+ .waitForResponse(
+ (r) => r.url().includes('/purchase/order/audit'),
+ { timeout: 15000 },
+ )
+ .catch(() => null);
+ await confirmBtn.click();
+ const res = await resp;
+ if (!res) fail('鏈崟鑾� audit 鍝嶅簲');
+ else {
+ const body = await res.json().catch(() => null);
+ if (body?.code === 0) ok('椹冲洖鎻愪氦鎴愬姛', JSON.stringify(body));
+ else fail('椹冲洖澶辫触', JSON.stringify(body));
+ }
+ await page.waitForTimeout(2000);
+ }
+ await shot(page, 'po-03-after-reject');
+
+ // 5. 鐘舵�佸簲鍙樹负瀹℃壒涓嶉�氳繃锛屼笖鍙噸鏂颁慨鏀�
+ await page.reload({ waitUntil: 'networkidle' });
+ await page.waitForTimeout(3000);
+ const pairs2 = await rowPairs(page);
+ const mine2 = pairs2.filter((p) => p.main.includes(seeded.supplierName));
+ const rejected = mine2.find((p) => p.fixed.includes('瀹℃壒涓嶉�氳繃'));
+ if (rejected) {
+ ok('椹冲洖鍚庢覆鏌撲负銆屽鎵逛笉閫氳繃銆�', rejected.fixed);
+ const canResubmit = /鎻愪氦/.test(rejected.fixed) && /(淇敼|缂栬緫)/.test(rejected.fixed);
+ const noAudit = !rejected.fixed.includes('瀹℃牳');
+ if (canResubmit && noAudit) ok('椹冲洖鍗曞彲銆屼慨鏀广�嶅苟鍙噸鏂般�屾彁浜ゃ�嶏紝涓斾笉鍐嶆樉绀哄鏍告寜閽�', rejected.fixed);
+ else fail('椹冲洖鍗曞叆鍙d笉绗�', rejected.fixed);
+ } else {
+ fail('鏈壘鍒板鎵逛笉閫氳繃鐨勭瀛愯鍗�', mine2.map((p) => p.fixed).join(' | '));
+ }
+ await shot(page, 'po-04-rejected');
+ }
+
+ console.log(`\n锛堢瀛愬崟鎹� id锛氳崏绋�=${seeded.draftId} 瀹℃壒涓啋椹冲洖=${seeded.approvingId}锛塦);
+ } catch (e) {
+ fail('鎵ц寮傚父', e.message);
+ await shot(page, 'po-99-crash').catch(() => {});
+ }
+
+ console.log('\n===== 鎹曡幏鍒扮殑鎺ュ彛涓氬姟閿欒 =====');
+ const relevant = apiErrors.filter(
+ (e) =>
+ !e.url.includes('/auth/login') &&
+ !e.url.includes('simple-list') &&
+ !e.url.includes('/dict') &&
+ !e.url.includes('unread-count'),
+ );
+ if (relevant.length === 0) console.log(' 锛堟棤锛�');
+ relevant.forEach((e) => console.log(` ${e.url} 鈫� code=${e.code} msg=${e.msg}`));
+
+ await browser.close();
+ const passed = results.filter((r) => r.pass).length;
+ console.log(`\n===== 姹囨�伙細${passed}/${results.length} 閫氳繃 =====`);
+ results.filter((r) => !r.pass).forEach((r) => console.log(` 澶辫触: ${r.name} ${r.extra}`));
+ process.exit(passed === results.length ? 0 : 1);
+})();
diff --git a/sql/mysql/xgdl_review_audit_pending_changes.sql b/sql/mysql/xgdl_review_audit_pending_changes.sql
index 38e98c2..5631353 100644
--- a/sql/mysql/xgdl_review_audit_pending_changes.sql
+++ b/sql/mysql/xgdl_review_audit_pending_changes.sql
@@ -3,9 +3,21 @@
-- 鐩爣搴擄細mom-xgdl
-- 璇存槑锛氭湰鑴氭湰骞傜瓑锛屽彲閲嶅鎵ц锛涘凡瀛樺湪鐨勫瓧娈�/鑿滃崟浼氳嚜鍔ㄨ烦杩囥��
-- 寮�鍙戝簱锛�192.168.197.128锛夊凡鍏ㄩ儴鎵ц杩囷紝鏈剼鏈敤浜庡悜鍏跺畠鐜锛堝娴嬭瘯/鐢熶骇锛夊悓姝ャ��
+-- 鍐呭缁撴瀯锛�
+-- 绗竴閮ㄥ垎 20 寮犲崟鎹〃鐨勫鏍镐汉鍥涘瓧娈�
+-- 绗簩閮ㄥ垎 涓婅堪鍗曟嵁鐨勩�屽鏍搞�嶆寜閽潈闄愶紙system_menu type=3锛�
+-- 绗笁閮ㄥ垎 鐢靛姏浣滀笟璁″垝鐢熸垚宸ュ崟缂栫爜瑙勫垯璇存槑锛堟棤闇�鎵ц锛�
+-- 绗洓閮ㄥ垎 閫氱敤瀹℃壒閰嶇疆琛� + 26 涓笟鍔$被鍨� + 瀹℃壒閰嶇疆椤佃彍鍗�
+-- + 閲囪喘璁″垝/閲囪喘璁㈠崟瀹℃牳瀛楁涓庢寜閽� + 瑙掕壊鎺堟潈
+--
+-- 鎵ц鍓嶇疆瑕佹眰锛氬繀椤荤敤鏀寔 DELIMITER 涓庡瓨鍌ㄨ繃绋嬬殑瀹㈡埛绔暣浣撴墽琛�
+-- 锛坢ysql CLI锛歮ysql -h<host> -P<port> -u<user> -p mom-xgdl < 鏈枃浠讹級銆�
+-- 澶氭暟鍥惧舰鍖�/鍦ㄧ嚎 SQL 鎺у埗鍙版寜鍒嗗彿鎷嗗垎璇彞锛屼細鎶� DELIMITER $$ 娈佃惤鎷嗗潖銆�
+--
-- 瀵瑰簲浠g爜鎻愪氦锛�
-- refactor(crm/erp/hrm/mes): 鍗曟嵁瀹℃牳鏀逛负瀹℃壒閰嶇疆鐘舵�佹満
-- fix(mes): 鐢靛姏浣滀笟璁″垝鐢熸垚宸ュ崟琛ュ厖缂栫爜瑙勫垯鍙栧彿
+-- e347fd6e refactor(approval): 閲囪喘璁″垝/閲囪喘璁㈠崟瀹℃壒鐢� BPM 鏀逛负杞婚噺绾х姸鎬佹満
-- ============================================================================
SET NAMES utf8mb4;
@@ -154,115 +166,115 @@
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '閿�鎴风敵璇峰鏍�' AS name, 'crm:cancellation:audit' AS perm, 'crm:cancellation:update' AS anchor) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'crm:cancellation:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'crm:cancellation:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'crm:cancellation:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '鍚堝悓瀹℃牳' AS name, 'crm:contract:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'crm:contract:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'crm:contract:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'crm:contract:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '鍙戠エ瀹℃牳' AS name, 'crm:invoice:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'crm:invoice:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'crm:invoice:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'crm:invoice:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '閫�璐瑰崟瀹℃牳' AS name, 'crm:refund:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'crm:refund:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'crm:refund:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'crm:refund:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '浠樻鍗曞鏍�' AS name, 'erp:finance-payment:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'erp:finance-payment:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'erp:finance-payment:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'erp:finance-payment:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '閲囪喘鐢宠瀹℃牳' AS name, 'erp:purchase-request:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'erp:purchase-request:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'erp:purchase-request:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'erp:purchase-request:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '璇峰亣鐢宠瀹℃牳' AS name, 'hrm:leave:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'hrm:leave:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:leave:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:leave:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '杞鐢宠瀹℃牳' AS name, 'hrm:regularize:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'hrm:regularize:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:regularize:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:regularize:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '绂昏亴鐢宠瀹℃牳' AS name, 'hrm:resignation:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'hrm:resignation:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:resignation:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:resignation:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '璋冨姩鐢宠瀹℃牳' AS name, 'hrm:transfer:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'hrm:transfer:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:transfer:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:transfer:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '鑰冨嫟寮傚父瀹℃牳' AS name, 'hrm:attendance-exception:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'hrm:attendance-exception:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:attendance-exception:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:attendance-exception:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '椤圭洰瀹℃牳' AS name, 'mes:pd-project:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:pd-project:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:pd-project:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:pd-project:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '鎶�鏈‘璁ゅ嚱瀹℃牳' AS name, 'mes:pd-tech-confirm:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:pd-tech-confirm:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:pd-tech-confirm:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:pd-tech-confirm:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '閲囪喘鍏ュ簱鍗曞鏍�' AS name, 'mes:wm-item-receipt:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:wm-item-receipt:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm-item-receipt:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm-item-receipt:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '鏉傞」鍑哄簱鍗曞鏍�' AS name, 'mes:wm-misc-issue:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:wm-misc-issue:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm-misc-issue:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm-misc-issue:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '閲囪喘閫�璐у崟瀹℃牳' AS name, 'mes:wm-return-vendor:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:wm-return-vendor:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm-return-vendor:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm-return-vendor:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '鐩樼偣浠诲姟瀹℃牳' AS name, 'mes:wm-stock-taking-task:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:wm-stock-taking-task:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm-stock-taking-task:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm-stock-taking-task:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '璁惧鍏ュ簱鍗曞鏍�' AS name, 'mes:wm:equipment-receipt:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:wm:equipment-receipt:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm:equipment-receipt:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm:equipment-receipt:audit') m);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM (SELECT '鏉傞」鍏ュ簱鍗曞鏍�' AS name, 'mes:wm:misc-receipt:audit' AS perm) t
JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:wm:misc-receipt:update') a ON 1 = 1
-WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm:misc-receipt:audit') m);
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:wm:misc-receipt:audit') m);
-- ============================================================================
-- 绗笁閮ㄥ垎锛氱數鍔涗綔涓氳鍒掔敓鎴愬伐鍗� 鈥斺�� 宸ュ崟缂栫爜鍙栧彿瑙勫垯锛堟棤鍙樻洿锛屼粎璇存槑锛�
@@ -273,7 +285,228 @@
-- 锛堟敞鎰忥細璇ヨ剼鏈細 TRUNC 寮忛噸寤鸿鍒欒〃锛屼粎閫傜敤浜庨娆″垵濮嬪寲锛屽嬁鍦ㄥ凡鏈夋暟鎹殑鐜鎵ц锛夈��
-- ============================================================================
+-- ============================================================================
+-- 绗洓閮ㄥ垎锛氶�氱敤瀹℃壒閰嶇疆锛堝鎵瑰紩鎿庣殑鍦板熀锛�
+--
+-- 鍓嶄笁閮ㄥ垎鍙姞浜嗐�屽鏍镐汉瀛楁銆嶅拰銆屽鏍告寜閽潈闄愩�嶏紝浣嗘病鏈夊缓瀹℃壒閰嶇疆琛ㄦ湰韬��
+-- 鍙窇鍓嶄笁閮ㄥ垎鐨勮瘽锛屾墍鏈夊崟鎹殑銆屾彁浜ゃ�嶉兘浼氬湪 validateApprovalEnabledAndGetApprovers
+-- 澶勫け璐ワ紙瀹℃壒閰嶇疆涓嶅瓨鍦� / 鏈惎鐢� / 鏈厤缃鎵逛汉锛夈�傛湰閮ㄥ垎琛ラ綈锛�
+-- 4.1 瀹℃壒閰嶇疆涓昏〃 + 瀹℃壒浜哄瓙琛�
+-- 4.2 26 涓笟鍔$被鍨嬬瀛愶紙榛樿鏈惎鐢級
+-- 4.3 瀹℃壒閰嶇疆椤甸潰鑿滃崟涓庢煡璇�/淇濆瓨鎸夐挳
+-- 4.4 閲囪喘璁″垝銆侀噰璐鍗曠殑瀹℃牳浜哄洓瀛楁
+-- 4.5 閲囪喘璁″垝銆侀噰璐鍗曠殑瀹℃牳鎸夐挳鏉冮檺
+-- 4.6 瑙掕壊鎺堟潈锛堟湰妗嗘灦鏃犮�岃秴绠$粫杩囨潈闄愩�嶉�昏緫锛屼笉鎺堟潈鍒� admin 涔熺湅涓嶅埌鎸夐挳锛�
+--
+-- 瀵瑰簲浠g爜鎻愪氦锛歟347fd6e refactor(approval): 閲囪喘璁″垝/閲囪喘璁㈠崟瀹℃壒鐢� BPM 鏀逛负杞婚噺绾х姸鎬佹満
+-- ============================================================================
+
+-- 4.1 瀹℃壒閰嶇疆涓昏〃 / 瀹℃壒浜哄瓙琛�
+CREATE TABLE IF NOT EXISTS system_approval_config (
+ id bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+ biz_type varchar(64) NOT NULL COMMENT '涓氬姟绫诲瀷缂栫爜锛屼笌鍚勬ā鍧楀鎵瑰垎绫荤紪鐮佷竴鑷�',
+ biz_name varchar(100) NOT NULL COMMENT '涓氬姟绫诲瀷鍚嶇О',
+ approval_enabled bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍚敤瀹℃壒',
+ remark varchar(500) NULL DEFAULT NULL COMMENT '澶囨敞',
+ creator varchar(64) NULL DEFAULT '' COMMENT '鍒涘缓鑰�',
+ create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ updater varchar(64) NULL DEFAULT '' COMMENT '鏇存柊鑰�',
+ update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (id) USING BTREE,
+ KEY idx_biz_type (biz_type) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '閫氱敤瀹℃壒閰嶇疆琛�';
+
+CREATE TABLE IF NOT EXISTS system_approval_config_user (
+ id bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+ config_id bigint NOT NULL COMMENT '瀹℃壒閰嶇疆ID锛堝叧鑱� system_approval_config.id锛�',
+ user_id bigint NOT NULL COMMENT '瀹℃壒浜虹敤鎴稩D锛堢郴缁熺敤鎴凤級',
+ sort int NOT NULL DEFAULT 0 COMMENT '鏄剧ず椤哄簭',
+ creator varchar(64) NULL DEFAULT '' COMMENT '鍒涘缓鑰�',
+ create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ updater varchar(64) NULL DEFAULT '' COMMENT '鏇存柊鑰�',
+ update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ deleted bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (id) USING BTREE,
+ KEY idx_config_id (config_id) USING BTREE,
+ KEY idx_user_id (user_id) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '閫氱敤瀹℃壒閰嶇疆-瀹℃壒浜鸿〃';
+
+-- 4.2 涓氬姟绫诲瀷绉嶅瓙锛氱紪鐮佷笌鍚勬ā鍧� *_APPROVE_* 甯搁噺涓�涓�瀵瑰簲锛岄粯璁ゅ叏閮ㄦ湭鍚敤
+INSERT IGNORE INTO system_approval_config
+ (biz_type, biz_name, approval_enabled, remark, creator, create_time, updater, update_time, deleted) VALUES
+('purchase_plan_approve', '閲囪喘璁″垝瀹℃牳', b'0', 'ERP 閲囪喘璁″垝', '1', NOW(), '1', NOW(), b'0'),
+('purchase_request_approve', '閲囪喘鐢宠瀹℃牳', b'0', 'ERP 閲囪喘鐢宠', '1', NOW(), '1', NOW(), b'0'),
+('erp_purchase_order_approve', '閲囪喘璁㈠崟瀹℃牳', b'0', 'ERP 閲囪喘璁㈠崟锛堝師璇敤 purchase_request_approve锛屽凡鏀逛负鐙珛缂栫爜锛�', '1', NOW(), '1', NOW(), b'0'),
+('erp_purchase_invoice_approve', '閲囪喘鏉ョエ瀹℃牳', b'0', 'ERP 閲囪喘鏉ョエ', '1', NOW(), '1', NOW(), b'0'),
+('erp_finance_payment_approve', '浠樻鍗曞鏍�', b'0', 'ERP 浠樻鍗�', '1', NOW(), '1', NOW(), b'0'),
+('contract_approve', '鍚堝悓瀹℃壒', b'0', 'CRM 鍚堝悓', '1', NOW(), '1', NOW(), b'0'),
+('refund_approve', '閫�璐瑰鎵�', b'0', 'CRM 閫�娆�', '1', NOW(), '1', NOW(), b'0'),
+('cancellation_approve', '閿�鎴峰鎵�', b'0', 'CRM 閿�鎴�', '1', NOW(), '1', NOW(), b'0'),
+('crm_invoice_approve', '寮�绁ㄥ鎵�', b'0', 'CRM 鍙戠エ', '1', NOW(), '1', NOW(), b'0'),
+('sale_quotation_approve', '閿�鍞姤浠峰鎵�', b'0', 'CRM 閿�鍞姤浠�', '1', NOW(), '1', NOW(), b'0'),
+('hrm_leave_approve', '璇峰亣瀹℃壒', b'0', 'HRM 璇峰亣鐢宠', '1', NOW(), '1', NOW(), b'0'),
+('hrm_regularize_approve', '杞瀹℃壒', b'0', 'HRM 杞鐢宠', '1', NOW(), '1', NOW(), b'0'),
+('hrm_resignation_approve', '绂昏亴瀹℃壒', b'0', 'HRM 绂昏亴鐢宠', '1', NOW(), '1', NOW(), b'0'),
+('hrm_transfer_approve', '璋冨姩瀹℃壒', b'0', 'HRM 璋冨姩鐢宠', '1', NOW(), '1', NOW(), b'0'),
+('hrm_attendance_approve', '鑰冨嫟寮傚父瀹℃壒', b'0', 'HRM 鑰冨嫟寮傚父', '1', NOW(), '1', NOW(), b'0'),
+('performance_appraise_approve', '缁╂晥鑰冩牳瀹℃壒', b'0', 'HRM 缁╂晥鑰冩牳', '1', NOW(), '1', NOW(), b'0'),
+('mes_pd_project_approve', '浜у搧椤圭洰瀹℃壒', b'0', 'MES 浜у搧椤圭洰', '1', NOW(), '1', NOW(), b'0'),
+('pd_tech_confirm_approve', '鎶�鏈‘璁ゅ嚱瀹℃壒', b'0', 'MES 浜у搧鎶�鏈‘璁ゅ嚱', '1', NOW(), '1', NOW(), b'0'),
+('item_receipt_approve', '閲囪喘鍏ュ簱瀹℃壒', b'0', 'MES 閲囪喘鍏ュ簱鍗�', '1', NOW(), '1', NOW(), b'0'),
+('equipment_receipt_approve', '璁惧鍏ュ簱瀹℃壒', b'0', 'MES 璁惧鍏ュ簱鍗�', '1', NOW(), '1', NOW(), b'0'),
+('misc_issue_approve', '鍏朵粬鍑哄簱瀹℃壒', b'0', 'MES 鍏朵粬鍑哄簱鍗�', '1', NOW(), '1', NOW(), b'0'),
+('misc_receipt_approve', '鍏朵粬鍏ュ簱瀹℃壒', b'0', 'MES 鍏朵粬鍏ュ簱鍗�', '1', NOW(), '1', NOW(), b'0'),
+('return_vendor_approve', '閫�璐у鎵�', b'0', 'MES 閫�璐у崟', '1', NOW(), '1', NOW(), b'0'),
+('stock_taking_result_approve', '鐩樼偣缁撴灉瀹℃壒', b'0', 'MES 鐩樼偣鍗�', '1', NOW(), '1', NOW(), b'0'),
+('wms_approve', '浠撳簱鍗曟嵁瀹℃牳', b'0', 'MES 浠撳偍閫氱敤瀹℃壒', '1', NOW(), '1', NOW(), b'0'),
+('compensation_approve', '璧斾粯瀹℃壒', b'0', '鍞悗璧斿伩鍗�', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.3 瀹℃壒閰嶇疆椤甸潰鑿滃崟
+-- 鐖剁骇閿氬畾銆岀郴缁熺鐞嗐�嶏細鍙� system/user/index 鐨� parent_id锛岄伩鍏嶇‖缂栫爜鐩綍 ID
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT '瀹℃壒閰嶇疆', '', 2, 8, a.parent_id, 'approval-config', 'ep:stamp', 'system/approvalConfig/index', 'SystemApprovalConfig', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM (SELECT MIN(m.parent_id) AS parent_id FROM system_menu m WHERE m.deleted = 0 AND m.component = 'system/user/index') a
+WHERE a.parent_id IS NOT NULL
+ AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND component = 'system/approvalConfig/index') x);
+
+-- 4.3.1 瀹℃壒閰嶇疆鏌ヨ鎸夐挳锛堢埗绾� = 涓婇潰鐨勯〉闈㈣彍鍗曪紝鎸� component 鍙嶆煡锛�
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT '瀹℃壒閰嶇疆鏌ヨ', 'system:approval-config:query', 3, 1, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM (SELECT MIN(m.id) AS parent_id FROM system_menu m WHERE m.deleted = 0 AND m.component = 'system/approvalConfig/index') a
+WHERE a.parent_id IS NOT NULL
+ AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'system:approval-config:query') x);
+
+-- 4.3.2 瀹℃壒閰嶇疆淇濆瓨鎸夐挳
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT '瀹℃壒閰嶇疆淇濆瓨', 'system:approval-config:update', 3, 2, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM (SELECT MIN(m.id) AS parent_id FROM system_menu m WHERE m.deleted = 0 AND m.component = 'system/approvalConfig/index') a
+WHERE a.parent_id IS NOT NULL
+ AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'system:approval-config:update') x);
+
+-- 4.4 閲囪喘璁″垝 / 閲囪喘璁㈠崟 瀹℃牳浜哄洓瀛楁
+-- 锛堢涓�閮ㄥ垎鍙鐩栦簡鍙﹀ 20 寮犺〃锛岃繖涓ゅ紶鏄噰璐鍒�/閲囪喘璁㈠崟鏀归�犳椂琛ョ殑锛�
+DROP TABLE IF EXISTS _tmp_xgdl_po_review_cols;
+CREATE TABLE _tmp_xgdl_po_review_cols (
+ tbl VARCHAR(64) NOT NULL,
+ col VARCHAR(64) NOT NULL,
+ coldef VARCHAR(255) NOT NULL
+);
+
+INSERT INTO _tmp_xgdl_po_review_cols (tbl, col, coldef) VALUES
+('erp_purchase_plan', 'reviewer_id', 'bigint COMMENT ''瀹℃牳浜虹紪鍙凤紙瀹為檯鎵ц瀹℃牳鐨勭敤鎴凤級'''),
+('erp_purchase_plan', 'reviewer_name', 'varchar(64) COMMENT ''瀹℃牳浜哄鍚嶏紙鍐椾綑璁板綍锛岄伩鍏嶅垪琛ㄦ煡璇㈠啀鏌ョ敤鎴凤級'''),
+('erp_purchase_plan', 'review_time', 'datetime COMMENT ''瀹℃牳鏃堕棿'''),
+('erp_purchase_plan', 'review_remark', 'varchar(500) COMMENT ''瀹℃牳鎰忚锛堥�氳繃鏃剁殑鎰忚 / 涓嶉�氳繃鏃剁殑鍘熷洜锛�'''),
+('erp_purchase_order', 'reviewer_id', 'bigint COMMENT ''瀹℃牳浜虹紪鍙凤紙瀹為檯鎵ц瀹℃牳鐨勭敤鎴凤級'''),
+('erp_purchase_order', 'reviewer_name', 'varchar(64) COMMENT ''瀹℃牳浜哄鍚嶏紙鍐椾綑璁板綍锛岄伩鍏嶅垪琛ㄦ煡璇㈠啀鏌ョ敤鎴凤級'''),
+('erp_purchase_order', 'review_time', 'datetime COMMENT ''瀹℃牳鏃堕棿'''),
+('erp_purchase_order', 'review_remark', 'varchar(500) COMMENT ''瀹℃牳鎰忚锛堥�氳繃鏃剁殑鎰忚 / 涓嶉�氳繃鏃剁殑鍘熷洜锛�''');
+
+DROP PROCEDURE IF EXISTS xgdl_add_po_review_cols;
+DELIMITER $$
+CREATE PROCEDURE xgdl_add_po_review_cols()
+BEGIN
+ DECLARE v_done INT DEFAULT 0;
+ DECLARE v_tbl VARCHAR(64);
+ DECLARE v_col VARCHAR(64);
+ DECLARE v_def VARCHAR(255);
+ DECLARE cur CURSOR FOR SELECT tbl, col, coldef FROM _tmp_xgdl_po_review_cols;
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
+
+ OPEN cur;
+ read_loop: LOOP
+ FETCH cur INTO v_tbl, v_col, v_def;
+ IF v_done = 1 THEN
+ LEAVE read_loop;
+ END IF;
+ IF EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = DATABASE() AND table_name = v_tbl)
+ AND NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_schema = DATABASE() AND table_name = v_tbl AND column_name = v_col)
+ THEN
+ SET @ddl = CONCAT('ALTER TABLE `', v_tbl, '` ADD COLUMN `', v_col, '` ', v_def);
+ PREPARE stmt FROM @ddl;
+ EXECUTE stmt;
+ DEALLOCATE PREPARE stmt;
+ END IF;
+ END LOOP;
+ CLOSE cur;
+END$$
+DELIMITER ;
+
+CALL xgdl_add_po_review_cols();
+DROP PROCEDURE IF EXISTS xgdl_add_po_review_cols;
+DROP TABLE IF EXISTS _tmp_xgdl_po_review_cols;
+
+-- 4.5 閲囪喘璁″垝 / 閲囪喘璁㈠崟 瀹℃牳鎸夐挳鏉冮檺锛堥敋鐐瑰悓绗竴閮ㄥ垎锛氬悇鑷祫婧愮殑 :update 鎸夐挳锛�
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM (SELECT '閲囪喘璁″垝瀹℃牳' AS name, 'erp:purchase-plan:audit' AS perm) t
+JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'erp:purchase-plan:update') a ON 1 = 1
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'erp:purchase-plan:audit') m);
+
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM (SELECT '閲囪喘璁㈠崟瀹℃牳' AS name, 'erp:purchase-order:audit' AS perm) t
+JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'erp:purchase-order:update') a ON 1 = 1
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'erp:purchase-order:audit') m);
+
+-- 4.6 琛ユ紡锛氫唬鐮侀噷宸蹭娇鐢ㄣ�佷絾绗簩閮ㄥ垎鏈垱寤虹殑 3 涓鏍告寜閽潈闄�
+-- 瀵硅处鏂瑰紡锛歡rep -rhoE "'[a-z]+:[a-z0-9-]+:audit'" --include=*.java
+-- 缂哄け浼氬鑷村搴旈〉闈㈢殑瀹℃牳鎸夐挳瀵逛换浣曚汉閮戒笉鏄剧ず锛堝惈 admin锛夈��
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM (SELECT '鏁呴殰璧斾粯瀹℃牳' AS name, 'aftersales:compensation:audit' AS perm) t
+JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'aftersales:compensation:update') a ON 1 = 1
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'aftersales:compensation:audit') m);
+
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM (SELECT '缁╂晥鑰冩牳瀹℃牳' AS name, 'hrm:performance-appraise:audit' AS perm) t
+JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'hrm:performance-appraise:update') a ON 1 = 1
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'hrm:performance-appraise:audit') m);
+
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT t.name, t.perm, 3, 99, a.parent_id, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM (SELECT '浜у搧椤圭洰瀹℃牳' AS name, 'mes:pd-product:audit' AS perm) t
+JOIN (SELECT MIN(x.parent_id) AS parent_id FROM system_menu x WHERE x.deleted = 0 AND x.permission = 'mes:pd-product:update') a ON 1 = 1
+WHERE a.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_menu WHERE deleted = 0 AND permission = 'mes:pd-product:audit') m);
+
+-- 4.7 瑙掕壊鎺堟潈
+-- 閲嶈锛氭湰妗嗘灦 SecurityFrameworkServiceImpl 娌℃湁銆岃秴绾х鐞嗗憳缁曡繃鏉冮檺鏍¢獙銆嶇殑鐭矾閫昏緫锛�
+-- 鏉冮檺涓�寰嬭蛋 system_role_menu銆備笉鎺堟潈鐨勮瘽杩� admin 閮界湅涓嶅埌瀹℃壒閰嶇疆鑿滃崟鍜屽鏍告寜閽��
+-- 寮�鍙戝簱瀹炴祴锛氬叏閮� :audit 鍧囧凡鎺堜簣 super_admin(1) 涓� common(2)锛�
+-- 浠� mes:pd-product:audit 涓� mes:pd-tech-confirm:audit 婕忔巿 super_admin 鈥斺��
+-- 涓嬮潰杩欐潯璇彞浼氶『甯︽妸杩欎袱涓己鍙hˉ榻愩��
+-- 鍏朵綑瑙掕壊璇峰湪銆岃鑹茬鐞嗐�嶉〉闈㈡寜闇�鍕鹃�夈��
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
+SELECT r.id, m.id, '1', NOW(), '1', NOW(), b'0'
+FROM system_role r
+CROSS JOIN system_menu m
+WHERE r.code IN ('super_admin', 'common')
+ AND r.deleted = 0
+ AND m.deleted = 0
+ AND ( m.component = 'system/approvalConfig/index'
+ OR m.permission IN ('system:approval-config:query', 'system:approval-config:update')
+ OR m.permission LIKE '%:audit' )
+ AND NOT EXISTS (
+ SELECT 1 FROM system_role_menu rm
+ WHERE rm.role_id = r.id AND rm.menu_id = m.id AND rm.deleted = 0
+ );
+
-- 瀹屾垚銆傚彲鎵ц浠ヤ笅璇彞鏍¢獙锛�
-- SELECT table_name, column_name FROM information_schema.columns
-- WHERE table_schema = DATABASE() AND column_name = 'reviewer_id';
-- SELECT permission FROM system_menu WHERE deleted = 0 AND permission LIKE '%:audit';
+-- SELECT COUNT(*) FROM system_approval_config WHERE deleted = 0; -- 鏈熸湜 26
+-- SELECT id, name, permission, parent_id FROM system_menu -- 蹇呴』鍥炶涓夐」锛屽嬁鍙湅琛屾暟
+-- WHERE deleted = 0 AND (component = 'system/approvalConfig/index'
+-- OR permission LIKE 'system:approval-config:%'
+-- OR permission IN ('erp:purchase-plan:audit','erp:purchase-order:audit'));
+-- SELECT r.code, COUNT(*) FROM system_role_menu rm JOIN system_role r ON r.id = rm.role_id
+-- JOIN system_menu m ON m.id = rm.menu_id
+-- WHERE rm.deleted = 0 AND (m.component = 'system/approvalConfig/index'
+-- OR m.permission LIKE 'system:approval-config:%'
+-- OR m.permission IN ('erp:purchase-plan:audit','erp:purchase-order:audit'))
+-- GROUP BY r.code; -- 鏈熸湜姣忚鑹� 5 鏉�
--
Gitblit v1.9.3