|
|
@@ -2564,7 +2564,10 @@ async function newWordPage(input, current) {
|
|
|
const result = rows.map((source) => { const practice = { id: source.id, kcid: source.kcid, scid: source.scid, xxcs: source.xxcs, yhid: source.yhid, jrscb: source.jrscb }; const detail = normalizeWordRow(source); return { ...practice, GeneralID: number(source.__practiceGeneralId || source.id), Title: detail.Title || detail.title || '', detail: [detail] }; }); const total = number(countRow.total); return { rows: result, page: { itemCount: total, pageCount: Math.ceil(total / paging.size), pageIndex: paging.index, pageSize: paging.size } };
|
|
|
}
|
|
|
async function courseWordPage(input, current) {
|
|
|
- const uid = await authorizeRequestedUser(current, input.uid); const nodes = String(input.nids || input.nid || '').split(',').map((value) => String(number(value))).filter((value) => value !== '0'); if (!nodes.length) fail(400, '缺少词库单元 ID'); const paging = { index: Math.max(1, number(input.cpage || input.page, 1)), size: Math.min(10000, Math.max(1, number(input.psize || input.pageSize, 1000))) }; const values = [DEFAULT_COMPANY_ID, String(uid), nodes]; const where = 'c."company" = $1 AND CAST(c."modelId" AS text) = \'52\' AND CAST(c."nodeId" AS text) = ANY($3::text[])'; const countRow = await Psql.one('SELECT COUNT(*)::int AS total FROM "CommonModel" c WHERE ' + where, values); const rows = await Psql.query('SELECT c.*, row_to_json(v) AS "__addon", p."xxcs" AS "__learned", p."jrscb" AS "__newWord" FROM "CommonModel" c LEFT JOIN "VocabularyWord" v ON v."company" = $1 AND CAST(v."id" AS text) = CAST(c."itemId" AS text) LEFT JOIN LATERAL (SELECT pr."xxcs", pr."jrscb" FROM "PracticeRecord" pr WHERE pr."company" = $1 AND CAST(pr."yhid" AS text) = $2 AND CAST(pr."scid" AS text) = CAST(c."generalId" AS text) ORDER BY pr."updatedAt" DESC LIMIT 1) p ON TRUE WHERE ' + where + ' ORDER BY c."orderId" ASC, c."generalId" ASC LIMIT $4 OFFSET $5', values.concat([paging.size, (paging.index - 1) * paging.size])); const result = rows.map((source) => { const learned = number(source.__learned); const newWord = number(source.__newWord); delete source.__learned; delete source.__newWord; const word = normalizeWordRow(source); return { ...word, detail: word, gldy: word.GeneralID, w_learned: learned, ifnew: newWord }; }); const total = number(countRow.total); return { rows: result, page: { itemCount: total, pageCount: Math.ceil(total / paging.size), pageIndex: paging.index, pageSize: paging.size } };
|
|
|
+ const uid = await authorizeRequestedUser(current, input.uid); const nodes = String(input.nids || input.nid || '').split(',').map((value) => String(number(value))).filter((value) => value !== '0'); if (!nodes.length) fail(400, '缺少词库单元 ID'); const paging = { index: Math.max(1, number(input.cpage || input.page, 1)), size: Math.min(500, Math.max(1, number(input.psize || input.pageSize, 100))) }; const values = [DEFAULT_COMPANY_ID, String(uid), nodes]; const where = 'c."company" = $1 AND c."modelId" = 52 AND CAST(c."nodeId" AS text) = ANY($3::text[])';
|
|
|
+ const countPromise = Psql.one('SELECT COUNT(*)::int AS total FROM "CommonModel" c WHERE ' + where, values);
|
|
|
+ const rowPromise = Psql.query('SELECT c.*, row_to_json(v) AS "__addon", COALESCE(p."learned",0) AS "__learned", COALESCE(p."newWord",0) AS "__newWord" FROM "CommonModel" c LEFT JOIN "VocabularyWord" v ON v."company" = $1 AND v."id" = c."itemId" LEFT JOIN (SELECT CAST(pr."scid" AS text) AS "wordId", MAX(CASE WHEN CAST(pr."xxcs" AS text) ~ \'^[0-9]+(?:\\.[0-9]+)?$\' THEN CAST(pr."xxcs" AS numeric) ELSE 0 END)::int AS "learned", MAX(CASE WHEN LOWER(CAST(pr."jrscb" AS text)) IN (\'1\',\'true\') THEN 1 ELSE 0 END)::int AS "newWord" FROM "PracticeRecord" pr WHERE pr."company" = $1 AND CAST(pr."yhid" AS text) = $2 GROUP BY CAST(pr."scid" AS text)) p ON p."wordId" = CAST(c."generalId" AS text) WHERE ' + where + ' ORDER BY c."orderId" ASC, c."generalId" ASC LIMIT $4 OFFSET $5', values.concat([paging.size, (paging.index - 1) * paging.size]));
|
|
|
+ const [countRow, rows] = await Promise.all([countPromise, rowPromise]); const result = rows.map((source) => { const learned = number(source.__learned); const newWord = number(source.__newWord); delete source.__learned; delete source.__newWord; const word = normalizeWordRow(source); return { ...word, detail: word, gldy: word.GeneralID, w_learned: learned, ifnew: newWord }; }); const total = number(countRow.total); return { rows: result, page: { itemCount: total, pageCount: Math.ceil(total / paging.size), pageIndex: paging.index, pageSize: paging.size } };
|
|
|
}
|
|
|
async function updateLearningWords(input, current) {
|
|
|
const uid = await authorizeRequestedUser(current, input.uid); const wordIds = [...new Set(String(input.wordsId || input.wordIds || '').split(',').map((value) => String(number(value))).filter((value) => value !== '0'))]; if (!wordIds.length) fail(400, '请选择单词'); if (wordIds.length > 100) fail(400, '单次最多操作 100 个单词'); const saveLearning = number(input.save) === 1; const hasNewWordFlag = input.ifnew !== undefined && input.ifnew !== ''; const newWordFlag = hasNewWordFlag ? (number(input.ifnew) === 1 ? '1' : '0') : null; if (!saveLearning && !hasNewWordFlag) fail(400, '缺少学习或生词操作类型'); const kcid = number(input.kcid);
|
|
|
@@ -2666,13 +2669,15 @@ async function appLearningTarget(current,requestedId) {
|
|
|
const linked=await Psql.oneOrNone('SELECT 1 AS found FROM "CourseAppointment" WHERE "company"=$1 AND "pl"=$2 AND "szyh"=$3 LIMIT 1',[DEFAULT_COMPANY_ID,String(ownId),String(targetId)]);if(!linked)fail(403,'无权查看该学员的学习记录');return targetId;
|
|
|
}
|
|
|
async function appLearningOverview(input,current) {
|
|
|
- const targetId=await appLearningTarget(current,input.studentId||input.uid),selected=String(input.date||'').replace(/\D/g,'').slice(0,8),recordValues=[DEFAULT_COMPANY_ID,targetId],recordWhere=['d."company"=$1','d."userId"=$2'];if(selected){recordValues.push(selected);recordWhere.push('d."dqrq"=$3');}
|
|
|
+ const targetId=await appLearningTarget(current,input.studentId||input.uid),selected=String(input.date||'').replace(/\D/g,'').slice(0,8),calendarStart=String(input.calendarStart||'').replace(/\D/g,'').slice(0,8),calendarEnd=String(input.calendarEnd||'').replace(/\D/g,'').slice(0,8),recordValues=[DEFAULT_COMPANY_ID,targetId],recordWhere=['d."company"=$1','d."userId"=$2'];if(selected){recordValues.push(selected);recordWhere.push('d."dqrq"=$3');}
|
|
|
const recordSql='SELECT c."objectId",c."generalId",c."title",c."createTime",c."createdAt",d."id",d."pl",d."con",d."djq",d."ygg",d."dqrq",d."dsid",d."fxrl",d."xxqs",d."szmdid",d."userId",d."learned" FROM "DailyStudyRecord" d LEFT JOIN "CommonModel" c ON c."company"=$1 AND c."modelId"=56 AND c."itemId"=d."id" WHERE '+recordWhere.join(' AND ')+' ORDER BY COALESCE(c."createTime",c."createdAt",d."updatedAt",d."createdAt") DESC LIMIT 100';
|
|
|
const scheduleValues=[DEFAULT_COMPANY_ID,String(targetId)];let schedulePromise=Promise.resolve([]);if(selected)schedulePromise=Psql.query('SELECT c."objectId",c."generalId",c."title",c."createTime",c."createdAt",a.*,n."nodeName" AS "courseTitle" FROM "CourseAppointment" a LEFT JOIN "CommonModel" c ON c."company"=$1 AND c."modelId"=54 AND c."itemId"=a."id" LEFT JOIN "Node" n ON n."company"=$1 AND n."nodeId"=CASE WHEN COALESCE(a."kcid",\'\')~\'^\\d+(?:\\.0+)?$\' THEN a."kcid"::numeric ELSE -1 END WHERE a."company"=$1 AND a."szyh"=$2 AND (REPLACE(LEFT(COALESCE(NULLIF(a."bxrq",\'\'),a."yysj"),10),\'-\',\'\')=$3 OR REPLACE(LEFT(COALESCE(NULLIF(a."yysj",\'\'),a."bxrq"),10),\'-\',\'\')=$3) ORDER BY COALESCE(NULLIF(a."yysj",\'\'),a."bxrq") ASC LIMIT 100',scheduleValues.concat([selected]));
|
|
|
+ const dateValues=[DEFAULT_COMPANY_ID,targetId],dateWhere=['"company"=$1','"userId"=$2'];if(calendarStart&&calendarEnd){dateValues.push(calendarStart,calendarEnd);dateWhere.push('"dqrq">=$3 AND "dqrq"<=$4');}
|
|
|
+ const groupValues=[DEFAULT_COMPANY_ID,String(targetId)],groupWhere=['"company"=$1','"szyh"=$2'];if(calendarStart&&calendarEnd){groupValues.push(calendarStart,calendarEnd);groupWhere.push('REPLACE(LEFT(COALESCE(NULLIF("bxrq",\'\'),"yysj"),10),\'-\',\'\')>=$3 AND REPLACE(LEFT(COALESCE(NULLIF("bxrq",\'\'),"yysj"),10),\'-\',\'\')<=$4');}
|
|
|
const [records,dateRows,scheduleGroups,schedules,courses,latestReading]=await Promise.all([
|
|
|
Psql.query(recordSql,recordValues),
|
|
|
- Psql.query('SELECT "dqrq" AS date,COUNT(*)::int AS count FROM "DailyStudyRecord" WHERE "company"=$1 AND "userId"=$2 GROUP BY "dqrq"',[DEFAULT_COMPANY_ID,targetId]),
|
|
|
- Psql.query('SELECT "bxrq","yysj","dszt",COUNT(*)::int AS count FROM "CourseAppointment" WHERE "company"=$1 AND "szyh"=$2 GROUP BY "bxrq","yysj","dszt"',[DEFAULT_COMPANY_ID,String(targetId)]),
|
|
|
+ Psql.query('SELECT "dqrq" AS date,COUNT(*)::int AS count FROM "DailyStudyRecord" WHERE '+dateWhere.join(' AND ')+' GROUP BY "dqrq"',dateValues),
|
|
|
+ Psql.query('SELECT "bxrq","yysj","dszt",COUNT(*)::int AS count FROM "CourseAppointment" WHERE '+groupWhere.join(' AND ')+' GROUP BY "bxrq","yysj","dszt"',groupValues),
|
|
|
schedulePromise,
|
|
|
Psql.query('SELECT c."objectId",c."generalId",c."title",c."createTime",c."createdAt",b.*,n."nodeName" AS "courseTitle" FROM "CourseBinding" b LEFT JOIN "CommonModel" c ON c."company"=$1 AND c."modelId"=58 AND c."itemId"=b."id" LEFT JOIN "Node" n ON n."company"=$1 AND n."nodeId"=CASE WHEN COALESCE(b."kcid",\'\')~\'^\\d+(?:\\.0+)?$\' THEN b."kcid"::numeric ELSE -1 END WHERE b."company"=$1 AND b."yhid"=$2 ORDER BY b."id" DESC LIMIT 100',[DEFAULT_COMPANY_ID,String(targetId)]),
|
|
|
Psql.oneOrNone('SELECT c."objectId",c."generalId",c."title",c."createTime",c."createdAt",d."id",d."pl",d."con",d."djq",d."ygg",d."dqrq",d."dsid",d."fxrl",d."xxqs",d."szmdid",d."userId",d."learned" FROM "DailyStudyRecord" d LEFT JOIN "CommonModel" c ON c."company"=$1 AND c."modelId"=56 AND c."itemId"=d."id" WHERE d."company"=$1 AND d."userId"=$2 AND COALESCE(NULLIF(d."xxqs",\'\'),\'[]\')<>\'[]\' ORDER BY COALESCE(c."createTime",c."createdAt",d."updatedAt",d."createdAt") DESC LIMIT 1',[DEFAULT_COMPANY_ID,targetId])
|
|
|
@@ -2683,8 +2688,8 @@ async function appLearningOverview(input,current) {
|
|
|
return {records:normalizedRecords,latestReadingRecord:appOverviewStudyRow(latestReading),recordDateCounts,scheduleDateCounts,schedules:schedules.map(appOverviewScheduleRow),courses:courses.map(appOverviewCourseRow),refreshedAt:new Date().toISOString()};
|
|
|
}
|
|
|
async function appCompanionOverview(input,current) {
|
|
|
- const uid=await authorizeRequestedUser(current,input.uid),coach=String(input.mode||'')==='coach'||number(current.get('legacyGroupId')||legacyData(current).GroupID)===3,history=Boolean(input.history),field=coach?'pl':'szyh',values=[DEFAULT_COMPANY_ID,String(uid)],clauses=['a."company"=$1','a."'+field+'"=$2'];if(history)clauses.push('a."dszt"~\'^\\d+(?:\\.0+)?$\' AND a."dszt"::numeric>=30');const limit=Math.min(500,Math.max(1,number(input.limit,history?100:500))),direction=coach&&!history?'ASC':'DESC';
|
|
|
- const schedulePromise=Psql.query('SELECT c."objectId",c."generalId",c."title",c."createTime",c."createdAt",a.*,n."nodeName" AS "courseTitle" FROM "CourseAppointment" a LEFT JOIN "CommonModel" c ON c."company"=$1 AND c."modelId"=54 AND c."itemId"=a."id" LEFT JOIN "Node" n ON n."company"=$1 AND n."nodeId"=CASE WHEN COALESCE(a."kcid",\'\')~\'^\\d+(?:\\.0+)?$\' THEN a."kcid"::numeric ELSE -1 END WHERE '+clauses.join(' AND ')+' ORDER BY COALESCE(NULLIF(a."yysj",\'\'),a."bxrq") '+direction+' LIMIT $3',values.concat([limit]));
|
|
|
+ const uid=await authorizeRequestedUser(current,input.uid),coach=String(input.mode||'')==='coach'||number(current.get('legacyGroupId')||legacyData(current).GroupID)===3,history=Boolean(input.history),field=coach?'pl':'szyh',values=[DEFAULT_COMPANY_ID,String(uid)],clauses=['a."company"=$1','a."'+field+'"=$2'];if(history)clauses.push('a."dszt"~\'^\\d+(?:\\.0+)?$\' AND a."dszt"::numeric>=30');const rangeStart=String(input.rangeStart||'').replace(/\D/g,'').slice(0,8),rangeEnd=String(input.rangeEnd||'').replace(/\D/g,'').slice(0,8);if(rangeStart&&rangeEnd){values.push(rangeStart,rangeEnd);clauses.push('REPLACE(LEFT(COALESCE(NULLIF(a."bxrq",\'\'),a."yysj"),10),\'-\',\'\')>=$'+(values.length-1)+' AND REPLACE(LEFT(COALESCE(NULLIF(a."bxrq",\'\'),a."yysj"),10),\'-\',\'\')<=$'+values.length);}const limit=Math.min(500,Math.max(1,number(input.limit,history?100:200))),direction=coach&&!history?'ASC':'DESC';
|
|
|
+ const schedulePromise=Psql.query('SELECT c."objectId",c."generalId",c."title",c."createTime",c."createdAt",a.*,n."nodeName" AS "courseTitle" FROM "CourseAppointment" a LEFT JOIN "CommonModel" c ON c."company"=$1 AND c."modelId"=54 AND c."itemId"=a."id" LEFT JOIN "Node" n ON n."company"=$1 AND n."nodeId"=CASE WHEN COALESCE(a."kcid",\'\')~\'^\\d+(?:\\.0+)?$\' THEN a."kcid"::numeric ELSE -1 END WHERE '+clauses.join(' AND ')+' ORDER BY COALESCE(NULLIF(a."yysj",\'\'),a."bxrq") '+direction+' LIMIT $'+(values.length+1),values.concat([limit]));
|
|
|
const coursePromise=coach?Promise.resolve([]):Psql.query('SELECT c."objectId",c."generalId",c."title",c."createTime",c."createdAt",b.*,n."nodeName" AS "courseTitle" FROM "CourseBinding" b LEFT JOIN "CommonModel" c ON c."company"=$1 AND c."modelId"=58 AND c."itemId"=b."id" LEFT JOIN "Node" n ON n."company"=$1 AND n."nodeId"=CASE WHEN COALESCE(b."kcid",\'\')~\'^\\d+(?:\\.0+)?$\' THEN b."kcid"::numeric ELSE -1 END WHERE b."company"=$1 AND b."yhid"=$2 ORDER BY b."id" DESC LIMIT 100',[DEFAULT_COMPANY_ID,String(uid)]);
|
|
|
const [schedules,courses]=await Promise.all([schedulePromise,coursePromise]);return{schedules:schedules.map(appOverviewScheduleRow),courses:courses.map(appOverviewCourseRow),refreshedAt:new Date().toISOString()};
|
|
|
}
|
|
|
@@ -2703,8 +2708,8 @@ async function appAccountOverview(input,current) {
|
|
|
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); }
|
|
|
function learnedAt(row) { for (const value of [row.studyCommonCreateTime,row.studyCommonCreatedAt,row.studyRecordCreatedAt,row.studyRecordUpdatedAt]) { if (!value) continue; const date = value instanceof Date ? value : new Date(value); if (!Number.isNaN(date.getTime()) && date.getUTCFullYear() > 1970) return date.toISOString(); } return learnedDate(row); }
|
|
|
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]);
|
|
|
- const rows = await Psql.query('SELECT c.*, row_to_json(m) AS "__addon", d."learned", d."dqrq", dc."createTime" AS "studyCommonCreateTime", dc."createdAt" AS "studyCommonCreatedAt", d."createdAt" AS "studyRecordCreatedAt", d."updatedAt" AS "studyRecordUpdatedAt", n."nodeName" AS "kc_title" FROM "CommonModel" c JOIN "MemoryPracticeRecord" m ON m."company" = $1 AND CAST(m."id" AS text) = CAST(c."itemId" AS text) LEFT JOIN "CommonModel" dc ON dc."company" = $1 AND CAST(dc."modelId" AS text) = \'56\' AND CAST(dc."generalId" AS text) = CAST(m."xxjlid" AS text) LEFT JOIN "DailyStudyRecord" d ON d."company" = $1 AND CAST(d."id" AS text) = CAST(dc."itemId" AS text) LEFT JOIN "Node" n ON n."company" = $1 AND CAST(n."nodeId" AS text) = CAST(m."kcid" AS text) WHERE ' + where + ' ORDER BY m."kywsj" DESC, c."updatedAt" DESC LIMIT $' + (values.length + 1) + ' OFFSET $' + (values.length + 2), rowValues); const result = rows.map((source) => { const addon = source.__addon || {}; delete source.__addon; const row = legacyAliases({ ...source, ...addon }, 'CommonModel'); row.learned_date = learnedDate(row); row.learned_at = learnedAt(row); delete row.studyCommonCreateTime; delete row.studyCommonCreatedAt; delete row.studyRecordCreatedAt; delete row.studyRecordUpdatedAt; if (income) { row.money = 0; row.incomeRuleUnavailable = true; } return row; }); const total = number(countRow.total); return { rows: result, page: { itemCount: total, pageCount: Math.ceil(total / paging.size), pageIndex: paging.index, pageSize: paging.size } };
|
|
|
+ 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', 'c."modelId" = 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 countPromise = Psql.one('SELECT COUNT(*)::int AS total FROM "CommonModel" c JOIN "MemoryPracticeRecord" m ON m."company" = $1 AND m."id" = c."itemId" WHERE ' + where, values); const rowValues = values.concat([paging.size, (paging.index - 1) * paging.size]);
|
|
|
+ const rowPromise = Psql.query('SELECT c.*, row_to_json(m) AS "__addon", d."learned", d."dqrq", dc."createTime" AS "studyCommonCreateTime", dc."createdAt" AS "studyCommonCreatedAt", d."createdAt" AS "studyRecordCreatedAt", d."updatedAt" AS "studyRecordUpdatedAt", n."nodeName" AS "kc_title" FROM "CommonModel" c JOIN "MemoryPracticeRecord" m ON m."company" = $1 AND m."id" = c."itemId" LEFT JOIN "CommonModel" dc ON dc."company" = $1 AND dc."modelId" = 56 AND CAST(dc."generalId" AS text) = CAST(m."xxjlid" AS text) LEFT JOIN "DailyStudyRecord" d ON d."company" = $1 AND d."id" = dc."itemId" LEFT JOIN "Node" n ON n."company" = $1 AND CAST(n."nodeId" AS text) = CAST(m."kcid" AS text) WHERE ' + where + ' ORDER BY m."kywsj" DESC, c."updatedAt" DESC LIMIT $' + (values.length + 1) + ' OFFSET $' + (values.length + 2), rowValues); const [countRow, rows] = await Promise.all([countPromise, rowPromise]); const result = rows.map((source) => { const addon = source.__addon || {}; delete source.__addon; const row = legacyAliases({ ...source, ...addon }, 'CommonModel'); row.learned_date = learnedDate(row); row.learned_at = learnedAt(row); delete row.studyCommonCreateTime; delete row.studyCommonCreatedAt; delete row.studyRecordCreatedAt; delete row.studyRecordUpdatedAt; if (income) { row.money = 0; row.incomeRuleUnavailable = true; } return row; }); const total = number(countRow.total); return { rows: result, page: { itemCount: total, pageCount: Math.ceil(total / paging.size), pageIndex: paging.index, pageSize: paging.size } };
|
|
|
}
|
|
|
async function memoryCompletedCount(input,current){
|
|
|
const uid=await authorizeRequestedUser(current,input.uid);
|