|
|
@@ -15,12 +15,12 @@ const CLASS_LABELS = {
|
|
|
GradeCate: '多级字典分类', Grade: '多级字典选项',
|
|
|
Currency: '货币管理', SysHoliday: '节假日管理',
|
|
|
Product: '商品', StoreProduct: '门店商品', StoreApplication: '门店申请', ShopFareTlp: '运费模板', ShopMoneyRegular: '金额规则', PayPlat: '支付平台',
|
|
|
- GuestBar: '互动社区', Guestbook: '留言', Guestcate: '留言分类', Feedback: '反馈', Baike: '百科', Search: '后台快捷入口', AdZone: '广告位管理', AdInfo: '广告内容管理', FontPicShape: '字体图形素材', FontPicShapeType: '字体图形分类', Agency: '代理机构', DeliveryCenter: '交付中心'
|
|
|
+ GuestBar: '互动社区', Guestbook: '留言', Guestcate: '留言分类', Feedback: '反馈', Baike: '百科', Search: '后台快捷入口', AdZone: '广告位管理', AdInfo: '广告内容管理', FontPicShape: '字体图形素材', FontPicShapeType: '字体图形分类', DesignAsk: '问卷调查', DesignQuestion: '问卷题目', DesignAnswer: '问卷答卷', Agency: '代理机构', DeliveryCenter: '交付中心'
|
|
|
};
|
|
|
const ALLOWED_CLASSES = new Set([
|
|
|
'PageTemplate','PaperQuestions','PayPlat','Permission','PlatComp','App','DesignAnswer','DesignAsk','Attachment','DesignPage','DesignQuestion','DesignRes','Feedback','DesignScence','StudentAchieve','ContentArticle','DesignSiteInfo','Profile','AdInfo','AdZone','ARoleAuth','Baike','ExamSysQuestions','ContactInfo','VocabularyWord','AssessmentProfile','Agency','MemoryPracticeRecord','DeliveryCenter','CourseBinding','PracticeRecord','CourseAppointment','Account','SurveyItem','SurveyLog','LessonRecord','DailyStudyRecord','CommonModel','ContentPublish','Company','_Role','_User','CRMSAttr','Currency','Datadic','Datadiccategory','DesignTlp','DocModel','DocPermission','ExamClass','ExamSysPapers','ExamType','ExamPoint','ExTeacher','FontPicShape','FontPicShapeType','Grade','GradeCate','Group','GroupModel','GuestBar','Guestbook','Guestcate','MailTemp','Manager','MisProcedure','MisProLevel','MisSign','MisType','PageStyle','Model','ModelField','Node','NodeAuth','NodeModelTemplate','Product','PlatUserRole','Pub','PubTw','PubWTHD','PubZXDC','PublishNode','QuestionsKnowledge','Role','StoreProduct','SafeMobile','Search','SenTask','ServiceSeat','ShopFareTlp','ShopMoneyRegular','Special','StoreApplication','StoreStyle','SysCSSManage','SysHoliday','SysLog','Temp','ThirdPlatInfo','UserCredit','UserDummyPoint','UserFriendGroup','UserLevel','UserSIcon','UserUserPoint','UserExpDomP','UserExpHis'
|
|
|
]);
|
|
|
-const READ_ONLY_CLASSES = new Set(['_Role','Permission','PayPlat','ThirdPlatInfo','GuestBar']);
|
|
|
+const READ_ONLY_CLASSES = new Set(['_Role','Permission','PayPlat','ThirdPlatInfo','GuestBar','DesignAnswer']);
|
|
|
const SYSTEM_FIELDS = new Set(['objectId','createdAt','updatedAt','ACL','company','password','authData','sessionToken','legacyPasswordHash','legacyPasswordHashType','appPassword','newapiToken','fmodeApiToken','useMasterKey','adminPassword','randNumber','adminRoleKey','isAdmin','roles','role','isDisabled','legacyUserId','legacyGroupId','legacyUserData','groupId']);
|
|
|
const HIDDEN_FIELDS = new Set(['password','authData','sessionToken','legacyPasswordHash','legacyPasswordHashType','appPassword','newapiToken','fmodeApiToken','useMasterKey','adminPassword','randNumber','apiKey','appSecret','secret','token']);
|
|
|
const GENERIC_WRITE_TYPES = new Set(['String','Number','Boolean','Date','Pointer','Object','Array']);
|
|
|
@@ -47,7 +47,10 @@ const CLASS_SYSTEM_FIELDS = {
|
|
|
AdZone: new Set(['id','cdate','cadmin','image','sourceKey']),
|
|
|
AdInfo: new Set(['id','cdate','zoneId','ztype','extend1','extend2','extend3','sourceKey']),
|
|
|
FontPicShape: new Set(['id','shape','typeId','userId','userName','sourceKey','createTime','updateTime']),
|
|
|
- FontPicShapeType: new Set(['id','sourceKey','createTime','updateTime'])
|
|
|
+ FontPicShapeType: new Set(['id','sourceKey','createTime','updateTime']),
|
|
|
+ DesignAsk: new Set(['id','cdate','cuser','adminId','sourceKey']),
|
|
|
+ DesignQuestion: new Set(['id','askId','cdate','cuser','orderId','sourceKey']),
|
|
|
+ DesignAnswer: new Set(['id','askId','ip','cdate','userId','sourceKey'])
|
|
|
};
|
|
|
|
|
|
function inputOf(request) {
|
|
|
@@ -1091,6 +1094,46 @@ async function handler(request, response) {
|
|
|
if (operation === 'legacyFontPicBlocker') {
|
|
|
const action = String(input.action || ''); const blockers = { Index: '目标 Schema 缺少旧 FontPic 成品字体图主表与历史数据', FontPicInfo: '目标 Schema 缺少旧 FontPic 成品字体图详情数据', FontPicInfo_Submit: '目标 Schema 缺少旧 FontPic 成品字体图持久化模型', FontPic_API: '目标 Schema 缺少旧 FontPic 成品字体图可删除记录', Draft: '目标 Schema 缺少旧 FontPicDraft 草稿表与历史数据', DraftInfo: '目标 Schema 缺少旧 FontPicDraft 草稿详情数据', DraftInfo_Submit: '目标 Schema 缺少旧 FontPicDraft 草稿持久化模型', Draft_API: '目标 Schema 缺少旧 FontPicDraft 可删除记录' }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧字体图动作'); fail(501, 'migration_blocked: ' + blockers[action]);
|
|
|
}
|
|
|
+ if (operation === 'surveys') {
|
|
|
+ const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20)); const search = String(input.search || '').trim().toLowerCase(); const status = Number(input.status == null ? -100 : input.status); if (search.length > 100 || ![-100,0,99].includes(status)) fail(400, '问卷筛选条件无效');
|
|
|
+ const fields = await schemaFor('DesignAsk'); const query = new Parse.Query('DesignAsk'); applyTenant(query, fields, context, input.companyId); query.limit(1000); let results = await query.find({ useMasterKey: true }); results = results.filter((entry) => (status === -100 || Number(entry.get('zstatus')) === status) && (!search || [entry.get('title'),entry.get('category'),entry.get('remind')].some((value) => String(value || '').toLowerCase().includes(search)))); results.sort((left, right) => Number(left.get('id') || 0) - Number(right.get('id') || 0)); const total = results.length; results = results.slice((page - 1) * pageSize, page * pageSize); return response.json({ success: true, data: { className: 'DesignAsk', page, pageSize, total, results: results.map(serializeObject) } });
|
|
|
+ }
|
|
|
+ if (operation === 'surveyQuestions') {
|
|
|
+ const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20)); const askId = Number(input.askId || 0); const search = String(input.search || '').trim().toLowerCase(); if (!Number.isInteger(askId) || askId < 1 || search.length > 100) fail(400, '问卷题目筛选条件无效');
|
|
|
+ const askFields = await schemaFor('DesignAsk'); const askQuery = new Parse.Query('DesignAsk'); applyTenant(askQuery, askFields, context, input.companyId); askQuery.limit(1000); const asks = await askQuery.find({ useMasterKey: true }); if (!asks.some((entry) => Number(entry.get('id')) === askId)) fail(404, '问卷不存在或不属于当前帐套');
|
|
|
+ const fields = await schemaFor('DesignQuestion'); const query = new Parse.Query('DesignQuestion'); applyTenant(query, fields, context, input.companyId); query.limit(1000); let results = await query.find({ useMasterKey: true }); results = results.filter((entry) => Number(entry.get('askId')) === askId && (!search || [entry.get('qtitle'),entry.get('qcontent'),entry.get('qoption')].some((value) => String(value || '').toLowerCase().includes(search)))); results.sort((left, right) => Number(left.get('orderId') || 0) - Number(right.get('orderId') || 0) || Number(left.get('id') || 0) - Number(right.get('id') || 0)); const total = results.length; results = results.slice((page - 1) * pageSize, page * pageSize); return response.json({ success: true, data: { className: 'DesignQuestion', askId, page, pageSize, total, results: results.map(serializeObject) } });
|
|
|
+ }
|
|
|
+ if (operation === 'surveyResults') {
|
|
|
+ const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20)); const askId = Number(input.askId || 0); if (!Number.isInteger(askId) || askId < 1) fail(400, '问卷答卷筛选条件无效');
|
|
|
+ const askFields = await schemaFor('DesignAsk'); const askQuery = new Parse.Query('DesignAsk'); applyTenant(askQuery, askFields, context, input.companyId); askQuery.limit(1000); const asks = await askQuery.find({ useMasterKey: true }); if (!asks.some((entry) => Number(entry.get('id')) === askId)) fail(404, '问卷不存在或不属于当前帐套');
|
|
|
+ const fields = await schemaFor('DesignAnswer'); const query = new Parse.Query('DesignAnswer'); applyTenant(query, fields, context, input.companyId); query.limit(1000); let results = await query.find({ useMasterKey: true }); results = results.filter((entry) => Number(entry.get('askId')) === askId); results.sort((left, right) => Number(right.get('id') || 0) - Number(left.get('id') || 0)); const total = results.length; results = results.slice((page - 1) * pageSize, page * pageSize); return response.json({ success: true, data: { className: 'DesignAnswer', askId, page, pageSize, total, results: results.map(serializeObject) } });
|
|
|
+ }
|
|
|
+ if (operation === 'saveSurvey') {
|
|
|
+ const objectId = String(input.objectId || ''); const payload = input.fields && typeof input.fields === 'object' ? input.fields : {}; if (!objectId) fail(501, 'migration_blocked: DesignAsk.id 与 Parse 保留 id 冲突,sourceKey 创建探测不能还原旧问卷编号,不能安全新增问卷');
|
|
|
+ const title = String(payload.title || '').trim(); const category = String(payload.category || '').trim(); const remind = String(payload.remind || '').trim(); const preViewImg = String(payload.preViewImg || '').trim(); const startRaw = payload.startDate && typeof payload.startDate === 'object' ? payload.startDate.iso : payload.startDate; const endRaw = payload.endDate && typeof payload.endDate === 'object' ? payload.endDate.iso : payload.endDate; const startDate = new Date(String(startRaw || '')); const endDate = new Date(String(endRaw || '')); const ztype = Number(payload.ztype || 0); const ipinterval = Number(payload.ipinterval || 0); const zstatus = Number(payload.zstatus == null ? 0 : payload.zstatus); const flags = { isIplimit: Number(payload.isIplimit || 0), isNeedLogin: Number(payload.isNeedLogin || 0), isShowResult: Number(payload.isShowResult || 0), isEnableVcode: Number(payload.isEnableVcode || 0) };
|
|
|
+ if (!title || title.length > 200 || category.length > 100 || remind.length > 10000 || preViewImg.length > 2000 || Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()) || endDate < startDate || endDate.getTime() < Date.now() || !Number.isInteger(ztype) || ztype < 0 || !Number.isFinite(ipinterval) || ipinterval < 0 || ![0,99].includes(zstatus) || Object.values(flags).some((value) => ![0,1].includes(value))) fail(400, '问卷标题、时间、状态或访问设置无效');
|
|
|
+ const fields = await schemaFor('DesignAsk'); const query = new Parse.Query('DesignAsk'); applyTenant(query, fields, context, input.companyId); const target = await query.get(objectId, { useMasterKey: true }); target.set('title', title); target.set('category', category); target.set('remind', remind); target.set('preViewImg', preViewImg); target.set('startDate', startDate); target.set('endDate', endDate); target.set('ztype', ztype); target.set('ipinterval', ipinterval); target.set('zstatus', zstatus); for (const [name,value] of Object.entries(flags)) target.set(name, value); await target.save(null, { useMasterKey: true }); await target.fetch({ useMasterKey: true }); await audit({ ...context, company: target.get('company') || context.company }, 'update-survey', 'DesignAsk', target.id); return response.json({ success: true, data: serializeObject(target) });
|
|
|
+ }
|
|
|
+ if (operation === 'saveSurveyQuestion') {
|
|
|
+ const objectId = String(input.objectId || ''); const payload = input.fields && typeof input.fields === 'object' ? input.fields : {}; if (!objectId) fail(501, 'migration_blocked: DesignQuestion.id 与 Parse 保留 id 冲突,sourceKey 创建探测不能还原旧题目编号,不能安全新增问卷题目');
|
|
|
+ const qtitle = String(payload.qtitle || '').trim(); const qcontent = String(payload.qcontent || '').trim(); const qtype = String(payload.qtype || '').trim().toLowerCase(); const qoption = String(payload.qoption || '').trim(); const qflag = String(payload.qflag || '').trim(); const required = payload.required === true || payload.required === 1 || payload.required === '1' || payload.required === 'true' ? 1 : 0; if (!qtitle || qtitle.length > 1000 || qcontent.length > 20000 || !/^[a-z][a-z0-9_-]{0,30}$/.test(qtype) || qoption.length > 20000 || qflag.length > 5000) fail(400, '问卷题目内容或类型无效');
|
|
|
+ if (['radio','checkbox'].includes(qtype)) { let options; try { options = JSON.parse(qoption); } catch { fail(400, '单选/多选题选项必须是有效 JSON 数组'); } if (!Array.isArray(options) || !options.length || options.length > 100 || options.some((item) => !item || typeof item !== 'object' || !String(item.text || '').trim() || String(item.text || '').length > 500 || String(item.value || '').length > 500)) fail(400, '单选/多选题选项格式无效'); }
|
|
|
+ if (qflag) { let flag; try { flag = JSON.parse(qflag); } catch { fail(400, '题目扩展设置必须是有效 JSON'); } if (!flag || typeof flag !== 'object' || Array.isArray(flag)) fail(400, '题目扩展设置必须是 JSON 对象'); }
|
|
|
+ const fields = await schemaFor('DesignQuestion'); const query = new Parse.Query('DesignQuestion'); applyTenant(query, fields, context, input.companyId); const target = await query.get(objectId, { useMasterKey: true }); const askId = Number(target.get('askId') || 0); const askFields = await schemaFor('DesignAsk'); const askQuery = new Parse.Query('DesignAsk'); applyTenant(askQuery, askFields, context, input.companyId); askQuery.limit(1000); const asks = await askQuery.find({ useMasterKey: true }); if (!asks.some((entry) => Number(entry.get('id')) === askId)) fail(409, '题目所属问卷已不存在,不能继续编辑');
|
|
|
+ target.set('qtitle', qtitle); target.set('qcontent', qcontent); target.set('qtype', qtype); target.set('qoption', qoption); target.set('qflag', qflag || '{}'); target.set('required', required); await target.save(null, { useMasterKey: true }); await target.fetch({ useMasterKey: true }); await audit({ ...context, company: target.get('company') || context.company }, 'update-survey-question', 'DesignQuestion', target.id); return response.json({ success: true, data: serializeObject(target) });
|
|
|
+ }
|
|
|
+ if (operation === 'surveyBatch') {
|
|
|
+ const className = String(input.className || ''); const action = String(input.action || ''); const allowed = className === 'DesignAsk' ? ['start','stop','delete'] : className === 'DesignQuestion' ? ['delete','sort'] : className === 'DesignAnswer' ? ['delete'] : []; if (!allowed.includes(action)) fail(400, '不支持的问卷批量操作'); const requestedIds = Array.isArray(input.objectIds) ? input.objectIds : [input.objectId]; const objectIds = [...new Set(requestedIds.map((value) => String(value || '').trim()).filter(Boolean))]; if (!objectIds.length || objectIds.length > 100) fail(400, '每次请选择 1 至 100 条问卷记录');
|
|
|
+ const fields = await schemaFor(className); const query = new Parse.Query(className); applyTenant(query, fields, context, input.companyId); query.containedIn('objectId', objectIds); query.limit(objectIds.length); const targets = await query.find({ useMasterKey: true }); if (targets.length !== objectIds.length) fail(404, '部分问卷记录不存在或不属于当前帐套');
|
|
|
+ if (action === 'delete') await Parse.Object.destroyAll(targets, { useMasterKey: true }); else if (className === 'DesignAsk') { for (const target of targets) target.set('zstatus', action === 'start' ? 99 : 0); await Parse.Object.saveAll(targets, { useMasterKey: true }); } else { const orders = Array.isArray(input.orders) ? input.orders : []; const orderMap = new Map(orders.map((item) => [String(item && item.objectId || ''), Number(item && item.orderId)])); if (orderMap.size !== objectIds.length || [...orderMap.values()].some((value) => !Number.isInteger(value) || value < 1 || value > 1000000)) fail(400, '题目排序数据无效'); for (const target of targets) target.set('orderId', orderMap.get(target.id)); await Parse.Object.saveAll(targets, { useMasterKey: true }); }
|
|
|
+ for (const target of targets) await audit({ ...context, company: target.get('company') || context.company }, 'survey-' + className + '-' + action, className, target.id); return response.json({ success: true, data: { className, action, updated: targets.length, results: action === 'delete' ? [] : targets.map(serializeObject) } });
|
|
|
+ }
|
|
|
+ if (operation === 'surveyResultChart') {
|
|
|
+ const askId = Number(input.askId || 0); if (!Number.isInteger(askId) || askId < 1) fail(400, '缺少有效问卷编号'); const askFields = await schemaFor('DesignAsk'); const askQuery = new Parse.Query('DesignAsk'); applyTenant(askQuery, askFields, context, input.companyId); askQuery.limit(1000); const asks = await askQuery.find({ useMasterKey: true }); const survey = asks.find((entry) => Number(entry.get('id')) === askId); if (!survey) fail(404, '问卷不存在或不属于当前帐套');
|
|
|
+ const questionFields = await schemaFor('DesignQuestion'); const questionQuery = new Parse.Query('DesignQuestion'); applyTenant(questionQuery, questionFields, context, input.companyId); questionQuery.limit(1000); const questions = (await questionQuery.find({ useMasterKey: true })).filter((entry) => Number(entry.get('askId')) === askId); const answerFields = await schemaFor('DesignAnswer'); const answerQuery = new Parse.Query('DesignAnswer'); applyTenant(answerQuery, answerFields, context, input.companyId); answerQuery.limit(1000); const answers = (await answerQuery.find({ useMasterKey: true })).filter((entry) => Number(entry.get('askId')) === askId); const counts = new Map(); let invalidAnswers = 0;
|
|
|
+ for (const entry of answers) { let details; try { details = JSON.parse(String(entry.get('answer') || '')); } catch { invalidAnswers++; continue; } if (!Array.isArray(details)) { invalidAnswers++; continue; } for (const detail of details) { const qid = String(detail && detail.qid || ''); let values = detail && detail.answer; if (!Array.isArray(values)) values = values == null || values === '' ? [] : [values]; for (const value of values) { const key = qid + '\u0000' + String(value); counts.set(key, (counts.get(key) || 0) + 1); } } }
|
|
|
+ const chart = questions.sort((left, right) => Number(left.get('orderId') || 0) - Number(right.get('orderId') || 0) || Number(left.get('id') || 0) - Number(right.get('id') || 0)).map((question) => { let options = []; try { const parsed = JSON.parse(String(question.get('qoption') || '[]')); if (Array.isArray(parsed)) options = parsed; } catch {} const qid = String(question.get('id') || ''); const known = new Set(options.map((option) => String(option && option.value || ''))); let otherCount = 0; for (const [key,value] of counts) if (key.startsWith(qid + '\u0000') && !known.has(key.slice(qid.length + 1))) otherCount += value; return { id: qid, title: String(question.get('qtitle') || ''), type: String(question.get('qtype') || ''), options: options.map((option) => ({ text: String(option && option.text || ''), value: String(option && option.value || ''), count: counts.get(qid + '\u0000' + String(option && option.value || '')) || 0 })), otherCount }; }); return response.json({ success: true, data: { survey: serializeObject(survey), responseCount: answers.length, invalidAnswers, questions: chart } });
|
|
|
+ }
|
|
|
if (operation === 'saveExamClass') {
|
|
|
const objectId = String(input.objectId || '');
|
|
|
const payload = input.fields && typeof input.fields === 'object' ? input.fields : {};
|
|
|
@@ -1354,7 +1397,7 @@ async function handler(request, response) {
|
|
|
const classWritable = !READ_ONLY_CLASSES.has(className);
|
|
|
if (operation === 'schema') {
|
|
|
const fieldList = Object.entries(fields).filter(([name]) => !HIDDEN_FIELDS.has(name) && !/(?:password|secret|sessiontoken|masterkey|privatekey)/i.test(name)).map(([name, field]) => ({ name, type: field.type, targetClass: field.targetClass, required: field.required === true, writable: classWritable && !isSystemField(className, name) && GENERIC_WRITE_TYPES.has(field.type) }));
|
|
|
- return response.json({ success: true, data: { className, label: CLASS_LABELS[className] || className, fields: fieldList, writable: classWritable, creatable: classWritable && !['CommonModel','Model','ModelField','Guestbook','Currency','SysHoliday','Search','AdZone','AdInfo','FontPicShape','FontPicShapeType'].includes(className), supportsSoftDelete: Boolean(fields.isDeleted) } });
|
|
|
+ return response.json({ success: true, data: { className, label: CLASS_LABELS[className] || className, fields: fieldList, writable: classWritable, creatable: classWritable && !['CommonModel','Model','ModelField','Guestbook','Currency','SysHoliday','Search','AdZone','AdInfo','FontPicShape','FontPicShapeType','DesignAsk','DesignQuestion'].includes(className), supportsSoftDelete: Boolean(fields.isDeleted) } });
|
|
|
}
|
|
|
if (operation === 'list') {
|
|
|
const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20));
|
|
|
@@ -1391,6 +1434,7 @@ async function handler(request, response) {
|
|
|
if (className === 'Search') fail(400, '快捷入口必须走专用保存流程');
|
|
|
if (className === 'AdZone' || className === 'AdInfo') fail(400, '广告位与广告内容必须走专用保存流程');
|
|
|
if (className === 'FontPicShape' || className === 'FontPicShapeType') fail(400, '字体图形素材与分类必须走专用保存流程');
|
|
|
+ if (className === 'DesignAsk' || className === 'DesignQuestion' || className === 'DesignAnswer') fail(400, '问卷、题目与答卷必须走专用流程');
|
|
|
if (className === 'CommonModel' && !objectId) fail(400, '内容新增必须同时写入模型附表,不能走通用保存');
|
|
|
let object;
|
|
|
if (objectId) { const query = new Parse.Query(className); applyTenant(query, fields, context, input.companyId); object = await query.get(objectId, { useMasterKey: true }); } else object = new Parse.Object(className);
|
|
|
@@ -1405,6 +1449,7 @@ async function handler(request, response) {
|
|
|
if (className === 'Model' || className === 'ModelField') fail(501, 'migration_blocked: 删除模型或字段需要同步删除 PostgreSQL 物理表/列并验证历史数据,托管云函数中未开放此 DDL 流程');
|
|
|
if (className === 'AdZone' || className === 'AdInfo') fail(400, '广告位与广告内容必须走专用批量删除流程');
|
|
|
if (className === 'FontPicShape' || className === 'FontPicShapeType') fail(400, '字体图形素材与分类必须走专用批量删除流程');
|
|
|
+ if (className === 'DesignAsk' || className === 'DesignQuestion' || className === 'DesignAnswer') fail(400, '问卷、题目与答卷必须走专用批量流程');
|
|
|
if (className === 'Guestcate') { const cateid = Number(object.get('cateid')) || 0; const companyId = pointerId(object.get('company')); const refs = await Psql.one('SELECT (SELECT COUNT(*)::int FROM "Guestcate" WHERE "company"=$1 AND "parentId"=$2) AS children,(SELECT COUNT(*)::int FROM "Guestbook" WHERE "company"=$1 AND "cateid"=$2) AS messages,(SELECT COUNT(*)::int FROM "GuestBar" WHERE "company"=$1 AND "cateId"=$2) AS posts', [companyId, cateid]); if (Number(refs.children) || Number(refs.messages) || Number(refs.posts)) fail(409, '分类仍被下级分类、留言或帖子引用,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-guest-category', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|
|
|
if (className === 'ExamClass') { const classId = Number(object.get('cId')) || 0; const companyId = pointerId(object.get('company')); const refs = await Psql.one('SELECT (SELECT COUNT(*)::int FROM "ExamClass" WHERE "company"=$1 AND "cClassid"=$2) AS children,(SELECT COUNT(*)::int FROM "ExamSysQuestions" WHERE "company"=$1 AND "pClass"=$2) AS questions', [companyId, classId]); if (Number(refs.children) || Number(refs.questions)) fail(409, '试题分类仍被下级分类或试题引用,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-exam-class', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|
|
|
if (className === 'ExamPoint') { const pointId = Number(object.get('id')) || 0; const companyId = pointerId(object.get('company')); const children = await Psql.one('SELECT COUNT(*)::int AS count FROM "ExamPoint" WHERE "company"=$1 AND "tid"=$2', [companyId, pointId]); if (Number(children.count)) fail(409, '考点仍有下级考点,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-exam-point', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|