|
@@ -32,7 +32,8 @@ const CLASS_SYSTEM_FIELDS = {
|
|
|
GuestBar: new Set(['id','cateId','pid','replyId','replyUserId','cuser','cuname','cdate','status','sourceKey','orderFlag','postFlag']),
|
|
GuestBar: new Set(['id','cateId','pid','replyId','replyUserId','cuser','cuname','cdate','status','sourceKey','orderFlag','postFlag']),
|
|
|
ExamClass: new Set(['cId','cClassid','cOrderBy','cClassType','sourceKey']),
|
|
ExamClass: new Set(['cId','cClassid','cOrderBy','cClassType','sourceKey']),
|
|
|
ExamPoint: new Set(['id','tid','orderBy','addUser','addTime','sourceKey']),
|
|
ExamPoint: new Set(['id','tid','orderBy','addUser','addTime','sourceKey']),
|
|
|
- QuestionsKnowledge: new Set(['kId','kClassId','kOrderBy','pid','cuser','cdate','sourceKey'])
|
|
|
|
|
|
|
+ QuestionsKnowledge: new Set(['kId','kClassId','kOrderBy','pid','cuser','cdate','sourceKey']),
|
|
|
|
|
+ ExTeacher: new Set(['id','tclsss','addUser','creatTime','sourceKey'])
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
function inputOf(request) {
|
|
function inputOf(request) {
|
|
@@ -845,6 +846,32 @@ async function handler(request, response) {
|
|
|
const results = await query.find({ useMasterKey: true });
|
|
const results = await query.find({ useMasterKey: true });
|
|
|
return response.json({ success: true, data: { classId, parentId, results: results.map(serializeObject) } });
|
|
return response.json({ success: true, data: { classId, parentId, results: results.map(serializeObject) } });
|
|
|
}
|
|
}
|
|
|
|
|
+ if (operation === 'saveExamTeacher') {
|
|
|
|
|
+ const objectId = String(input.objectId || ''); const payload = input.fields && typeof input.fields === 'object' ? input.fields : {};
|
|
|
|
|
+ const teacherName = String(payload.tname || '').trim(); const classId = Number(input.classId == null ? payload.tclsss || 0 : input.classId);
|
|
|
|
|
+ if (!teacherName || teacherName.length > 50) fail(400, '教师姓名长度应为 1 至 50 位');
|
|
|
|
|
+ if (!Number.isInteger(classId) || classId < 0) fail(400, '任教试题分类编号无效');
|
|
|
|
|
+ let companyId = pointerId(context.company); if (context.isSuperAdmin && input.companyId) companyId = String(input.companyId); if (!companyId) fail(400, '教师必须指定帐套');
|
|
|
|
|
+ const company = await new Parse.Query('Company').get(companyId, { useMasterKey: true });
|
|
|
|
|
+ if (classId > 0) { const subject = await Psql.oneOrNone('SELECT "cId" FROM "ExamClass" WHERE "company"=$1 AND "cId"=$2 LIMIT 1', [companyId, classId]); if (!subject) fail(404, '任教试题分类不存在或不属于当前帐套'); }
|
|
|
|
|
+ const fields = await schemaFor('ExTeacher'); let target;
|
|
|
|
|
+ if (objectId) { const query = new Parse.Query('ExTeacher'); applyTenant(query, fields, context, input.companyId); target = await query.get(objectId, { useMasterKey: true }); companyId = pointerId(target.get('company')) || companyId; }
|
|
|
|
|
+ else target = new Parse.Object('ExTeacher');
|
|
|
|
|
+ const limits = { post: 50, teach: 50, fileUpload: 255, remark: 1000 };
|
|
|
|
|
+ for (const [name, limit] of Object.entries(limits)) { const value = String(payload[name] || '').trim(); if (value.length > limit) fail(400, name + ' 超出最大长度 ' + limit); target.set(name, value); }
|
|
|
|
|
+ target.set('tname', teacherName); target.set('tclsss', classId); target.set('company', company); target.set('addUser', Number(context.current.get('legacyUserId')) || 0);
|
|
|
|
|
+ if (!objectId) { target.set('sourceKey', 'cloud:admin-exam-teacher:' + companyId + ':' + Date.now() + ':' + Math.random().toString(36).slice(2,10)); target.set('creatTime', new Date()); }
|
|
|
|
|
+ try { await target.save(null, { useMasterKey: true }); }
|
|
|
|
|
+ catch (error) { fail(422, '教师初始写入失败: ' + String(error && error.message || error)); }
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!objectId) {
|
|
|
|
|
+ const rows = await Psql.query('WITH lock_row AS MATERIALIZED (SELECT pg_advisory_xact_lock(hashtext(\'xiaoshu-exam-teacher-id:\'||$1))), next_id AS MATERIALIZED (SELECT COALESCE(MAX("id"),0)+1 AS id FROM "ExTeacher",lock_row WHERE "company"=$1 AND COALESCE("id",0)>0) UPDATE "ExTeacher" SET "id"=next_id.id,"sourceKey"=\'[["ID",\'||next_id.id::text||\']]\',"updatedAt"=NOW() FROM next_id WHERE "objectId"=$2 RETURNING next_id.id', [companyId, target.id]);
|
|
|
|
|
+ if (!(Number(rows[0] && rows[0].id) || 0)) throw new Error('无法分配教师编号');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) { if (!objectId) await target.destroy({ useMasterKey: true }).catch(() => undefined); fail(422, '教师编号写入失败: ' + String(error && error.message || error)); }
|
|
|
|
|
+ await target.fetch({ useMasterKey: true }); await audit({ ...context, company }, objectId ? 'update-exam-teacher' : 'create-exam-teacher', 'ExTeacher', target.id);
|
|
|
|
|
+ return response.json({ success: true, data: serializeObject(target) });
|
|
|
|
|
+ }
|
|
|
if (operation === 'barBatch') {
|
|
if (operation === 'barBatch') {
|
|
|
const action = String(input.action || '');
|
|
const action = String(input.action || '');
|
|
|
const actions = ['audit','unaudit','recycle','recover','move','top-global','top-section','top-undo','elite','elite-undo','bottom','bottom-undo'];
|
|
const actions = ['audit','unaudit','recycle','recover','move','top-global','top-section','top-undo','elite','elite-undo','bottom','bottom-undo'];
|
|
@@ -930,7 +957,7 @@ async function handler(request, response) {
|
|
|
const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20));
|
|
const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20));
|
|
|
let query = new Parse.Query(className); applyTenant(query, fields, context, input.companyId);
|
|
let query = new Parse.Query(className); applyTenant(query, fields, context, input.companyId);
|
|
|
const search = String(input.search || '').trim();
|
|
const search = String(input.search || '').trim();
|
|
|
- if (search) { const searchField = ['name','title','nodeName','specName','modelName','fieldName','fieldAlias','groupName','cClassName','testPoint','kName','username','realName','mobile','sourceKey'].find((name) => fields[name] && fields[name].type === 'String'); if (searchField) query.matches(searchField, escapeRegex(search), 'i'); }
|
|
|
|
|
|
|
+ if (search) { const searchField = ['name','title','nodeName','specName','modelName','fieldName','fieldAlias','groupName','cClassName','testPoint','kName','tname','username','realName','mobile','sourceKey'].find((name) => fields[name] && fields[name].type === 'String'); if (searchField) query.matches(searchField, escapeRegex(search), 'i'); }
|
|
|
const sort = fields[input.sort] ? String(input.sort) : fields.updatedAt ? 'updatedAt' : 'createdAt'; if (input.order === 'asc') query.ascending(sort); else query.descending(sort);
|
|
const sort = fields[input.sort] ? String(input.sort) : fields.updatedAt ? 'updatedAt' : 'createdAt'; if (input.order === 'asc') query.ascending(sort); else query.descending(sort);
|
|
|
const total = await query.count({ useMasterKey: true }); query.skip((page - 1) * pageSize); query.limit(pageSize);
|
|
const total = await query.count({ useMasterKey: true }); query.skip((page - 1) * pageSize); query.limit(pageSize);
|
|
|
const results = (await query.find({ useMasterKey: true })).filter(isVisible);
|
|
const results = (await query.find({ useMasterKey: true })).filter(isVisible);
|
|
@@ -953,6 +980,7 @@ async function handler(request, response) {
|
|
|
if (className === 'ExamClass') fail(400, '试题分类必须走专用保存流程');
|
|
if (className === 'ExamClass') fail(400, '试题分类必须走专用保存流程');
|
|
|
if (className === 'ExamPoint') fail(400, '考点必须走专用保存流程');
|
|
if (className === 'ExamPoint') fail(400, '考点必须走专用保存流程');
|
|
|
if (className === 'QuestionsKnowledge') fail(400, '知识点必须走专用保存流程');
|
|
if (className === 'QuestionsKnowledge') fail(400, '知识点必须走专用保存流程');
|
|
|
|
|
+ if (className === 'ExTeacher') fail(400, '考试教师必须走专用保存流程');
|
|
|
if (className === 'CommonModel' && !objectId) fail(400, '内容新增必须同时写入模型附表,不能走通用保存');
|
|
if (className === 'CommonModel' && !objectId) fail(400, '内容新增必须同时写入模型附表,不能走通用保存');
|
|
|
let object;
|
|
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);
|
|
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);
|