|
|
@@ -8,24 +8,24 @@ async function lessonExportCreate(context,input){
|
|
|
return Psql.tx(async db=>{
|
|
|
await db.one('SELECT pg_advisory_xact_lock(hashtext($1),hashtext($2))',[company,actor]);
|
|
|
await db.none('DELETE FROM xs_history_export_job WHERE company=$1 AND actor=$2 AND expires_at<=now()',[company,actor]);
|
|
|
- const prior=(await db.query('SELECT * FROM xs_history_export_job WHERE company=$1 AND actor=$2 AND request_key=$3 AND expires_at>now()',[company,actor,key]))[0];
|
|
|
+ const prior=(await db.query('SELECT id,filters,scope,status,total,processed,error,snapshot_ready,expires_at FROM xs_history_export_job WHERE company=$1 AND actor=$2 AND request_key=$3 AND expires_at>now()',[company,actor,key]))[0];
|
|
|
if(prior){if(JSON.stringify(prior.filters)!==JSON.stringify(JSON.parse(JSON.stringify(filters)))||prior.scope!==scope){
|
|
|
// PostgreSQL jsonb canonicalizes key order; compare its canonical representation instead.
|
|
|
const same=await db.one('SELECT filters=$2::jsonb same FROM xs_history_export_job WHERE id=$1',[prior.id,JSON.stringify(filters)]);if(!same.same||prior.scope!==scope)fail(409,'导出请求条件已变化');
|
|
|
}return lessonExportProjection(prior);}
|
|
|
- const active=(await db.query("SELECT * FROM xs_history_export_job WHERE company=$1 AND actor=$2 AND status IN('queued','running') AND expires_at>now()",[company,actor]))[0];
|
|
|
+ const active=(await db.query("SELECT id,filters,scope,status,total,processed,error,snapshot_ready,expires_at FROM xs_history_export_job WHERE company=$1 AND actor=$2 AND status IN('queued','running') AND expires_at>now()",[company,actor]))[0];
|
|
|
if(active){const same=await db.one('SELECT filters=$2::jsonb same FROM xs_history_export_job WHERE id=$1',[active.id,JSON.stringify(filters)]);if(!same.same||active.scope!==scope)fail(409,'已有导出进行中,请等待或取消');return lessonExportProjection(active);}
|
|
|
const job=await db.one('INSERT INTO xs_history_export_job(company,actor,scope,request_key,filters) VALUES($1,$2,$3,$4,$5::jsonb) RETURNING *',[company,actor,scope,key,JSON.stringify(filters)]);return lessonExportProjection(job);
|
|
|
});
|
|
|
}
|
|
|
-async function lessonExportOwned(context,input,db=Psql){
|
|
|
+async function lessonExportOwned(context,input,db=Psql,includeFile=false){
|
|
|
requireRole(context,['ops-manager','ops-staff','ops-auditor']);
|
|
|
- const job=(await db.query('SELECT * FROM xs_history_export_job WHERE id=$1 AND company=$2 AND actor=$3 AND scope=$4 AND expires_at>now()',[text(input.jobId),companyIdOf(context,input),text(context.current.id),lessonExportScope(context)]))[0];
|
|
|
+ const job=(await db.query('SELECT id,status,total,processed,error,snapshot_ready,expires_at'+(includeFile?',file_base64':'')+' FROM xs_history_export_job WHERE id=$1 AND company=$2 AND actor=$3 AND scope=$4 AND expires_at>now()',[text(input.jobId),companyIdOf(context,input),text(context.current.id),lessonExportScope(context)]))[0];
|
|
|
if(!job)fail(404,'未找到有效导出任务');return job;
|
|
|
}
|
|
|
async function lessonExportStatus(context,input){return lessonExportProjection(await lessonExportOwned(context,input));}
|
|
|
async function lessonExportCancel(context,input){const job=await lessonExportOwned(context,input);await Psql.none("UPDATE xs_history_export_job SET status='cancelled',lease=NULL,lease_until=NULL,file_base64=NULL WHERE id=$1 AND status IN('queued','running')",[job.id]);return lessonExportStatus(context,input);}
|
|
|
-async function lessonExportDownload(context,input){const job=await lessonExportOwned(context,input);if(job.status!=='ready'||!job.file_base64)fail(409,'导出尚未完成');return{fileName:'训练记录.xlsx',contentType:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',base64:job.file_base64};}
|
|
|
+async function lessonExportDownload(context,input){const job=await lessonExportOwned(context,input,Psql,true);if(job.status!=='ready'||!job.file_base64)fail(409,'导出尚未完成');return{fileName:'训练记录.xlsx',contentType:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',base64:job.file_base64};}
|
|
|
async function lessonExportClaim(context){
|
|
|
requireSuperAdmin(context);
|
|
|
return Psql.tx(async db=>{
|