|
@@ -241,7 +241,7 @@ const BLOCKED = {
|
|
|
user_group_usr_supply: '目标 Schema 无会员续费订单类', user_group_usr_upgrade: '目标 Schema 无会员升级订单类', user_shop_order: '目标 Schema 无订单类', user_shop_sales: '目标 Schema 无销售明细类',
|
|
user_group_usr_supply: '目标 Schema 无会员续费订单类', user_group_usr_upgrade: '目标 Schema 无会员升级订单类', user_shop_order: '目标 Schema 无订单类', user_shop_sales: '目标 Schema 无销售明细类',
|
|
|
user_star_add: '目标 Schema 无收藏关系类', user_star_del: '目标 Schema 无收藏关系类', user_star_is: '目标 Schema 无收藏关系类', user_update_paypwd: '需要独立支付密码哈希服务',
|
|
user_star_add: '目标 Schema 无收藏关系类', user_star_del: '目标 Schema 无收藏关系类', user_star_is: '目标 Schema 无收藏关系类', user_update_paypwd: '需要独立支付密码哈希服务',
|
|
|
vote_add: '目标 Schema 无可证明的投票记录类', vote_ask: '目标 Schema 无可证明的投票记录类', vote_question: '目标 Schema 无可证明的投票记录类',
|
|
vote_add: '目标 Schema 无可证明的投票记录类', vote_ask: '目标 Schema 无可证明的投票记录类', vote_question: '目标 Schema 无可证明的投票记录类',
|
|
|
- content_add: 'content/addon 双表写入关系尚待字段级回归', content_add_zt: 'content/addon 双表写入关系尚待字段级回归', content_update: 'content/addon 双表写入关系尚待字段级回归',
|
|
|
|
|
|
|
+ content_add: 'content/addon 双表写入关系尚待字段级回归', content_update: 'content/addon 双表写入关系尚待字段级回归',
|
|
|
product_del: '尚未建立门店管理员授权关系', product_stock_change: '尚未建立门店管理员授权关系', product_sale_change: '尚未建立门店管理员授权关系',
|
|
product_del: '尚未建立门店管理员授权关系', product_stock_change: '尚未建立门店管理员授权关系', product_sale_change: '尚未建立门店管理员授权关系',
|
|
|
unit_record_update: '旧源码在调用前无条件 return;需确认是否废弃或提供新的单元聚合规则', e_order_update_v2: '状态 11 还需原子扣减课时、写余额账本并创建 21 天抗遗忘记录;云运行时未提供事务',
|
|
unit_record_update: '旧源码在调用前无条件 return;需确认是否废弃或提供新的单元聚合规则', e_order_update_v2: '状态 11 还需原子扣减课时、写余额账本并创建 21 天抗遗忘记录;云运行时未提供事务',
|
|
|
e_get_21list_tj: '迁移数据未包含复习收入金额字段或可验证的计价规则',
|
|
e_get_21list_tj: '迁移数据未包含复习收入金额字段或可验证的计价规则',
|
|
@@ -380,6 +380,18 @@ 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 }; }
|
|
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 };
|
|
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 };
|
|
|
}
|
|
}
|
|
|
|
|
+function reviewSchedule() {
|
|
|
|
|
+ const shifted = new Date(Date.now() + 8 * 60 * 60 * 1000); const today = new Date(Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), shifted.getUTCDate())); const pad = (value) => String(value).padStart(2, '0');
|
|
|
|
|
+ return [1,2,3,5,7,9,11,14,17,21,30,40,50,60,90].map((offset) => { const day = new Date(today.getTime() + offset * 86400000); return day.getUTCFullYear() + pad(day.getUTCMonth() + 1) + pad(day.getUTCDate()); }).join(',');
|
|
|
|
|
+}
|
|
|
|
|
+async function createStudyContent(input, current) {
|
|
|
|
|
+ const content = parseObject(input.content, '内容'); const addon = parseObject(input.addon, '附表内容'); const modelId = number(content.ModelID ?? content.modelId); if (modelId !== 56) fail(400, 'content_add_zt 只支持每日学习记录 Model 56');
|
|
|
|
|
+ const uid = await authorizeRequestedUser(current, addon.UserID ?? addon.userId); const words = addon.xxqs === undefined ? [] : await validateStudyWords(addon.xxqs); const actorId = ownLegacyId(current); const recordId = Math.floor(100000000000000 + Math.random() * 800000000000000); const generalId = recordId + 1; let record; let common;
|
|
|
|
|
+ record = new Parse.Object('DailyStudyRecord'); record.set('company', current.get('company')); record.set('sourceKey', 'cloud:content_add_zt:' + uid + ':' + generalId); record.set('userId', uid); record.set('pl', actorId && actorId !== uid ? String(actorId) : '0'); record.set('fxrl', reviewSchedule()); record.set('xxqs', JSON.stringify(words)); record.set('learned', words.length || Math.max(0, number(addon.learned))); record.set('ygg', words.length ? words.filter((item) => item.check === 1).length : Math.max(0, number(addon.ygg))); record.set('djq', words.length ? words.filter((item) => item.check === 2).length : Math.max(0, number(addon.djq)));
|
|
|
|
|
+ if (addon.con !== undefined) record.set('con', cleanText(addon.con, 1000)); if (addon.dqrq !== undefined) { const date = cleanText(addon.dqrq, 20); if (!/^\d{8}$/.test(date)) fail(400, '学习日期必须为 yyyymmdd'); record.set('dqrq', date); } if (addon.dsid !== undefined) record.set('dsid', cleanText(addon.dsid, 100)); if (addon.szmdid !== undefined) record.set('szmdid', cleanText(addon.szmdid, 100));
|
|
|
|
|
+ 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:content_add_zt:' + uid + ':' + generalId); common.set('generalId', generalId); common.set('itemId', recordId); common.set('modelId', 56); common.set('nodeId', number(content.nodeId ?? content.NodeID, 291) || 291); common.set('tableName', 'ZL_C_ss'); common.set('title', cleanText(content.title ?? content.Title, 200) || cleanText(current.get('nickname') || current.get('username'), 200) || '学习记录'); common.set('inputer', cleanText(content.inputer ?? content.Inputer ?? current.get('username'), 100)); common.set('hits', number(content.Hits ?? content.hits, record.get('learned'))); common.set('status', number(content.Status ?? content.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 String(generalId);
|
|
|
|
|
+}
|
|
|
async function orderStatistics(input, current) {
|
|
async function orderStatistics(input, current) {
|
|
|
const uid = await authorizeRequestedUser(current, input.uid); let target = current; if (uid !== ownLegacyId(current)) { const fields = await fieldsOf('_User'); target = await findByLegacyId('_User', fields, uid, ['legacyUserId','userid','num']); } if (!target) fail(404, '用户不存在'); const groupId = number(target.get('legacyGroupId') || target.get('groupId')); let t30 = 0; let t60 = 0; let tTiyan = 0; let total = 0; let commission = 0; let duration = 0;
|
|
const uid = await authorizeRequestedUser(current, input.uid); let target = current; if (uid !== ownLegacyId(current)) { const fields = await fieldsOf('_User'); target = await findByLegacyId('_User', fields, uid, ['legacyUserId','userid','num']); } if (!target) fail(404, '用户不存在'); const groupId = number(target.get('legacyGroupId') || target.get('groupId')); let t30 = 0; let t60 = 0; let tTiyan = 0; let total = 0; let commission = 0; let duration = 0;
|
|
|
if (groupId === 3) { const row = await Psql.one('SELECT COUNT(*) FILTER (WHERE CAST("kclx" AS text) = \'1\')::int AS t30, COUNT(*) FILTER (WHERE CAST("kclx" AS text) = \'2\')::int AS t60, COUNT(*) FILTER (WHERE CAST("kclx" AS text) = \'3\')::int AS tiyan, COUNT(*)::int AS total FROM "LessonRecord" WHERE "company" = $1 AND CAST("jsmz" AS text) = $2', [DEFAULT_COMPANY_ID, String(uid)]); t30 = number(row.t30); t60 = number(row.t60); tTiyan = number(row.tiyan); total = number(row.total); commission = t30 * 20 + t60 * 40 + tTiyan * 40; duration = t30 * 0.5 + t60 + tTiyan; }
|
|
if (groupId === 3) { const row = await Psql.one('SELECT COUNT(*) FILTER (WHERE CAST("kclx" AS text) = \'1\')::int AS t30, COUNT(*) FILTER (WHERE CAST("kclx" AS text) = \'2\')::int AS t60, COUNT(*) FILTER (WHERE CAST("kclx" AS text) = \'3\')::int AS tiyan, COUNT(*)::int AS total FROM "LessonRecord" WHERE "company" = $1 AND CAST("jsmz" AS text) = $2', [DEFAULT_COMPANY_ID, String(uid)]); t30 = number(row.t30); t60 = number(row.t60); tTiyan = number(row.tiyan); total = number(row.total); commission = t30 * 20 + t60 * 40 + tTiyan * 40; duration = t30 * 0.5 + t60 + tTiyan; }
|
|
@@ -395,7 +407,7 @@ async function resolveContent(input, requireOne = false) { const fields = await
|
|
|
async function handler(request, response) {
|
|
async function handler(request, response) {
|
|
|
try {
|
|
try {
|
|
|
const input = inputOf(request); const action = String(input.action || ''); if (!action) return response.status(400).json(reject('缺少 action'));
|
|
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_order_detail','e_order_tongji','e_record_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','content_add_zt','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_tongji','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 (BLOCKED[action]) return response.status(501).json(reject('migration_blocked: ' + BLOCKED[action]));
|
|
|
if (action === 'user_login_passwd') {
|
|
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('请输入账号和密码'));
|
|
const username = String(input.name || input.username || '').trim(); const password = String(input.passwd || input.password || ''); if (!username || !password) return response.status(400).json(reject('请输入账号和密码'));
|
|
@@ -412,7 +424,7 @@ async function handler(request, response) {
|
|
|
if (action === 'user_update' || action === 'user_update_pwd' || action === 'user_update_pwdall') { if (action !== 'user_update') { const password = String(input.passwd || input.password || input.pwd || ''); if (password.length < 8) return response.status(400).json(reject('新密码至少 8 位')); current.setPassword(password); current.set('passwordResetRequired', false); } else { const allowed = { honeyName:'nickname', nickname:'nickname', mobile:'mobile', userFace:'avatar', avatar:'avatar', realName:'realName', gender:'gender', school:'school' }; for (const [source,target] of Object.entries(allowed)) if (input[source] !== undefined) current.set(target, input[source]); } await current.save(null, { useMasterKey: true }); return response.json(envelope(legacyUser(current, action !== 'user_update'))); }
|
|
if (action === 'user_update' || action === 'user_update_pwd' || action === 'user_update_pwdall') { if (action !== 'user_update') { const password = String(input.passwd || input.password || input.pwd || ''); if (password.length < 8) return response.status(400).json(reject('新密码至少 8 位')); current.setPassword(password); current.set('passwordResetRequired', false); } else { const allowed = { honeyName:'nickname', nickname:'nickname', mobile:'mobile', userFace:'avatar', avatar:'avatar', realName:'realName', gender:'gender', school:'school' }; for (const [source,target] of Object.entries(allowed)) if (input[source] !== undefined) current.set(target, input[source]); } await current.save(null, { useMasterKey: true }); return response.json(envelope(legacyUser(current, action !== 'user_update'))); }
|
|
|
if (action === 'app_update') { const filters = input.platform ? [{ field: 'platform', value: input.platform }] : []; const result = await sqlPage('App', { page: 1, pageSize: 1 }, filters); return response.json(envelope(result.rows[0] || null)); }
|
|
if (action === 'app_update') { const filters = input.platform ? [{ field: 'platform', value: input.platform }] : []; const result = await sqlPage('App', { page: 1, pageSize: 1 }, filters); return response.json(envelope(result.rows[0] || null)); }
|
|
|
if (action === 'content_list' || action === 'content_list_llk') { const result = await contentPage(input, !current); return response.json(envelope(result.rows, undefined, result.page)); }
|
|
if (action === 'content_list' || action === 'content_list_llk') { const result = await contentPage(input, !current); return response.json(envelope(result.rows, undefined, result.page)); }
|
|
|
- if (action === 'content_get') { const id = String(input.id || input.gid || input.generalId || ''); if (!id) return response.status(400).json(reject('缺少内容 ID')); const identity = /^[A-Za-z0-9_-]{10,40}$/.test(id) ? { objectId: id } : { generalId: id }; const base = await contentPage({ page: 1, pageSize: 1, ...identity }); let detail = base.rows[0]; if (!detail) return response.status(404).json(reject('内容不存在')); const detailModel = number(detail.modelId || detail.ModelID); if (CONTENT_ADDONS[detailModel]) detail = (await contentPage({ page: 1, pageSize: 1, modelId: detailModel, ...identity })).rows[0] || detail; if (PRIVATE_CONTENT_MODELS.has(detailModel)) { if (!current) return response.status(401).json(reject('该内容详情需要登录')); await authorizeContentDetail(current, detail); } return response.json(envelope(detail)); }
|
|
|
|
|
|
|
+ if (action === 'content_get') { const id = String(input.id || input.gid || input.generalId || ''); if (!id) return response.status(400).json(reject('缺少内容 ID')); const identity = /^\d+$/.test(id) ? { generalId: id } : { objectId: id }; const base = await contentPage({ page: 1, pageSize: 1, ...identity }); let detail = base.rows[0]; if (!detail) return response.status(404).json(reject('内容不存在')); const detailModel = number(detail.modelId || detail.ModelID); if (CONTENT_ADDONS[detailModel]) detail = (await contentPage({ page: 1, pageSize: 1, modelId: detailModel, ...identity })).rows[0] || detail; if (PRIVATE_CONTENT_MODELS.has(detailModel)) { if (!current) return response.status(401).json(reject('该内容详情需要登录')); await authorizeContentDetail(current, detail); } return response.json(envelope(detail)); }
|
|
|
if (action === 'content_uphis') { const resolved = await resolveContent(input, true); if (!resolved.object) return response.status(404).json(reject('内容不存在')); resolved.object.increment('hits', Math.max(1, number(input.num, 1))); await resolved.object.save(null, { useMasterKey: true }); return response.json(envelope({ hits: resolved.object.get('hits') })); }
|
|
if (action === 'content_uphis') { const resolved = await resolveContent(input, true); if (!resolved.object) return response.status(404).json(reject('内容不存在')); resolved.object.increment('hits', Math.max(1, number(input.num, 1))); await resolved.object.save(null, { useMasterKey: true }); return response.json(envelope({ hits: resolved.object.get('hits') })); }
|
|
|
if (action === 'content_add' || action === 'content_update') { if (!current) fail(401,'需要登录'); const resolved = action === 'content_update' ? await resolveContent(input, true) : { object: new Parse.Object('CommonModel'), fields: await fieldsOf('CommonModel') }; const object = resolved.object; if (!object) return response.status(404).json(reject('内容不存在')); const allowed = ['title','subtitle','nodeId','modelId','status','topImg','tagKey','template']; for (const name of allowed) if (input[name] !== undefined && resolved.fields[name]) object.set(name, ['nodeId','modelId','status'].includes(name) ? number(input[name]) : input[name]); object.set('company', companyPointer()); object.set('inputer', String(current.get('username') || '')); await object.save(null, { useMasterKey: true }); return response.json(envelope(safe(object))); }
|
|
if (action === 'content_add' || action === 'content_update') { if (!current) fail(401,'需要登录'); const resolved = action === 'content_update' ? await resolveContent(input, true) : { object: new Parse.Object('CommonModel'), fields: await fieldsOf('CommonModel') }; const object = resolved.object; if (!object) return response.status(404).json(reject('内容不存在')); const allowed = ['title','subtitle','nodeId','modelId','status','topImg','tagKey','template']; for (const name of allowed) if (input[name] !== undefined && resolved.fields[name]) object.set(name, ['nodeId','modelId','status'].includes(name) ? number(input[name]) : input[name]); object.set('company', companyPointer()); object.set('inputer', String(current.get('username') || '')); await object.save(null, { useMasterKey: true }); return response.json(envelope(safe(object))); }
|
|
|
if (action === 'node_list') { const filters = input.pid !== undefined ? [{ field: 'parentId', value: number(input.pid) }] : []; const result = await sqlPage('Node', input, filters); return response.json(envelope(result.rows, undefined, result.page)); }
|
|
if (action === 'node_list') { const filters = input.pid !== undefined ? [{ field: 'parentId', value: number(input.pid) }] : []; const result = await sqlPage('Node', input, filters); return response.json(envelope(result.rows, undefined, result.page)); }
|
|
@@ -421,6 +433,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_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 })); }
|
|
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);
|
|
const legacyId = ownLegacyId(current);
|
|
|
|
|
+ if (action === 'content_add_zt') return response.json(envelope(await createStudyContent(input, current)));
|
|
|
if (action === 'e_order_tongji') return response.json(envelope(await orderStatistics(input, current)));
|
|
if (action === 'e_order_tongji') return response.json(envelope(await orderStatistics(input, current)));
|
|
|
if (action === 'stu_record_update_v2') return response.json(envelope(await upsertStudyRecord(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_add_words') return response.json(envelope(await updateLearningWords(input, current)));
|