|
|
@@ -243,7 +243,7 @@ const BLOCKED = {
|
|
|
vote_add: '目标 Schema 无可证明的投票记录类', vote_ask: '目标 Schema 无可证明的投票记录类', vote_question: '目标 Schema 无可证明的投票记录类',
|
|
|
content_add: 'content/addon 双表写入关系尚待字段级回归', content_add_zt: 'content/addon 双表写入关系尚待字段级回归', content_update: 'content/addon 双表写入关系尚待字段级回归',
|
|
|
product_del: '尚未建立门店管理员授权关系', product_stock_change: '尚未建立门店管理员授权关系', product_sale_change: '尚未建立门店管理员授权关系',
|
|
|
- unit_record_update: '旧源码在调用前无条件 return;需确认是否废弃或提供新的单元聚合规则', e_order_update_v2: '需确认预约状态机与角色权限', e_order_tongji: '需确认预约统计口径',
|
|
|
+ unit_record_update: '旧源码在调用前无条件 return;需确认是否废弃或提供新的单元聚合规则', e_order_tongji: '需确认预约统计口径',
|
|
|
e_get_21list_tj: '迁移数据未包含复习收入金额字段或可验证的计价规则',
|
|
|
pub_add: '需确认 Pub 业务类型与审核规则', pub_list: '需确认 Pub 业务类型与可见范围', user_dept: '目标 Schema 无可证明的用户部门关系',
|
|
|
user_rnauth_add: '实名认证需新的合规审核与敏感数据存储', user_rnauth_get: '实名认证需新的合规审核与敏感数据存储', user_rnauth_upd: '实名认证需新的合规审核与敏感数据存储',
|
|
|
@@ -344,9 +344,9 @@ async function authorizeBusinessRow(current, row, subjectFields, actorFields = [
|
|
|
}
|
|
|
function normalizeWordRow(source) { const addon = source.__addon || {}; const merged = { ...source, ...addon }; delete merged.__addon; return legacyAliases(merged, 'CommonModel'); }
|
|
|
async function orderDetailRow(inputId, current, authorize = true) {
|
|
|
- const id = String(inputId || ''); if (!id) fail(400, '缺少预约 ID'); const rows = await Psql.query('SELECT c.*, row_to_json(a) AS "__addon", n."nodeName" AS "__courseTitle" FROM "CommonModel" c JOIN "CourseAppointment" a ON a."company" = $1 AND CAST(a."id" AS text) = CAST(c."itemId" AS text) LEFT JOIN "Node" n ON n."company" = $1 AND CAST(n."nodeId" AS text) = CAST(a."kcid" AS text) WHERE c."company" = $1 AND CAST(c."modelId" AS text) = \'54\' AND CAST(c."generalId" AS text) = $2 LIMIT 1', [DEFAULT_COMPANY_ID, id]);
|
|
|
- let source = rows[0]; if (!source && /^[A-Za-z0-9_-]{10,40}$/.test(id)) { const direct = await Psql.query('SELECT a.*, n."nodeName" AS "__courseTitle" FROM "CourseAppointment" a LEFT JOIN "Node" n ON n."company" = $1 AND CAST(n."nodeId" AS text) = CAST(a."kcid" AS text) WHERE a."company" = $1 AND a."objectId" = $2 LIMIT 1', [DEFAULT_COMPANY_ID, id]); if (direct[0]) source = { ...direct[0], generalId: id, GeneralID: id, title: direct[0].title || '课程预约' }; }
|
|
|
- if (!source) return null; const addon = source.__addon || {}; const courseTitle = source.__courseTitle || source.subtitle || ''; delete source.__addon; delete source.__courseTitle; const row = legacyAliases({ ...source, ...addon }, 'CommonModel'); row.kcmc = row.kcmc || courseTitle; row.yykcmc = row.yykcmc || courseTitle; row.HoneyName = row.HoneyName || String(row.Title || '').split('(')[0]; if (authorize) await authorizeBusinessRow(current, row, ['szyh'], ['pl']); return row;
|
|
|
+ const id = String(inputId || ''); if (!id) fail(400, '缺少预约 ID'); const rows = await Psql.query('SELECT c.*, a."objectId" AS "__appointmentObjectId", row_to_json(a) AS "__addon", n."nodeName" AS "__courseTitle" FROM "CommonModel" c JOIN "CourseAppointment" a ON a."company" = $1 AND CAST(a."id" AS text) = CAST(c."itemId" AS text) LEFT JOIN "Node" n ON n."company" = $1 AND CAST(n."nodeId" AS text) = CAST(a."kcid" AS text) WHERE c."company" = $1 AND CAST(c."modelId" AS text) = \'54\' AND CAST(c."generalId" AS text) = $2 LIMIT 1', [DEFAULT_COMPANY_ID, id]);
|
|
|
+ let source = rows[0]; if (!source && /^[A-Za-z0-9_-]{10,40}$/.test(id)) { const direct = await Psql.query('SELECT a.*, n."nodeName" AS "__courseTitle" FROM "CourseAppointment" a LEFT JOIN "Node" n ON n."company" = $1 AND CAST(n."nodeId" AS text) = CAST(a."kcid" AS text) WHERE a."company" = $1 AND a."objectId" = $2 LIMIT 1', [DEFAULT_COMPANY_ID, id]); if (direct[0]) source = { ...direct[0], __appointmentObjectId: direct[0].objectId, generalId: id, GeneralID: id, title: direct[0].title || '课程预约' }; }
|
|
|
+ if (!source) return null; const addon = source.__addon || {}; const appointmentObjectId = source.__appointmentObjectId || addon.objectId; const courseTitle = source.__courseTitle || source.subtitle || ''; delete source.__addon; delete source.__appointmentObjectId; delete source.__courseTitle; const row = legacyAliases({ ...source, ...addon }, 'CommonModel'); row.appointmentObjectId = appointmentObjectId; row.kcmc = row.kcmc || courseTitle; row.yykcmc = row.yykcmc || courseTitle; row.HoneyName = row.HoneyName || String(row.Title || '').split('(')[0]; if (authorize) await authorizeBusinessRow(current, row, ['szyh'], ['pl']); return row;
|
|
|
}
|
|
|
async function recordDetailData(input, current) {
|
|
|
const id = String(input.id || input.gid || ''); if (!id) fail(400, '缺少学习记录 ID'); const rows = await Psql.query('SELECT c.*, row_to_json(d) AS "__addon" FROM "CommonModel" c JOIN "DailyStudyRecord" d ON d."company" = $1 AND CAST(d."id" AS text) = CAST(c."itemId" AS text) WHERE c."company" = $1 AND CAST(c."modelId" AS text) = \'56\' AND CAST(c."generalId" AS text) = $2 LIMIT 1', [DEFAULT_COMPANY_ID, id]);
|
|
|
@@ -380,6 +380,10 @@ async function upsertStudyRecord(input, current) {
|
|
|
if (record) { applyFields(record); common.set('title', title); common.set('inputer', inputer); common.set('hits', number(record.get('learned'))); await Parse.Object.saveAll([record, common], { useMasterKey: true }); return { GeneralID: number(found.generalId), generalId: number(found.generalId), objectId: common.id, recordObjectId: record.id, created: false }; }
|
|
|
created = true; const baseId = Math.floor(100000000000000 + Math.random() * 800000000000000); const recordId = baseId; const generalId = baseId + 1; record = new Parse.Object('DailyStudyRecord'); record.set('company', current.get('company')); record.set('sourceKey', 'cloud:stu_record:' + uid + ':' + orderId); applyFields(record); await record.save(null, { useMasterKey: true }); try { await Psql.none('UPDATE "DailyStudyRecord" SET "id" = $1 WHERE "objectId" = $2', [recordId, record.id]); common = new Parse.Object('CommonModel'); common.set('company', current.get('company')); common.set('sourceKey', 'cloud:stu_record:' + uid + ':' + orderId); common.set('generalId', generalId); common.set('itemId', recordId); common.set('modelId', 56); common.set('nodeId', 291); common.set('tableName', 'ZL_C_ss'); common.set('title', title); common.set('inputer', inputer); common.set('hits', number(record.get('learned'))); common.set('status', 99); await common.save(null, { useMasterKey: true }); } catch (error) { if (common && common.id) await common.destroy({ useMasterKey: true }).catch(() => undefined); await record.destroy({ useMasterKey: true }).catch(() => undefined); throw error; } return { GeneralID: generalId, generalId, objectId: common.id, recordObjectId: record.id, created };
|
|
|
}
|
|
|
+async function updateOrderStatus(input, current) {
|
|
|
+ const content = parseObject(input.content, '预约更新内容'); const orderId = String(content.GeneralID ?? content.generalId ?? input.orderId ?? input.id ?? '').trim(); if (!orderId) fail(400, '缺少预约 ID'); const status = number(input.status, -1); if (![10,11,20,30].includes(status)) fail(400, '不支持的预约状态'); const requestedActor = number(input.uid || ownLegacyId(current)); if (!isAdminUser(current) && requestedActor !== ownLegacyId(current)) fail(403, '无权代替其他用户更新预约'); const order = await orderDetailRow(orderId, current, false); if (!order) fail(404, '预约记录不存在'); const studentId = number(order.szyh); const coachId = number(order.pl); const actorId = ownLegacyId(current); const requiredActor = status === 20 ? studentId : coachId; if (!isAdminUser(current) && (!requiredActor || actorId !== requiredActor)) fail(403, status === 20 ? '仅预约学员可以确认评价' : '仅预约陪练可以更新课程状态'); const previous = number(order.dszt); const allowedPrevious = { 10:[0,10], 11:[10,11], 20:[11,20], 30:[11,20,30] }[status]; if (!allowedPrevious.includes(previous)) fail(409, '预约状态不能从 ' + previous + ' 更新为 ' + status); if (previous === status) return { GeneralID: order.GeneralID ?? order.generalId ?? orderId, previousStatus: previous, status, unchanged: true };
|
|
|
+ const appointment = await new Parse.Query('CourseAppointment').get(String(order.appointmentObjectId), { useMasterKey: true }); appointment.set('dszt', String(status)); const updatedAt = cleanText(content.UpDateTime ?? content.updateTime, 30); if (updatedAt && !/^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2})?)?$/.test(updatedAt)) fail(400, '预约更新时间格式错误'); if (status === 10 && updatedAt && !appointment.get('kssj')) appointment.set('kssj', updatedAt); if (status === 11 && updatedAt) appointment.set('jssj', updatedAt); await appointment.save(null, { useMasterKey: true }); return { GeneralID: order.GeneralID ?? order.generalId ?? orderId, previousStatus: previous, status, unchanged: false, objectId: appointment.id };
|
|
|
+}
|
|
|
function learnedDate(row) { const raw = String(row.dqrq || '').trim(); return /^\d{8}$/.test(raw) ? raw.slice(0,4) + '-' + raw.slice(4,6) + '-' + raw.slice(6,8) : String(row.kywsj || '').slice(0,10); }
|
|
|
async function memoryPage(input, current) {
|
|
|
const uid = await authorizeRequestedUser(current, input.uid); const paging = largePageInput(input); const income = number(input.shouru) === 1; const values = [DEFAULT_COMPANY_ID, String(uid)]; const clauses = ['c."company" = $1', 'CAST(c."modelId" AS text) = \'60\'', 'CAST(m."' + (income ? 'plid' : 'yhid') + '" AS text) = $2']; if (input.status !== undefined && input.status !== '') { values.push(String(number(input.status))); clauses.push('CAST(m."fxzt" AS text) = $' + values.length); } const where = clauses.join(' AND '); const countRow = await Psql.one('SELECT COUNT(*)::int AS total FROM "CommonModel" c JOIN "MemoryPracticeRecord" m ON m."company" = $1 AND CAST(m."id" AS text) = CAST(c."itemId" AS text) WHERE ' + where, values); const rowValues = values.concat([paging.size, (paging.index - 1) * paging.size]);
|
|
|
@@ -389,7 +393,7 @@ async function resolveContent(input, requireOne = false) { const fields = await
|
|
|
async function handler(request, response) {
|
|
|
try {
|
|
|
const input = inputOf(request); const action = String(input.action || ''); if (!action) return response.status(400).json(reject('缺少 action'));
|
|
|
- if (action === 'migration_status') return response.json(envelope({ implemented: ['app_update','user_login_passwd','user_register','user_info_name','user_get','user_list','e_user_list','user_update','user_update_pwd','user_update_pwdall','content_list','content_list_llk','content_get','content_uphis','node_list','node_get','product_list','product_get','product_stock_list','e_add_words','e_ck_list','e_get_21list','e_record_detail','e_order_detail','e_words_list','stu_record_update_v2','user_point_list','guestbook_add'], blocked: BLOCKED }));
|
|
|
+ if (action === 'migration_status') return response.json(envelope({ implemented: ['app_update','user_login_passwd','user_register','user_info_name','user_get','user_list','e_user_list','user_update','user_update_pwd','user_update_pwdall','content_list','content_list_llk','content_get','content_uphis','node_list','node_get','product_list','product_get','product_stock_list','e_add_words','e_ck_list','e_get_21list','e_order_detail','e_order_update_v2','e_record_detail','e_words_list','stu_record_update_v2','user_point_list','guestbook_add'], blocked: BLOCKED }));
|
|
|
if (BLOCKED[action]) return response.status(501).json(reject('migration_blocked: ' + BLOCKED[action]));
|
|
|
if (action === 'user_login_passwd') {
|
|
|
const username = String(input.name || input.username || '').trim(); const password = String(input.passwd || input.password || ''); if (!username || !password) return response.status(400).json(reject('请输入账号和密码'));
|
|
|
@@ -415,6 +419,7 @@ async function handler(request, response) {
|
|
|
if (action === 'product_get') { const result = await sqlPage('Product', { page: 1, pageSize: 1 }, [{ field: 'id', value: number(input.id) }]); return result.rows[0] ? response.json(envelope(result.rows[0])) : response.status(404).json(reject('商品不存在')); }
|
|
|
if (action === 'product_del' || action === 'product_stock_change' || action === 'product_sale_change') { const fields = await fieldsOf('Product'); const found = await findByLegacyId('Product', fields, input.id, ['id']); if (!found) return response.status(404).json(reject('商品不存在')); if (action === 'product_del') await found.destroy({ useMasterKey: true }); else { const field = action === 'product_stock_change' ? 'nums' : 'isChu'; if (fields[field]) found.set(field, action === 'product_stock_change' ? number(input.nums || input.stock) : Boolean(input.value ?? input.status)); await found.save(null, { useMasterKey: true }); } return response.json(envelope({ objectId: found.id })); }
|
|
|
const legacyId = ownLegacyId(current);
|
|
|
+ if (action === 'e_order_update_v2') return response.json(envelope(await updateOrderStatus(input, current)));
|
|
|
if (action === 'stu_record_update_v2') return response.json(envelope(await upsertStudyRecord(input, current)));
|
|
|
if (action === 'e_add_words') return response.json(envelope(await updateLearningWords(input, current)));
|
|
|
if (action === 'e_ck_list') { const result = await courseWordPage(input, current); return response.json(envelope(result.rows, undefined, result.page)); }
|