businessFlowMiddlewareLogic.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. import { Middleware, PayloadAction } from '@reduxjs/toolkit';
  2. import prepare, { unprepare } from '../domain/exam/prepare';
  3. import { BusinessFlowState, setBusinessFlow } from './BusinessFlowSlice';
  4. import { setFeedbackOpen } from './exam/largeScreenSlice';
  5. import { setFeatureNotAvailableOpen } from './featureNotAvailableSlice';
  6. import { suspendOrCompleteStudy } from '@/API/patient/workActions';
  7. import { setExpDisable } from '@/API/exam/deviceActions';
  8. import { RootState } from './store';
  9. import { getQuota } from '@/API/security/quotaActions';
  10. import { showQuotaAlert } from './security/quotaModalSlice';
  11. import emitter from '@/utils/eventEmitter';
  12. import {
  13. setBodyPositions,
  14. transformWorksToBodyPositions,
  15. } from './exam/bodyPositionListSlice';
  16. import {
  17. worklistToProcess,
  18. prepareWorksForExam,
  19. } from '@/domain/patient/worklistToExam';
  20. import { executeRegisterLogic } from '@/domain/patient/registerLogic';
  21. import registerToExam from '@/domain/patient/registerToExam';
  22. import { addWork, clearWorks } from './exam/examWorksCacheSlice';
  23. import { resetViewerContainer } from './view/viewerContainerSlice';
  24. let continueBusinessFlow = '';
  25. const businessFlowMiddlewareLogic: Middleware =
  26. (store) => (next) => async (action: PayloadAction<string>) => {
  27. //const result = next(action);
  28. // console.log(
  29. // `[businessFlowMiddleware] Action dispatched: ${action.type} ${action.payload}`
  30. // );
  31. if (action.type !== setBusinessFlow.type) {
  32. return next(action); // Only handle setBusinessFlow actions
  33. }
  34. // 响应功能不可用后的继续操作
  35. if (action.payload === 'continueAfterFeatureNotAvailable') {
  36. console.log(
  37. `[businessFlowMiddleware] Continuing to: ${continueBusinessFlow}`
  38. );
  39. return next({ ...action, payload: continueBusinessFlow });
  40. }
  41. const state = store.getState().BusinessFlow as BusinessFlowState;
  42. const currentKey = state.currentKey;
  43. const targetKey = action.payload;
  44. console.log(`[businessFlowMiddleware] currentKey: ${currentKey}`);
  45. console.log(
  46. `[businessFlowMiddleware] Current business flow is now: ${state.currentKey} ; Last key was: ${state.lastKey}`
  47. );
  48. //进入检查
  49. if (action.payload === 'exam') {
  50. //进入检查前判断配额
  51. try {
  52. const quotaResponse = await getQuota();
  53. if (quotaResponse.data.available <= 0) {
  54. store.dispatch(showQuotaAlert());
  55. return;
  56. }
  57. if (quotaResponse.code !== '0x000000') {
  58. store.dispatch(showQuotaAlert());
  59. return;
  60. }
  61. } catch (error) {
  62. console.error('[businessFlowMiddleware] Quota check failed:', error);
  63. store.dispatch(showQuotaAlert());
  64. return;
  65. }
  66. if (currentKey === 'worklist' || currentKey === 'history') {
  67. const state = store.getState();
  68. // 获取选中的 study
  69. const selectedIds =
  70. currentKey === 'worklist'
  71. ? state.workSelection.selectedIds
  72. : state.historySelection.selectedIds;
  73. const entitiesData =
  74. currentKey === 'worklist'
  75. ? state.workEntities.data
  76. : state.historyEntities.data;
  77. if (selectedIds.length === 0) {
  78. console.warn('[businessFlowMiddleware] No study selected for exam');
  79. return; // 阻止进入 exam
  80. }
  81. const selectedWork = entitiesData.find(
  82. (work) => work.StudyID === selectedIds[0]
  83. );
  84. if (!selectedWork) {
  85. console.error('[businessFlowMiddleware] Selected study not found');
  86. return; // 阻止进入 exam
  87. }
  88. try {
  89. // 使用公共函数准备数据
  90. const [updatedTask] = await prepareWorksForExam(selectedWork);
  91. // 清空并更新缓存
  92. store.dispatch(clearWorks());
  93. store.dispatch(addWork(updatedTask));
  94. // 转换为 body positions
  95. const bodyPositions = await transformWorksToBodyPositions([
  96. updatedTask,
  97. ]);
  98. store.dispatch(setBodyPositions(bodyPositions));
  99. console.log(
  100. `[businessFlowMiddleware] Successfully prepared ${bodyPositions.length} body positions for exam`
  101. );
  102. } catch (error) {
  103. console.error(
  104. '[businessFlowMiddleware] Failed to prepare data for exam:',
  105. error
  106. );
  107. return; // 阻止进入 exam
  108. }
  109. }
  110. // 进入检查前,如果是从register来的,则判断数据合法性,执行注册等逻辑
  111. if (currentKey === 'register') {
  112. const systemMode = (store.getState() as RootState).systemMode.mode;
  113. // 如果是急诊模式,跳过验证(急诊流程已经完成注册)
  114. if (systemMode === 'Emergency') {
  115. console.log(
  116. '[businessFlowMiddleware] Emergency mode, skipping register validation'
  117. );
  118. // 急诊模式下仍需要准备 body positions
  119. try {
  120. const state = store.getState();
  121. const works = state.examWorksCache.works;
  122. const bodyPositions = await transformWorksToBodyPositions(works);
  123. store.dispatch(setBodyPositions(bodyPositions));
  124. } catch (error) {
  125. console.error(
  126. '[businessFlowMiddleware] Failed to prepare body positions:',
  127. error
  128. );
  129. return; // 阻止进入exam
  130. }
  131. } else {
  132. // 正常注册流程,需要验证
  133. const result = await executeRegisterLogic(store);
  134. if (!result.success) {
  135. console.log(
  136. '[businessFlowMiddleware] Register validation failed, blocking entry to exam'
  137. );
  138. return; // 阻止进入exam
  139. }
  140. try {
  141. await registerToExam(result.data!);
  142. const state = store.getState();
  143. const works = state.examWorksCache.works;
  144. await transformWorksToBodyPositions(works)
  145. .then((bodyPositions) => {
  146. store.dispatch(setBodyPositions(bodyPositions));
  147. })
  148. .catch((error) => {
  149. console.error(
  150. '[businessFlowMiddleware] Transform works to body positions failed:',
  151. error
  152. );
  153. return; // 阻止进入exam
  154. });
  155. } catch (_error) {
  156. console.log(
  157. '[businessFlowMiddleware] Register validation failed',
  158. _error
  159. );
  160. return; // 阻止进入exam
  161. }
  162. console.log(
  163. '[businessFlowMiddleware] Register validation succeeded, proceeding to exam'
  164. );
  165. }
  166. }
  167. prepare();
  168. return next(action);
  169. }
  170. if (action.payload === 'process') {
  171. if (isFromExamToView(action.payload, currentKey)) {
  172. //从检查进入图像处理,有可能是曝光导致的,怎么知道呢?看发生器状态
  173. if (store.getState().generatorMonitor.acquisitionState === 1) {
  174. //发生器正在采集
  175. console.log(
  176. `[businessFlowMiddleware] Exiting exam flow, but generator is still acquiring.`
  177. );
  178. return next(action); // 发生器正在采集,不能退出
  179. } else {
  180. //非曝光导致的从检查进入处理 // 说明从检查退出 , 执行清理
  181. console.log(
  182. `[businessFlowMiddleware] Exiting exam flow, last key was: ${state.lastKey}`
  183. );
  184. // 离开检查前,禁止发生器曝光
  185. try {
  186. await setExpDisable();
  187. } catch (error) {
  188. console.error('[businessFlowMiddleware] setExpDisable Failed to disable exposure:', error);
  189. }
  190. unprepare();
  191. }
  192. } else {
  193. //不是从检查进入图像处理,
  194. if (currentKey === 'worklist') {
  195. //从工单进入图像处理,准备数据
  196. const state = store.getState() as RootState;
  197. const selectedIds = state.workSelection.selectedIds;
  198. if (selectedIds.length > 0) {
  199. try {
  200. await worklistToProcess(selectedIds);
  201. console.log(
  202. `[businessFlowMiddleware] Successfully processed ${selectedIds.length} selected works from worklist to process`
  203. );
  204. } catch (error) {
  205. console.error(
  206. '[businessFlowMiddleware] Error processing works from worklist:',
  207. error
  208. );
  209. // 可以选择是否阻止进入process,这里我们选择继续
  210. return;
  211. }
  212. } else {
  213. console.warn(
  214. '[businessFlowMiddleware] No works selected in worklist to process'
  215. );
  216. }
  217. }
  218. if (currentKey === 'history') {
  219. //从历史进入图像处理,
  220. //从工单进入图像处理,准备数据
  221. const state = store.getState() as RootState;
  222. const selectedIds = state.historySelection.selectedIds;
  223. if (selectedIds.length > 0) {
  224. try {
  225. await worklistToProcess(selectedIds);
  226. console.log(
  227. `[businessFlowMiddleware] Successfully processed ${selectedIds.length} selected works from worklist to process`
  228. );
  229. } catch (error) {
  230. console.error(
  231. '[businessFlowMiddleware] Error processing works from worklist:',
  232. error
  233. );
  234. // 可以选择是否阻止进入process,这里我们选择继续
  235. return;
  236. }
  237. } else {
  238. console.warn(
  239. '[businessFlowMiddleware] No works selected in worklist to process'
  240. );
  241. }
  242. }
  243. return next(action);
  244. }
  245. }
  246. if (
  247. action.payload === 'archivelist' ||
  248. action.payload === 'bin' ||
  249. action.payload === 'outputlist' ||
  250. action.payload === 'print'
  251. ) {
  252. console.log(
  253. `[businessFlowMiddleware] Feature not available: ${action.payload}`
  254. );
  255. continueBusinessFlow = action.payload; // 保存要继续的业务流程
  256. store.dispatch(setFeatureNotAvailableOpen(true));
  257. return;
  258. }
  259. if (isLeavingProcess(action.payload, currentKey)) {
  260. // 说明从图像处理退出 , 执行清理
  261. console.log(
  262. `[businessFlowMiddleware] Leaving process flow, last key was: ${state.lastKey}`
  263. );
  264. // 重置 viewerContainerSlice 到初始状态
  265. store.dispatch(resetViewerContainer());
  266. }
  267. //退出检查的判断和退出处理的判断
  268. if ((
  269. isExitingExam(action.payload, currentKey) &&
  270. action.payload !== 'exitExamCompleted' &&
  271. action.payload !== 'exitExamSuspended' &&
  272. action.payload !== 'process' // 从检查进入处理,不需要检查曝光情况
  273. ) ||
  274. isLeavingProcess(action.payload, currentKey) &&
  275. action.payload !== 'exitExamCompleted' &&
  276. action.payload !== 'exitExamSuspended' &&
  277. action.payload !== 'exam'
  278. ){
  279. console.log(
  280. `[businessFlowMiddleware] Exiting exam flow to go to : ${action.payload}`
  281. );
  282. const exposureStatus = store.getState().bodyPositionList.exposureStatus;
  283. console.log(
  284. `[businessFlowMiddleware] Exposure status: ${exposureStatus}`
  285. );
  286. if (exposureStatus === 'Half Exposed') {
  287. store.dispatch(setFeedbackOpen(true)); //显示反馈框
  288. // store.dispatch({ type: 'SET_CONTINUE_BUSINESS_FLOW', payload: currentKey });
  289. continueBusinessFlow = action.payload; // 保存退出检查时,要去哪个业务流程
  290. return; //阻止退出exam 或者 process
  291. } else if (exposureStatus === 'Fully Exposed') {
  292. // Notify backend
  293. console.log(
  294. '[businessFlowMiddleware] Fully Exposed, notifying backend'
  295. );
  296. const studyId =
  297. (store.getState() as RootState)?.bodyPositionList?.selectedBodyPosition
  298. ?.work?.StudyID ?? '';
  299. suspendOrCompleteStudy(studyId, 'Completed').then(() => {
  300. console.log(`[businessFlowMiddleware] 全曝光,通知后端完成`)
  301. }).catch((error) => {
  302. console.log(`[businessFlowMiddleware] 全曝光,通知后端失败 ${error}`);
  303. });
  304. // Proceed with the action
  305. return next(action);
  306. } else if (exposureStatus === 'Not Exposed') {
  307. // Proceed with the action
  308. return next(action);
  309. }
  310. }
  311. //退出检查的中间过程
  312. if (action.payload === 'exitExamCompleted') {
  313. // Notify backend with different interfaces for completed and suspended
  314. console.log(
  315. `[businessFlowMiddleware] Notifying backend for ${action.payload}`
  316. );
  317. // 通知服务端。
  318. const studyId =
  319. (store.getState() as RootState)?.bodyPositionList?.selectedBodyPosition
  320. ?.work?.StudyID ?? '';
  321. if (!studyId) {
  322. console.error(
  323. '[businessFlowMiddleware] No study ID found for the selected body position.'
  324. );
  325. }
  326. await suspendOrCompleteStudy(studyId, 'Completed');
  327. return next({ ...action, payload: continueBusinessFlow });
  328. }
  329. if (action.payload === 'exitExamSuspended') {
  330. // Notify backend with different interfaces for completed and suspended
  331. const studyId =
  332. (store.getState() as RootState)?.bodyPositionList?.selectedBodyPosition
  333. ?.work?.StudyID ?? '';
  334. if (!studyId) {
  335. console.error(
  336. '[businessFlowMiddleware] No study ID found for the selected body position.'
  337. );
  338. }
  339. await suspendOrCompleteStudy(studyId, 'InProgress');
  340. console.log(
  341. `[businessFlowMiddleware] Notifying backend for ${action.payload}`
  342. );
  343. return next({ ...action, payload: continueBusinessFlow });
  344. }
  345. const result = next(action);
  346. // 发射业务流程切换完成事件
  347. emitter.emit('BUSINESS_FLOW_CHANGED', {
  348. from: currentKey,
  349. to: targetKey,
  350. timestamp: Date.now(),
  351. });
  352. console.log(
  353. `[businessFlowMiddleware] Emitted BUSINESS_FLOW_CHANGED event: ${currentKey} -> ${targetKey}`
  354. );
  355. return result;
  356. };
  357. /**
  358. * 从检查到图像处理
  359. */
  360. function isFromExamToView(currentAction: string, currentKey: string): boolean {
  361. return currentAction === 'process' && currentKey === 'exam';
  362. }
  363. function isExitingExam(currentAction: string, currentKey: string): boolean {
  364. console.log(
  365. `[businessFlowMiddleware] Checking if exiting exam: ${currentAction} vs ${currentKey}`
  366. );
  367. return currentAction !== 'exam' && currentKey === 'exam';
  368. }
  369. function isLeavingProcess(currentAction: string, currentKey: string): boolean {
  370. return currentAction !== 'process' && currentKey === 'process';
  371. }
  372. export default businessFlowMiddlewareLogic;