Compare commits

..

151 Commits

Author SHA1 Message Date
he b143ffbcfc 角色禁用不发邮件
continuous-integration/drone/push Build is passing Details
2025-06-20 16:57:59 +08:00
he 1d8be8c04b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-20 16:46:25 +08:00
he 139e1f8186 退出项目不发邮件 2025-06-20 16:46:24 +08:00
hang 81cb43d3a3 修改PD 逻辑
continuous-integration/drone/push Build is passing Details
2025-06-20 16:21:28 +08:00
hang c2db7a2c61 修改项目邮件配置
continuous-integration/drone/push Build is passing Details
2025-06-20 14:46:02 +08:00
hang 8cd19b929c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-20 14:25:43 +08:00
hang c62e7b13a9 修改搜索查询 2025-06-20 14:25:38 +08:00
he fc81738176 修改文档发布发邮件
continuous-integration/drone/push Build is passing Details
2025-06-20 14:10:33 +08:00
he b1e1840c87 修改阅片期计划的排序
continuous-integration/drone/push Build is passing Details
2025-06-20 14:03:37 +08:00
he b31d7ee762 阅片期计划可以选择基线
continuous-integration/drone/push Build is passing Details
2025-06-20 13:55:34 +08:00
he fb0c3879cf 邮件发送
continuous-integration/drone/push Build is passing Details
2025-06-20 11:53:25 +08:00
he 5ffe788a3b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-20 09:31:57 +08:00
he 056573874a 修改受试者内随机 2025-06-20 09:31:55 +08:00
hang c959a47fa6 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-19 17:35:27 +08:00
hang 41fe678708 CDISC导表库bug 2025-06-19 17:35:24 +08:00
he e7bbf09a50 修改稽查的单位翻译
continuous-integration/drone/push Build is passing Details
2025-06-19 16:33:21 +08:00
he 3bd16d7fce 邮件修改
continuous-integration/drone/push Build is passing Details
2025-06-19 16:18:01 +08:00
he db1d1b0f14 修改
continuous-integration/drone/push Build is passing Details
2025-06-19 15:08:24 +08:00
he ef980fdc68 邮件修改
continuous-integration/drone/push Build is passing Details
2025-06-19 14:31:41 +08:00
he 4de186a36b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-19 14:16:54 +08:00
he 0dd73991f0 邮件修改 2025-06-19 14:16:52 +08:00
hang 27e8e0e79c 修改子项排序
continuous-integration/drone/push Build is passing Details
2025-06-19 13:32:34 +08:00
hang fa53d6e88e 修改标准查询条件
continuous-integration/drone/push Build is passing Details
2025-06-19 11:37:54 +08:00
hang f3fc3fa4bf 修改邮件配置
continuous-integration/drone/push Build is passing Details
2025-06-19 11:26:17 +08:00
hang 476f692355 禁用人员系统查看文档
continuous-integration/drone/push Build is passing Details
2025-06-19 10:32:11 +08:00
hang 41b2a638fe 完全随机触发系统盲态名称
continuous-integration/drone/push Build is passing Details
2025-06-19 10:21:33 +08:00
hang c3631eee51 修改有序阅片生成任务bug
continuous-integration/drone/push Build is passing Details
2025-06-18 18:06:16 +08:00
hang 3ed74a578e 修改邮件配置为适配多标准
continuous-integration/drone/push Build is passing Details
2025-06-18 17:14:13 +08:00
hang 918728a42e 修改日志保留天数
continuous-integration/drone/push Build is passing Details
2025-06-18 13:57:50 +08:00
hang a8c0bef99e 关键图像-加上条件,已签名的才导出
continuous-integration/drone/push Build is passing Details
2025-06-18 13:05:53 +08:00
hang b3468a6d1b 修改影像下载bug
continuous-integration/drone/push Build is passing Details
2025-06-18 10:40:14 +08:00
hang dba8d3c964 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-18 10:33:29 +08:00
hang 8f5b024d3b 修改导表bug 2025-06-18 10:33:28 +08:00
he ea836d9349 修改阅片期计划生成裁判的逻辑
continuous-integration/drone/push Build is passing Details
2025-06-17 16:45:09 +08:00
he c32878911d 生成阅片期计划的时候 产生裁判 2025-06-17 16:45:07 +08:00
hang 0c4bd680dd 增加影像统计导出
continuous-integration/drone/push Build is passing Details
2025-06-17 16:30:13 +08:00
hang 90cd6ad0eb 真实随机阅片导表-1
continuous-integration/drone/push Build is passing Details
2025-06-17 09:59:22 +08:00
hang 013561541c 随机阅片导表
continuous-integration/drone/push Build is running Details
2025-06-17 09:58:53 +08:00
he f1f584c337 修改阅片期选择访视下拉框的数据
continuous-integration/drone/push Build is passing Details
2025-06-16 16:46:05 +08:00
he c443969f18 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-16 16:05:33 +08:00
hang 01621511f1 随机阅片排序-5
continuous-integration/drone/push Build is running Details
2025-06-16 16:03:25 +08:00
he 0740b38b10 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8 2025-06-16 15:59:03 +08:00
he ebd70e2202 生成了裁判任务 阅片期不让删除和修改 2025-06-16 15:59:00 +08:00
hang 80c026d5b2 修改CDISC 导出bug
continuous-integration/drone/push Build is passing Details
2025-06-16 15:58:33 +08:00
hang 8e2fbf06c5 随机阅片-NextTask-5
continuous-integration/drone/push Build is passing Details
2025-06-16 15:18:49 +08:00
hang daee20e309 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-16 15:00:48 +08:00
hang 3ba7481df8 末次访视的展示+ 展示基线选择阅片期 2025-06-16 15:00:37 +08:00
he 1e299e3f9e 添加阅片期 产生裁判
continuous-integration/drone/push Build is running Details
2025-06-16 14:59:23 +08:00
he 05cc46a6d6 修改标记信息
continuous-integration/drone/push Build is passing Details
2025-06-13 16:01:23 +08:00
he f1b943b6b0 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-13 14:40:47 +08:00
he 9f00c1c49b 修改阅片标记信息 2025-06-13 14:40:47 +08:00
hang b1fd623065 随机阅片NextTask-4
continuous-integration/drone/push Build is passing Details
2025-06-13 14:33:15 +08:00
hang 7683866718 随机阅片序号3
continuous-integration/drone/push Build is passing Details
2025-06-13 11:49:14 +08:00
hang d58395c25e 随机阅片序号2
continuous-integration/drone/push Build is passing Details
2025-06-13 11:38:09 +08:00
hang 15dd21451e 随机阅片随机序号
continuous-integration/drone/push Build is passing Details
2025-06-13 10:46:29 +08:00
hang 483c7c68e8 修改重置阅片稽查3
continuous-integration/drone/push Build is passing Details
2025-06-13 09:53:59 +08:00
hang 738ad29c0b 修改阅片重置稽查
continuous-integration/drone/push Build is passing Details
2025-06-12 17:55:02 +08:00
hang f87b57ecc5 修改重置阅片稽查
continuous-integration/drone/push Build is passing Details
2025-06-12 17:40:50 +08:00
hang fccd704659 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-12 16:36:37 +08:00
hang e5b41735ec 确认浏览临床数据 2025-06-12 16:36:29 +08:00
he 07b496eb67 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-11 15:26:20 +08:00
he 762e46480c 删除标记 2025-06-11 15:26:18 +08:00
hang a7ad22ed28 修改关键图下载
continuous-integration/drone/push Build is passing Details
2025-06-11 14:35:56 +08:00
hang 8610a627ea 修改导出影响接口
continuous-integration/drone/push Build is passing Details
2025-06-11 14:03:50 +08:00
hang 302658d467 修改影响导出列表
continuous-integration/drone/push Build is passing Details
2025-06-11 13:46:13 +08:00
hang 97c882b0b2 肿瘤学稽查修改
continuous-integration/drone/push Build is passing Details
2025-06-11 09:33:24 +08:00
hang d711bb4b7f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-11 09:27:24 +08:00
hang 1a4ca6e36b 增加进入阅片中稽查 2025-06-11 09:27:18 +08:00
he aaac85b7b2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-10 17:16:59 +08:00
he cb3e4bc671 添加数据 2025-06-10 17:16:58 +08:00
hang 9f187e4b36 稽查 申请PM 重阅,有SPM 判断
continuous-integration/drone/push Build is passing Details
2025-06-10 15:11:41 +08:00
hang 663ec11c8d 稽查修改,区分标识
continuous-integration/drone/push Build is passing Details
2025-06-10 13:44:27 +08:00
he 122447a621 修改
continuous-integration/drone/push Build is passing Details
2025-06-10 10:31:09 +08:00
he 31bd3c646b 修改
continuous-integration/drone/push Build is passing Details
2025-06-10 10:11:16 +08:00
he f53fc409cb 修改
continuous-integration/drone/push Build is passing Details
2025-06-10 10:06:11 +08:00
he 7f4357de68 修改
continuous-integration/drone/push Build is passing Details
2025-06-10 09:37:26 +08:00
he 23fa689498 修改
continuous-integration/drone/push Build is passing Details
2025-06-10 09:31:55 +08:00
he e564debbab Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-10 09:13:49 +08:00
he 04aa2c79f6 修改 2025-06-10 09:13:48 +08:00
hang 68d66523e8 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-09 17:38:44 +08:00
hang 549f394d43 增加稽查标识 2025-06-09 17:38:41 +08:00
he 5a83bf9221 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-09 17:01:32 +08:00
he 65bffa26b7 修改定时发送 2025-06-09 17:01:30 +08:00
hang 39a6f6a9dc Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-09 15:26:22 +08:00
hang fc02428435 导表修改 2025-06-09 15:26:20 +08:00
he 4262189508 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-09 15:21:34 +08:00
he 6dd6a5f696 修改 2025-06-09 15:21:33 +08:00
hang e03f2e35b5 修改到处临床数据bug
continuous-integration/drone/push Build is running Details
2025-06-09 15:20:02 +08:00
hang ea4a3db145 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-09 14:57:02 +08:00
hang f4ac12d4f8 修改导表+界面展示 2025-06-09 14:57:01 +08:00
he a2afe4f6e4 修改
continuous-integration/drone/push Build is passing Details
2025-06-09 14:44:17 +08:00
he ef31afa1d8 修改
continuous-integration/drone/push Build is passing Details
2025-06-09 14:14:41 +08:00
he d9fda60b99 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-09 13:39:48 +08:00
he 6508698bda 添加字段 2025-06-09 13:39:47 +08:00
hang 734675a1a5 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-09 10:31:06 +08:00
hang d2697d5133 检查部位、检查技术、检查名称在确认提交时,增加校验 2025-06-09 10:31:02 +08:00
he 1c7f4eac90 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-09 10:19:03 +08:00
he 6dcaa953d4 添加历史记录 2025-06-09 10:19:00 +08:00
hang 1f67d24abc 修改部位使用
continuous-integration/drone/push Build is passing Details
2025-06-06 14:24:25 +08:00
hang c7a310c1ad Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-06 11:12:51 +08:00
hang 578aaa467c 邮件修改+影像导出 2025-06-06 11:12:46 +08:00
he 82a7c4de29 报错修改
continuous-integration/drone/push Build is passing Details
2025-06-05 16:19:59 +08:00
he fe4df21a68 稽查添加
continuous-integration/drone/push Build is passing Details
2025-06-05 15:19:39 +08:00
he 9cb6fb2a92 添加验证
continuous-integration/drone/push Build is passing Details
2025-06-05 14:52:07 +08:00
he 37cfe07ea6 添加字段
continuous-integration/drone/push Build is passing Details
2025-06-05 09:20:47 +08:00
he 52ffdac948 修改
continuous-integration/drone/push Build is passing Details
2025-06-04 17:09:23 +08:00
he 83a15a0b47 修改
continuous-integration/drone/push Build is passing Details
2025-06-04 16:14:24 +08:00
he 1952a108a9 修改
continuous-integration/drone/push Build is passing Details
2025-06-04 16:00:32 +08:00
he ccff36507f 修改
continuous-integration/drone/push Build is passing Details
2025-06-04 15:51:27 +08:00
he 285fd0e1bb 修改映射
continuous-integration/drone/push Build is passing Details
2025-06-04 15:17:58 +08:00
he 918aafa7e5 测量值可以为空
continuous-integration/drone/push Build is passing Details
2025-06-04 14:24:26 +08:00
he 60accb2f87 修改
continuous-integration/drone/push Build is passing Details
2025-06-04 14:21:56 +08:00
he 018c9afee0 添加表
continuous-integration/drone/push Build is passing Details
2025-06-04 14:13:38 +08:00
he 1b9351b23f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-04 13:36:31 +08:00
he 86ea3373fc 修改 2025-06-04 13:36:30 +08:00
hang dd3b43da7b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-04 13:15:22 +08:00
hang 3151e42e78 访视裁判,裁判选择标记bug 2025-06-04 13:15:21 +08:00
he 5d8f94ef65 修改
continuous-integration/drone/push Build is passing Details
2025-06-04 10:51:32 +08:00
he 26acc94172 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running Details
2025-06-04 10:50:28 +08:00
he 5c663ec3e9 添加获取模块类型列表 2025-06-04 10:50:28 +08:00
hang 5c0917f14e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-04 10:20:52 +08:00
hang 74230da400 修改CDISC 多表导出 2025-06-04 10:20:47 +08:00
he df104ec527 验证修改
continuous-integration/drone/push Build is passing Details
2025-06-04 09:42:00 +08:00
he 5cdef2051a 修改
continuous-integration/drone/push Build is passing Details
2025-06-03 17:21:07 +08:00
he 470d05131e 修改
continuous-integration/drone/push Build is passing Details
2025-06-03 16:57:04 +08:00
he 5e046b847d Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-06-03 16:12:11 +08:00
he 8b6fcf9ae1 添加字段 2025-06-03 16:12:09 +08:00
hang 4dfa1e9812 表格问题竖列留底
continuous-integration/drone/push Build is passing Details
2025-06-03 16:02:45 +08:00
he a05cb71e0d 修改
continuous-integration/drone/push Build is passing Details
2025-06-03 13:21:56 +08:00
he 137b18446c 修改
continuous-integration/drone/push Build is passing Details
2025-06-03 12:46:51 +08:00
he e44478dafe 修改
continuous-integration/drone/push Build is passing Details
2025-06-03 11:45:57 +08:00
he 0a5a36adb3 添加表
continuous-integration/drone/push Build is passing Details
2025-06-03 10:43:31 +08:00
he 91e4553795 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-05-30 09:03:29 +08:00
he f504cfee9e 修改 2025-05-30 09:03:26 +08:00
hang 2e7e3acf68 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-05-29 17:56:20 +08:00
hang c00b98daf8 心动超声后端处理下载,上传,预备 2025-05-29 17:56:19 +08:00
he 235f420e97 添加字段
continuous-integration/drone/push Build is passing Details
2025-05-29 11:37:35 +08:00
he da15b352a1 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-05-29 11:30:28 +08:00
he 9857b1ae92 添加发送邮件功能 2025-05-29 11:30:24 +08:00
hang 9e83f8df36 返回EnrollId
continuous-integration/drone/push Build is passing Details
2025-05-28 11:35:05 +08:00
he ae431df9b5 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-05-27 13:30:14 +08:00
he e5592c5bfa 修改 2025-05-27 13:30:11 +08:00
hang 1b2bca9f1e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-05-27 13:15:41 +08:00
hang 4235e6025a 下载项目影像修改 2025-05-27 13:15:38 +08:00
he 209ce439c2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-05-27 10:27:36 +08:00
he eabf8f4532 添加字段 2025-05-27 10:27:33 +08:00
hang 07ddb7b234 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details
2025-05-26 16:37:44 +08:00
hang 1f157bd1a2 项目下载影像 2025-05-26 16:37:41 +08:00
he 0c28f85f49 修改按照标记时间排序
continuous-integration/drone/push Build is passing Details
2025-05-26 14:11:54 +08:00
hang 44536b803f 裁判阅片明细表bug
continuous-integration/drone/push Build is passing Details
2025-05-23 19:13:23 +08:00
hang 750c31a159 修改导出bug
continuous-integration/drone/push Build is passing Details
2025-05-23 19:00:33 +08:00
106 changed files with 162785 additions and 318 deletions

View File

@ -0,0 +1,37 @@
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
namespace IRaCIS.Core.SCP.Filter
{
public class ModelActionFilter : ActionFilterAttribute, IActionFilter
{
public IStringLocalizer _localizer;
public ModelActionFilter(IStringLocalizer localizer)
{
_localizer = localizer;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var validationErrors = context.ModelState
.Keys
.SelectMany(k => context.ModelState[k]!.Errors)
.Select(e => e.ErrorMessage)
.ToArray();
//---提供给接口的参数无效。
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject( validationErrors)));
}
}
}
}

View File

@ -0,0 +1,61 @@
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.SCP.Filter
{
public class ProjectExceptionFilter : Attribute, IExceptionFilter
{
private readonly ILogger<ProjectExceptionFilter> _logger;
public IStringLocalizer _localizer;
public ProjectExceptionFilter(IStringLocalizer localizer, ILogger<ProjectExceptionFilter> logger)
{
_logger = logger;
_localizer = localizer;
}
public void OnException(ExceptionContext context)
{
//context.ExceptionHandled;//记录当前这个异常是否已经被处理过了
if (!context.ExceptionHandled)
{
if (context.Exception.GetType().Name == "DbUpdateConcurrencyException")
{
//---并发更新,当前不允许该操作
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ProjectException_ConcurrentUpdateNotAllowed"] + context.Exception.Message));
}
if (context.Exception.GetType() == typeof(BusinessValidationFailedException))
{
var error = context.Exception as BusinessValidationFailedException;
context.Result = new JsonResult(ResponseOutput.NotOk(context.Exception.Message, error!.Code));
}
else if(context.Exception.GetType() == typeof(QueryBusinessObjectNotExistException))
{
context.Result = new JsonResult(ResponseOutput.NotOk( context.Exception.Message, ApiResponseCodeEnum.DataNotExist));
}
else
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (context.Exception.InnerException is null ? (context.Exception.Message /*+ context.Exception.StackTrace*/)
: (context.Exception.InnerException?.Message /*+ context.Exception.InnerException?.StackTrace*/)), ApiResponseCodeEnum.ProgramException));
}
_logger.LogError(context.Exception.InnerException is null ? (context.Exception.Message + context.Exception.StackTrace) : (context.Exception.InnerException?.Message + context.Exception.InnerException?.StackTrace));
}
else
{
//继续
}
context.ExceptionHandled = true;//标记当前异常已经被处理过了
}
}
}

View File

@ -0,0 +1,120 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
namespace IRaCIS.Core.Application.Service.BusinessFilter
{
/// <summary>
/// 统一返回前端数据包装之前在控制器包装现在修改为动态Api 在ResultFilter这里包装减少重复冗余代码
/// by zhouhang 2021.09.12 周末
/// </summary>
public class UnifiedApiResultFilter : Attribute, IAsyncResultFilter
{
/// <summary>
/// 异步版本
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.Result is ObjectResult objectResult)
{
var statusCode = objectResult.StatusCode ?? context.HttpContext.Response.StatusCode;
//是200 并且没有包装 那么包装结果
if (statusCode == 200 && !(objectResult.Value is IResponseOutput))
{
//if (objectResult.Value == null)
//{
// var apiResponse = ResponseOutput.DBNotExist();
// objectResult.Value = apiResponse;
// objectResult.DeclaredType = apiResponse.GetType();
//}
//else
//{
var type = objectResult.Value?.GetType();
if ( type!=null&& type.IsGenericType&&(type.GetGenericTypeDefinition()==typeof(ValueTuple<,>)|| type.GetGenericTypeDefinition()==typeof(Tuple<,>)))
{
//报错
//var tuple = (object, object))objectResult.Value;
//var (val1, val2) = ((dynamic, dynamic))objectResult.Value;
//var apiResponse = ResponseOutput.Ok(val1, val2);
//OK
var tuple = (dynamic)objectResult.Value;
var apiResponse = ResponseOutput.Ok(tuple.Item1, tuple.Item2);
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
else
{
var apiResponse = ResponseOutput.Ok(objectResult.Value);
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
//}
}
//如果不是200 是IResponseOutput 不处理
else if (statusCode != 200 && (objectResult.Value is IResponseOutput))
{
}
else if(statusCode != 200&&!(objectResult.Value is IResponseOutput))
{
//---程序错误,请联系开发人员。
var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError"));
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
}
await next.Invoke();
}
public static bool IsTupleType(Type type, bool checkBaseTypes = false)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (type == typeof(Tuple))
return true;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(Tuple<>)
|| genType == typeof(Tuple<,>)
|| genType == typeof(Tuple<,>))
return true;
}
if (!checkBaseTypes)
break;
type = type.BaseType;
}
return false;
}
}
}

View File

@ -0,0 +1,57 @@
using Autofac;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.AspNetCore.Http;
using Panda.DynamicWebApi;
using System;
using System.Linq;
using System.Reflection;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Application.Service;
using AutoMapper;
using IRaCIS.Core.SCP.Service;
namespace IRaCIS.Core.SCP
{
// ReSharper disable once IdentifierTypo
public class AutofacModuleSetup : Autofac.Module
{
protected override void Load(ContainerBuilder containerBuilder)
{
#region byzhouhang 20210917 此处注册泛型仓储 可以减少Domain层 和Infra.EFcore 两层 空的仓储接口定义和 仓储文件定义
containerBuilder.RegisterGeneric(typeof(Repository<>))
.As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
containerBuilder.RegisterType<Repository>().As<IRepository>().InstancePerLifetimeScope();
#endregion
#region 指定控制器也由autofac 来进行实例获取 https://www.cnblogs.com/xwhqwer/p/15320838.html
//获取所有控制器类型并使用属性注入
containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
.Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
.PropertiesAutowired();
#endregion
Assembly application = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + typeof(BaseService).Assembly.GetName().Name+".dll");
containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
.PropertiesAutowired().AsImplementedInterfaces();
//containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
//containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
}
}
}

View File

@ -0,0 +1,64 @@
using EntityFramework.Exceptions.SqlServer;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using Medallion.Threading.SqlServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IRaCIS.Core.SCP
{
public static class EFSetup
{
public static void AddEFSetup( this IServiceCollection services, IConfiguration configuration)
{
services.AddHttpContextAccessor();
services.AddScoped<IUserInfo, UserInfo>();
services.AddScoped<ISaveChangesInterceptor, AuditEntityInterceptor>();
//这个注入没有成功--注入是没问题的构造函数也只是支持参数就好错在注入的地方不能写DbContext
//Web程序中通过重用池中DbContext实例可提高高并发场景下的吞吐量 这在概念上类似于ADO.NET Provider原生的连接池操作方式具有节省DbContext实例化成本的优点
services.AddDbContext<IRaCISDBContext>((sp, options) =>
{
// 在控制台
//public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
var logFactory = LoggerFactory.Create(builder => { builder.AddDebug(); });
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value,
contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
options.UseLoggerFactory(logFactory);
options.UseExceptionProcessor();
options.EnableSensitiveDataLogging();
options.AddInterceptors(new QueryWithNoLockDbCommandInterceptor());
options.AddInterceptors(sp.GetServices<ISaveChangesInterceptor>());
options.UseProjectables();
});
//// Register an additional context factory as a Scoped service, which gets a pooled context from the Singleton factory we registered above,
//services.AddScoped<IRaCISDBScopedFactory>();
//// Finally, arrange for a context to get injected from our Scoped factory:
//services.AddScoped(sp => sp.GetRequiredService<IRaCISDBScopedFactory>().CreateDbContext());
//注意区分 easy caching 也有 IDistributedLockProvider
services.AddSingleton<IDistributedLockProvider>(sp =>
{
//var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
return new SqlDistributedSynchronizationProvider(configuration.GetSection("ConnectionStrings:RemoteNew").Value);
});
}
}
}

View File

@ -0,0 +1,59 @@

using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
namespace IRaCIS.Core.SCP
{
public static class NewtonsoftJsonSetup
{
public static void AddNewtonsoftJsonSetup(this IMvcBuilder builder, IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<IOSSService,OSSService>();
builder.AddNewtonsoftJson(options =>
{
//options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
// 忽略循环引用
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
//处理返回给前端 可空类型 给出默认值 比如in? 为null 设置 默认值0
options.SerializerSettings.ContractResolver = new NullToEmptyStringResolver(); //new DefaultContractResolver();// new NullToEmptyStringResolver();
// 设置时间格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
//options.SerializerSettings.Converters.Add(new JSONCustomDateConverter()) ;
//options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
})
.AddControllersAsServices()//动态webApi属性注入需要
.ConfigureApiBehaviorOptions(o =>
{
o.SuppressModelStateInvalidFilter = true; //自己写验证
});
Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
{
//日期类型默认格式化处理
setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";
setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
return setting;
});
}
}
}

View File

@ -0,0 +1,36 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
namespace IRaCIS.Core.SCP
{
public class NullToEmptyStringResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
var list= type.GetProperties()
.Select(p =>
{
var jp = base.CreateProperty(p, memberSerialization);
jp.ValueProvider = new NullToEmptyStringValueProvider(p);
return jp;
}).ToList();
var uu = list.Select(t => t.PropertyName).ToList();
//获取复杂对象属性
properties = properties.TakeWhile(t => !uu.Contains(t.PropertyName)).ToList();
list.AddRange(properties);
return list;
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Reflection;
using Newtonsoft.Json.Serialization;
namespace IRaCIS.Core.SCP
{
public class NullToEmptyStringValueProvider : IValueProvider
{
PropertyInfo _MemberInfo;
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
{
_MemberInfo = memberInfo;
}
public object GetValue(object target)
{
object result = _MemberInfo.GetValue(target);
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { };
//else if (_MemberInfo.PropertyType == typeof(Nullable<Int32>) && result == null) result = 0;
else if (_MemberInfo.PropertyType == typeof(Nullable<Decimal>) && result == null) result = 0.00M;
return result;
}
public void SetValue(object target, object value)
{
if(_MemberInfo.PropertyType == typeof(string))
{
//去掉前后空格
_MemberInfo.SetValue(target, value==null?string.Empty: value.ToString()==string.Empty? value:value.ToString().Trim());
}
else
{
_MemberInfo.SetValue(target, value);
}
}
}
}

View File

@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" />
<PackageReference Include="DistributedLock.Core" Version="1.0.8" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.1" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.1" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Minio" Version="6.0.4" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IRaCIS.Core.Infra.EFCore\IRaCIS.Core.Infra.EFCore.csproj" />
<ProjectReference Include="..\IRaCIS.Core.Infrastructure\IRaCIS.Core.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Helper\" />
</ItemGroup>
</Project>

186
IRC.Core.Dicom/Program.cs Normal file
View File

@ -0,0 +1,186 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using AutoMapper.EquivalencyExpression;
using FellowOakDicom;
using FellowOakDicom.Imaging;
using FellowOakDicom.Imaging.NativeCodec;
using FellowOakDicom.Network;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.SCP;
using IRaCIS.Core.SCP.Filter;
using IRaCIS.Core.SCP.Service;
using MassTransit;
using MassTransit.NewIdProviders;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Panda.DynamicWebApi;
using Serilog;
using Serilog.Events;
using System.Runtime.InteropServices;
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
var enviromentName = config["ASPNETCORE_ENVIRONMENT"];
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = enviromentName
});
#region 主机配置
NewId.SetProcessIdProvider(new CurrentProcessIdProvider());
builder.Configuration.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{enviromentName}.json", false, true);
builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterModule<AutofacModuleSetup>();
})
.UseSerilog();
#endregion
#region 配置服务
var _configuration = builder.Configuration;
//健康检查
builder.Services.AddHealthChecks();
//本地化
builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources");
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
builder.Services.AddControllers(options =>
{
options.Filters.Add<ModelActionFilter>();
options.Filters.Add<ProjectExceptionFilter>();
options.Filters.Add<UnitOfWorkFilter>();
})
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
builder.Services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
builder.Services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
builder.Services.AddOptions().Configure<DicomSCPServiceOption>(_configuration.GetSection("DicomSCPServiceConfig"));
//动态WebApi + UnifiedApiResultFilter 省掉控制器代码
//动态webApi 目前存在的唯一小坑是生成api上服务上的动态代理AOP失效 间接掉用不影响
builder.Services
.AddDynamicWebApi(dynamicWebApiOption =>
{
//默认是 api
dynamicWebApiOption.DefaultApiPrefix = "";
//首字母小写
dynamicWebApiOption.GetRestFulActionName = (actionName) => char.ToLower(actionName[0]) + actionName.Substring(1);
//删除 Service后缀
dynamicWebApiOption.RemoveControllerPostfixes.Add("Service");
});
//AutoMapper
builder.Services.AddAutoMapper(automapper =>
{
automapper.AddCollectionMappers();
}, typeof(BaseService).Assembly);
//EF ORM QueryWithNoLock
builder.Services.AddEFSetup(_configuration);
builder.Services.AddMediator(cfg =>
{
});
//转发头设置 获取真实IP
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
builder.Services.AddFellowOakDicom().AddTranscoderManager<NativeTranscoderManager>()
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
.AddImageManager<ImageSharpImageManager>();
#endregion
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
app.UseAuthorization();
app.MapControllers();
#region 日志
Log.Logger = new LoggerConfiguration()
//.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
#endregion
#region 运行环境 部署平台
Log.Logger.Warning($"当前环境:{enviromentName}");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Log.Logger.Warning($"当前部署平台环境windows");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Log.Logger.Warning($"当前部署平台环境linux");
}
else
{
Log.Logger.Warning($"当前部署平台环境OSX or FreeBSD");
}
#endregion
DicomSetupBuilder.UseServiceProvider(app.Services);
var logger = app.Services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>();
var server = DicomServerFactory.Create<CStoreSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services, logger: logger);
app.Run();

View File

@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:11224",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5127",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,111 @@
using AutoMapper;
using IRaCIS.Core.Application.Service.BusinessFilter;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Localization;
using Panda.DynamicWebApi;
using Panda.DynamicWebApi.Attributes;
using System.Diagnostics.CodeAnalysis;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.Domain.Models;
namespace IRaCIS.Core.SCP.Service
{
#pragma warning disable CS8618
#region 非泛型版本
[Authorize, DynamicWebApi, UnifiedApiResultFilter]
public class BaseService : IBaseService, IDynamicWebApi
{
public IMapper _mapper { get; set; }
public IUserInfo _userInfo { get; set; }
public IStringLocalizer _localizer { get; set; }
public IWebHostEnvironment _hostEnvironment { get; set; }
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
{
return new ResponseOutput<string>()
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
}
}
public interface IBaseService
{
[MemberNotNull(nameof(_mapper))]
public IMapper _mapper { get; set; }
[MemberNotNull(nameof(_userInfo))]
public IUserInfo _userInfo { get; set; }
[MemberNotNull(nameof(_localizer))]
public IStringLocalizer _localizer { get; set; }
[MemberNotNull(nameof(_hostEnvironment))]
public IWebHostEnvironment _hostEnvironment { get; set; }
}
#endregion
#region 泛型版本测试
public interface IBaseServiceTest<T> where T : Entity
{
[MemberNotNull(nameof(_mapper))]
public IMapper _mapper { get; set; }
[MemberNotNull(nameof(_userInfo))]
public IUserInfo _userInfo { get; set; }
[MemberNotNull(nameof(_localizer))]
public IStringLocalizer _localizer { get; set; }
}
[Authorize, DynamicWebApi, UnifiedApiResultFilter]
public class BaseServiceTest<T> : IBaseServiceTest<T>, IDynamicWebApi where T : Entity
{
public IMapper _mapper { get; set; }
public IUserInfo _userInfo { get; set; }
public IStringLocalizer _localizer { get; set; }
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
{
return new ResponseOutput<string>()
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
}
}
#endregion
}

View File

@ -0,0 +1,376 @@
using FellowOakDicom.Network;
using FellowOakDicom;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using IRaCIS.Core.Domain.Share;
using Serilog;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.Options;
using System.Data;
using FellowOakDicom.Imaging;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using IRaCIS.Core.Infrastructure;
namespace IRaCIS.Core.SCP.Service
{
public class DicomSCPServiceOption
{
public List<string> CalledAEList { get; set; }
public string ServerPort { get; set; }
}
public class CStoreSCPService : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider
{
private IServiceProvider _serviceProvider { get; set; }
private List<Guid> _SCPStudyIdList { get; set; } = new List<Guid>();
private SCPImageUpload _upload { get; set; }
private Guid _trialId { get; set; }
private Guid _trialSiteId { get; set; }
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
{
DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ExplicitVRBigEndian,
DicomTransferSyntax.ImplicitVRLittleEndian
};
private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[]
{
// Lossless
DicomTransferSyntax.JPEGLSLossless, //1.2.840.10008.1.2.4.80
DicomTransferSyntax.JPEG2000Lossless, //1.2.840.10008.1.2.4.90
DicomTransferSyntax.JPEGProcess14SV1, //1.2.840.10008.1.2.4.70
DicomTransferSyntax.JPEGProcess14, //1.2.840.10008.1.2.4.57 JPEG Lossless, Non-Hierarchical (Process 14)
DicomTransferSyntax.RLELossless, //1.2.840.10008.1.2.5
// Lossy
DicomTransferSyntax.JPEGLSNearLossless,//1.2.840.10008.1.2.4.81"
DicomTransferSyntax.JPEG2000Lossy, //1.2.840.10008.1.2.4.91
DicomTransferSyntax.JPEGProcess1, //1.2.840.10008.1.2.4.50
DicomTransferSyntax.JPEGProcess2_4, //1.2.840.10008.1.2.4.51
// Uncompressed
DicomTransferSyntax.ExplicitVRLittleEndian, //1.2.840.10008.1.2.1
DicomTransferSyntax.ExplicitVRBigEndian, //1.2.840.10008.1.2.2
DicomTransferSyntax.ImplicitVRLittleEndian //1.2.840.10008.1.2
};
public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies, IServiceProvider injectServiceProvider)
: base(stream, fallbackEncoding, log, dependencies)
{
_serviceProvider = injectServiceProvider.CreateScope().ServiceProvider;
}
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
{
_upload = new SCPImageUpload() { StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost };
Log.Logger.Warning($"接收到来自{association.CallingAE}的连接");
//_serviceProvider = (IServiceProvider)this.UserState;
var _trialDicomAERepository = _serviceProvider.GetService<IRepository<TrialDicomAE>>();
var trialDicomAEList = _trialDicomAERepository.Select(t => new { t.CalledAE, t.TrialId }).ToList();
var trialCalledAEList = trialDicomAEList.Select(t => t.CalledAE).ToList();
Log.Logger.Information("当前系统配置:", string.Join('|', trialDicomAEList));
var findCalledAE = trialDicomAEList.Where(t => t.CalledAE == association.CalledAE).FirstOrDefault();
var isCanReceiveIamge = false;
if (findCalledAE != null)
{
_trialId = findCalledAE.TrialId;
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
if (findTrialSiteAE != null)
{
_trialSiteId = findTrialSiteAE.TrialSiteId;
isCanReceiveIamge = true;
}
}
if (association.CallingAE == "test-callingAE")
{
isCanReceiveIamge = true;
}
if (!trialCalledAEList.Contains(association.CalledAE) || isCanReceiveIamge == false)
{
Log.Logger.Warning($"拒绝CallingAE:{association.CallingAE} CalledAE:{association.CalledAE}的连接");
return SendAssociationRejectAsync(
DicomRejectResult.Permanent,
DicomRejectSource.ServiceUser,
DicomRejectReason.CalledAENotRecognized);
}
foreach (var pc in association.PresentationContexts)
{
if (pc.AbstractSyntax == DicomUID.Verification)
{
pc.AcceptTransferSyntaxes(_acceptedTransferSyntaxes);
}
else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None)
{
pc.AcceptTransferSyntaxes(_acceptedImageTransferSyntaxes);
}
}
return SendAssociationAcceptAsync(association);
}
public async Task OnReceiveAssociationReleaseRequestAsync()
{
await DataMaintenanceAsaync();
//记录监控
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _SCPStudyIdList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
await _SCPImageUploadRepository.AddAsync(_upload, true);
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
await SendAssociationReleaseResponseAsync();
}
private async Task DataMaintenanceAsaync()
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}传输结束开始维护数据处理检查Modality");
//处理检查Modality
var _dictionaryRepository = _serviceProvider.GetService<IRepository<Dictionary>>();
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
var dicModalityList = _dictionaryRepository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList();
var seriesModalityList = _seriesRepository.Where(t => _SCPStudyIdList.Contains(t.StudyId)).Select(t => new { SCPStudyId = t.StudyId, t.Modality }).ToList();
foreach (var g in seriesModalityList.GroupBy(t => t.SCPStudyId))
{
var modality = string.Join('、', g.Select(t => t.Modality).Distinct().ToList());
//特殊逻辑
var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty;
if (modality == "MR")
{
modalityForEdit = "MRI";
}
if (modality == "PT")
{
modalityForEdit = "PET";
}
if (modality == "PT、CT" || modality == "CT、PT")
{
modalityForEdit = "PET-CT";
}
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == g.Key, u => new SCPStudy() { Modalities = modality, ModalityForEdit = modalityForEdit });
}
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}维护数据结束");
}
public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}接收中断,中断原因:{source.ToString() + reason.ToString()}");
/* nothing to do here */
}
public async void OnConnectionClosed(Exception exception)
{
/* nothing to do here */
//奇怪的bug 上传的时候用王捷修改的影像会关闭重新连接导致检查id 丢失,然后状态不一致
if (exception == null)
{
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
}
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
}
public async Task<DicomCStoreResponse> OnCStoreRequestAsync(DicomCStoreRequest request)
{
string studyInstanceUid = request.Dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID);
//Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, _trialId.ToString());
var ossService = _serviceProvider.GetService<IOSSService>();
var dicomArchiveService = _serviceProvider.GetService<IDicomArchiveService>();
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var storeRelativePath = string.Empty;
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
long fileSize = 0;
try
{
using (MemoryStream ms = new MemoryStream())
{
await request.File.SaveAsync(ms);
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
fileSize = ms.Length;
}
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
}
catch (Exception ec)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 上传异常 {ec.Message}");
}
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
using (await @lock.AcquireAsync())
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
if (!_SCPStudyIdList.Contains(scpStudyId))
{
_SCPStudyIdList.Add(scpStudyId);
}
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
//没有缩略图
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
{
// 生成缩略图
using (var memoryStream = new MemoryStream())
{
DicomImage image = new DicomImage(request.Dataset);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(memoryStream, new JpegEncoder());
// 上传缩略图到 OSS
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, seriesId.ToString() + ".preview.jpg", false);
Console.WriteLine(seriesPath + " Id: " + seriesId);
series.ImageResizePath = seriesPath;
}
}
await _seriesRepository.SaveChangesAsync();
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
return new DicomCStoreResponse(request, DicomStatus.Success);
}
public Task OnCStoreRequestExceptionAsync(string tempFileName, Exception e)
{
// let library handle logging and error response
return Task.CompletedTask;
}
public Task<DicomCEchoResponse> OnCEchoRequestAsync(DicomCEchoRequest request)
{
return Task.FromResult(new DicomCEchoResponse(request, DicomStatus.Success));
}
}
}

View File

@ -0,0 +1,356 @@
using IRaCIS.Core.Domain.Share;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Infrastructure;
using Medallion.Threading;
using FellowOakDicom;
using FellowOakDicom.Imaging.Codec;
using System.Data;
using IRaCIS.Core.Domain.Models;
using FellowOakDicom.Network;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using System.Runtime.Intrinsics.X86;
using Serilog.Sinks.File;
namespace IRaCIS.Core.SCP.Service
{
public class DicomArchiveService : BaseService, IDicomArchiveService
{
private readonly IRepository<SCPPatient> _patientRepository;
private readonly IRepository<SCPStudy> _studyRepository;
private readonly IRepository<SCPSeries> _seriesRepository;
private readonly IRepository<SCPInstance> _instanceRepository;
private readonly IRepository<Dictionary> _dictionaryRepository;
private readonly IDistributedLockProvider _distributedLockProvider;
private List<Guid> _instanceIdList = new List<Guid>();
public DicomArchiveService(IRepository<SCPPatient> patientRepository, IRepository<SCPStudy> studyRepository,
IRepository<SCPSeries> seriesRepository,
IRepository<SCPInstance> instanceRepository,
IRepository<Dictionary> dictionaryRepository,
IDistributedLockProvider distributedLockProvider)
{
_distributedLockProvider = distributedLockProvider;
_studyRepository = studyRepository;
_patientRepository = patientRepository;
_seriesRepository = seriesRepository;
_instanceRepository = instanceRepository;
_dictionaryRepository = dictionaryRepository;
}
/// <summary>
/// 单个文件接收 归档
/// </summary>
/// <param name="dataset"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
{
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID,string.Empty);
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
var isStudyNeedAdd = false;
var isSeriesNeedAdd = false;
var isInstanceNeedAdd = false;
var isPatientNeedAdd = false;
//var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
//using (@lock.Acquire())
{
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
//先传输了修改了患者编号的又传输了没有修改患者编号的导致后传输的没有修改患者编号的下面的检查为0
if (findPatient == null && findStudy==null)
{
isPatientNeedAdd = true;
findPatient = new SCPPatient()
{
Id = NewId.NextSequentialGuid(),
TrialId=trialId,
TrialSiteId=trialSiteId,
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
EarliestStudyTime = studyTime,
LatestStudyTime = studyTime,
LatestPushTime = DateTime.Now,
};
if (findPatient.PatientBirthDate.Length == 8)
{
var birthDateStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}-{findPatient.PatientBirthDate[4]}{findPatient.PatientBirthDate[5]}-{findPatient.PatientBirthDate[6]}{findPatient.PatientBirthDate[7]}";
var yearStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}";
int year = 0;
var canParse = int.TryParse(yearStr, out year);
if (canParse && year > 1900)
{
findPatient.PatientBirthDate = birthDateStr;
DateTime birthDate;
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
{
var patientAge = studyTime.Value.Year - birthDate.Year;
// 如果生日还未到,年龄减去一岁
if (studyTime.Value < birthDate.AddYears(patientAge))
{
patientAge--;
}
findPatient.PatientAge = patientAge.ToString();
}
}
else
{
findPatient.PatientBirthDate = string.Empty;
}
}
}
else
{
if (studyTime < findPatient.EarliestStudyTime)
{
findPatient.EarliestStudyTime = studyTime;
}
if (studyTime > findPatient.LatestStudyTime)
{
findPatient.LatestStudyTime = studyTime;
}
findPatient.LatestPushTime = DateTime.Now;
}
if (findStudy == null)
{
isStudyNeedAdd = true;
findStudy = new SCPStudy
{
CalledAE = calledAE,
CallingAE = callingAE,
PatientId = findPatient.Id,
Id = studyId,
TrialId = trialId,
TrialSiteId = trialSiteId,
StudyInstanceUid = studyInstanceUid,
StudyTime = studyTime,
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
//ModalityForEdit = modalityForEdit,
Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
InstitutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty),
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
StudyId = dataset.GetSingleValueOrDefault(DicomTag.StudyID, string.Empty),
AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, string.Empty),
//需要特殊处理
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
//IsDoubleReview = addtionalInfo.IsDoubleReview,
SeriesCount = 0,
InstanceCount = 0
};
if (findStudy.PatientBirthDate.Length == 8)
{
findStudy.PatientBirthDate = $"{findStudy.PatientBirthDate[0]}{findStudy.PatientBirthDate[1]}{findStudy.PatientBirthDate[2]}{findStudy.PatientBirthDate[3]}-{findStudy.PatientBirthDate[4]}{findStudy.PatientBirthDate[5]}-{findStudy.PatientBirthDate[6]}{findStudy.PatientBirthDate[7]}";
}
}
if (findSerice == null)
{
isSeriesNeedAdd = true;
findSerice = new SCPSeries
{
Id = seriesId,
StudyId = findStudy.Id,
StudyInstanceUid = findStudy.StudyInstanceUid,
SeriesInstanceUid = seriesInstanceUid,
SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
//SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
//SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
SequenceName = dataset.GetSingleValueOrDefault(DicomTag.SequenceName, string.Empty),
ProtocolName = dataset.GetSingleValueOrDefault(DicomTag.ProtocolName, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
InstanceCount = 0
};
++findStudy.SeriesCount;
}
if (findInstance == null)
{
isInstanceNeedAdd = true;
findInstance = new SCPInstance
{
Id = instanceId,
StudyId = findStudy.Id,
SeriesId = findSerice.Id,
StudyInstanceUid = findStudy.StudyInstanceUid,
SeriesInstanceUid = findSerice.SeriesInstanceUid,
SopInstanceUid = sopInstanceUid,
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.ContentDate).Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.ContentTime).TimeOfDay),
//InstanceTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.ContentDate) + dataset.GetSingleValue<string>(DicomTag.ContentTime), out DateTime dt) ? dt : null,
//InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate,(DateTime?)null)?.Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, TimeSpan.Zero)),
//dataset.GetSingleValueOrDefault(DicomTag.ContentDate,DateTime.Now);//, DicomTag.ContentTime)
CPIStatus = false,
ImageRows = dataset.GetSingleValueOrDefault(DicomTag.Rows, 0),
ImageColumns = dataset.GetSingleValueOrDefault(DicomTag.Columns, 0),
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
Path = fileRelativePath,
FileSize= fileSize,
};
++findStudy.InstanceCount;
++findSerice.InstanceCount;
}
if (isPatientNeedAdd)
{
var ss = await _patientRepository.AddAsync(findPatient);
}
if (isStudyNeedAdd)
{
var dd = await _studyRepository.AddAsync(findStudy);
}
else
{
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == findStudy.Id, t => new SCPStudy() { IsUploadFinished = false });
}
if (isSeriesNeedAdd)
{
await _seriesRepository.AddAsync(findSerice);
}
if (isInstanceNeedAdd)
{
await _instanceRepository.AddAsync(findInstance);
}
else
{
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
}
await _studyRepository.SaveChangesAsync();
return findStudy.Id;
}
}
// 从DICOM文件中获取使用的字符集
private string GetEncodingVaulueFromDicomFile(DicomDataset dataset, DicomTag dicomTag)
{
// 获取DICOM文件的特定元素通常用于指示使用的字符集
var charset = dataset.GetSingleValueOrDefault(DicomTag.SpecificCharacterSet, string.Empty);
var dicomEncoding = DicomEncoding.GetEncoding(charset);
var dicomStringElement = dataset.GetDicomItem<DicomStringElement>(dicomTag);
var bytes = dicomStringElement.Buffer.Data;
return dicomEncoding.GetString(bytes);
//// 从DICOM文件中获取使用的字符集
//string filePath = "C:\\Users\\hang\\Documents\\WeChat Files\\wxid_r2imdzb7j3q922\\FileStorage\\File\\2024-05\\1.2.840.113619.2.80.169103990.5390.1271401378.4.dcm";
//DicomFile dicomFile = DicomFile.Open(filePath);
//// 获取DICOM文件的特定元素通常用于指示使用的字符集
//var charset = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SpecificCharacterSet, string.Empty);
//var dicomEncoding = DicomEncoding.GetEncoding(charset);
//var value = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
//var dicomStringElement = dicomFile.Dataset.GetDicomItem<DicomStringElement>(DicomTag.PatientName);
//var bytes = dicomStringElement.Buffer.Data;
//var aa= dicomEncoding.GetString(bytes);
}
}
}

View File

@ -0,0 +1,11 @@
using FellowOakDicom;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
namespace IRaCIS.Core.SCP.Service
{
public interface IDicomArchiveService
{
Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
}
}

View File

@ -0,0 +1,770 @@
using AlibabaCloud.SDK.Sts20150401;
using Aliyun.OSS;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.NewtonsoftJson;
using MassTransit;
using Microsoft.Extensions.Options;
using Minio;
using Minio.DataModel.Args;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
namespace IRaCIS.Core.SCP;
#region 绑定和返回模型
[LowerCamelCaseJson]
public class MinIOOptions : AWSOptions
{
public int Port { get; set; }
}
public class AWSOptions
{
public string EndPoint { get; set; }
public bool UseSSL { get; set; }
public string AccessKeyId { get; set; }
public string RoleArn { get; set; }
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
public int DurationSeconds { get; set; }
public string Region { get; set; }
}
public class AliyunOSSOptions
{
public string RegionId { get; set; }
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string InternalEndpoint { get; set; }
public string EndPoint { get; set; }
public string BucketName { get; set; }
public string RoleArn { get; set; }
public string Region { get; set; }
public string ViewEndpoint { get; set; }
public int DurationSeconds { get; set; }
}
public class ObjectStoreServiceOptions
{
public string ObjectStoreUse { get; set; }
public AliyunOSSOptions AliyunOSS { get; set; }
public MinIOOptions MinIO { get; set; }
public AWSOptions AWS { get; set; }
}
public class ObjectStoreDTO
{
public string ObjectStoreUse { get; set; }
public AliyunOSSTempToken AliyunOSS { get; set; }
public MinIOOptions MinIO { get; set; }
public AWSTempToken AWS { get; set; }
}
[LowerCamelCaseJson]
public class AliyunOSSTempToken
{
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string EndPoint { get; set; }
public string BucketName { get; set; }
public string Region { get; set; }
public string ViewEndpoint { get; set; }
public string SecurityToken { get; set; }
public DateTime Expiration { get; set; }
}
[LowerCamelCaseJson]
public class AWSTempToken
{
public string Region { get; set; }
public string SessionToken { get; set; }
public string EndPoint { get; set; }
public string AccessKeyId { get; set; }
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
public DateTime Expiration { get; set; }
}
public enum ObjectStoreUse
{
AliyunOSS = 0,
MinIO = 1,
AWS = 2,
}
#endregion
// aws 参考链接 https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/S3/S3_Basics
public interface IOSSService
{
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true);
public Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath);
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
public Task<string> GetSignedUrl(string ossRelativePath);
public Task DeleteFromPrefix(string prefix);
public ObjectStoreDTO GetObjectStoreTempToken();
}
public class OSSService : IOSSService
{
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
private AliyunOSSTempToken AliyunOSSTempToken { get; set; }
private AWSTempToken AWSTempToken { get; set; }
public OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options)
{
ObjectStoreServiceOptions = options.CurrentValue;
}
/// <summary>
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
/// </summary>
/// <param name="fileStream"></param>
/// <param name="oosFolderPath"></param>
/// <param name="fileRealName"></param>
/// <param name="isFileNameAddGuid"></param>
/// <returns></returns>
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
{
GetObjectStoreTempToken();
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}" : $"{oosFolderPath}/{fileRealName}";
try
{
using (var memoryStream = new MemoryStream())
{
fileStream.Seek(0, SeekOrigin.Begin);
fileStream.CopyTo(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
// 上传文件
var result = _ossClient.PutObject(aliConfig.BucketName, ossRelativePath, memoryStream);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var putObjectArgs = new PutObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithStreamData(memoryStream)
.WithObjectSize(memoryStream.Length);
await minioClient.PutObjectAsync(putObjectArgs);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var putObjectRequest = new Amazon.S3.Model.PutObjectRequest()
{
BucketName = awsConfig.BucketName,
InputStream = memoryStream,
Key = ossRelativePath,
};
await amazonS3Client.PutObjectAsync(putObjectRequest);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException($"上传发生异常:{ex.Message}");
}
return "/" + ossRelativePath;
}
/// <summary>
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
/// </summary>
/// <param name="localFilePath"></param>
/// <param name="oosFolderPath"></param>
/// <param name="isFileNameAddGuid"></param>
/// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception>
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
{
GetObjectStoreTempToken();
var localFileName = Path.GetFileName(localFilePath);
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
// 上传文件
var result = _ossClient.PutObject(aliConfig.BucketName, ossRelativePath, localFilePath);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var putObjectArgs = new PutObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithFileName(localFilePath);
await minioClient.PutObjectAsync(putObjectArgs);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var putObjectRequest = new Amazon.S3.Model.PutObjectRequest()
{
BucketName = awsConfig.BucketName,
FilePath = localFilePath,
Key = ossRelativePath,
};
await amazonS3Client.PutObjectAsync(putObjectRequest);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
return "/" + ossRelativePath;
}
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
{
GetObjectStoreTempToken();
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
// 上传文件
var result = _ossClient.GetObject(aliConfig.BucketName, ossRelativePath);
// 将下载的文件流保存到本地文件
using (var fs = File.OpenWrite(localFilePath))
{
result.Content.CopyTo(fs);
fs.Close();
}
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var getObjectArgs = new GetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithFile(localFilePath);
await minioClient.GetObjectAsync(getObjectArgs);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var getObjectArgs = new Amazon.S3.Model.GetObjectRequest()
{
BucketName = awsConfig.BucketName,
Key = ossRelativePath,
};
await (await amazonS3Client.GetObjectAsync(getObjectArgs)).WriteResponseStreamToFileAsync(localFilePath, true, CancellationToken.None);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss下载失败!" + ex.Message);
}
}
public async Task<string> GetSignedUrl(string ossRelativePath)
{
GetObjectStoreTempToken();
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
// 生成签名URL。
var req = new GeneratePresignedUriRequest(aliConfig.BucketName, ossRelativePath, SignHttpMethod.Get)
{
// 设置签名URL过期时间默认值为3600秒。
Expiration = DateTime.Now.AddHours(1),
};
var uri = _ossClient.GeneratePresignedUri(req);
return uri.PathAndQuery;
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var args = new PresignedGetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithExpiry(3600)
/*.WithHeaders(reqParams)*/;
var presignedUrl = await minioClient.PresignedGetObjectAsync(args);
Uri uri = new Uri(presignedUrl);
string relativePath = uri.PathAndQuery;
return relativePath;
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var presignedUrl = await amazonS3Client.GetPreSignedURLAsync(new GetPreSignedUrlRequest()
{
BucketName = awsConfig.BucketName,
Key = ossRelativePath,
Expires = DateTime.UtcNow.AddMinutes(120)
});
Uri uri = new Uri(presignedUrl);
string relativePath = uri.PathAndQuery;
return relativePath;
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss授权url失败!" + ex.Message);
}
}
/// <summary>
/// 删除某个目录的文件
/// </summary>
/// <param name="prefix"></param>
/// <returns></returns>
public async Task DeleteFromPrefix(string prefix)
{
GetObjectStoreTempToken();
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
try
{
ObjectListing objectListing = null;
string nextMarker = null;
do
{
// 使用 prefix 模拟目录结构,设置 MaxKeys 和 NextMarker
objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(aliConfig.BucketName)
{
Prefix = prefix,
MaxKeys = 1000,
Marker = nextMarker
});
List<string> keys = objectListing.ObjectSummaries.Select(t => t.Key).ToList();
// 删除获取到的文件
if (keys.Count > 0)
{
_ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, keys, false));
}
// 设置 NextMarker 以获取下一页的数据
nextMarker = objectListing.NextMarker;
} while (objectListing.IsTruncated);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var listArgs = new ListObjectsArgs().WithBucket(minIOConfig.BucketName).WithPrefix(prefix).WithRecursive(true);
// 创建一个空列表用于存储对象键
var objects = new List<string>();
// 使用 await foreach 来异步迭代对象列表
await foreach (var item in minioClient.ListObjectsEnumAsync(listArgs))
{
objects.Add(item.Key);
}
if (objects.Count > 0)
{
var objArgs = new RemoveObjectsArgs()
.WithBucket(minIOConfig.BucketName)
.WithObjects(objects);
// 删除对象
await minioClient.RemoveObjectsAsync(objArgs);
}
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
// 列出指定前缀下的所有对象
var listObjectsRequest = new ListObjectsV2Request
{
BucketName = awsConfig.BucketName,
Prefix = prefix
};
var listObjectsResponse = await amazonS3Client.ListObjectsV2Async(listObjectsRequest);
if (listObjectsResponse.S3Objects.Count > 0)
{
// 准备删除请求
var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest
{
BucketName = awsConfig.BucketName,
Objects = new List<KeyVersion>()
};
foreach (var s3Object in listObjectsResponse.S3Objects)
{
deleteObjectsRequest.Objects.Add(new KeyVersion
{
Key = s3Object.Key
});
}
// 批量删除对象
var deleteObjectsResponse = await amazonS3Client.DeleteObjectsAsync(deleteObjectsRequest);
}
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
public ObjectStoreDTO GetObjectStoreTempToken()
{
var ossOptions = ObjectStoreServiceOptions.AliyunOSS;
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var client = new Client(new AlibabaCloud.OpenApiClient.Models.Config()
{
AccessKeyId = ossOptions.AccessKeyId,
AccessKeySecret = ossOptions.AccessKeySecret,
//AccessKeyId = "LTAI5tJV76pYX5yPg1N9QVE8",
//AccessKeySecret = "roRNLa9YG1of4pYruJGCNKBXEWTAWa",
Endpoint = "sts.cn-hangzhou.aliyuncs.com"
});
var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
// 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称例如oss-role-session。
assumeRoleRequest.RoleSessionName = $"session-name-{NewId.NextGuid()}";
// 将<YOUR_ROLE_ARN>替换为拥有上传文件到指定OSS Bucket权限的RAM角色的ARN。
assumeRoleRequest.RoleArn = ossOptions.RoleArn;
//assumeRoleRequest.RoleArn = "acs:ram::1899121822495495:role/webdirect";
assumeRoleRequest.DurationSeconds = ossOptions.DurationSeconds;
var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
var response = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
var credentials = response.Body.Credentials;
var tempToken = new AliyunOSSTempToken()
{
AccessKeyId = credentials.AccessKeyId,
AccessKeySecret = credentials.AccessKeySecret,
//转为服务器时区,最后统一转为客户端时区
Expiration = TimeZoneInfo.ConvertTimeFromUtc(DateTime.Parse(credentials.Expiration), TimeZoneInfo.Local),
SecurityToken = credentials.SecurityToken,
Region = ossOptions.Region,
BucketName = ossOptions.BucketName,
EndPoint = ossOptions.EndPoint,
ViewEndpoint = ossOptions.ViewEndpoint,
};
AliyunOSSTempToken = tempToken;
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, AliyunOSS = tempToken };
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, MinIO = ObjectStoreServiceOptions.MinIO };
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsOptions = ObjectStoreServiceOptions.AWS;
//aws 临时凭证
// 创建 STS 客户端
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey);
// 使用 AssumeRole 请求临时凭证
var assumeRoleRequest = new AssumeRoleRequest
{
RoleArn = awsOptions.RoleArn, // 角色 ARN
RoleSessionName = $"session-name-{NewId.NextGuid()}",
DurationSeconds = awsOptions.DurationSeconds // 临时凭证有效期
};
var assumeRoleResponse = stsClient.AssumeRoleAsync(assumeRoleRequest).Result;
var credentials = assumeRoleResponse.Credentials;
var tempToken = new AWSTempToken()
{
AccessKeyId = credentials.AccessKeyId,
SecretAccessKey = credentials.SecretAccessKey,
SessionToken = credentials.SessionToken,
Expiration = credentials.Expiration,
Region = awsOptions.Region,
BucketName = awsOptions.BucketName,
EndPoint = awsOptions.EndPoint,
ViewEndpoint = awsOptions.ViewEndpoint,
};
AWSTempToken = tempToken;
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, AWS = tempToken };
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Infrastructure"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRC.Core.SCP", "IRC.Core.SCP\IRC.Core.SCP.csproj", "{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IRC.Core.Dicom", "IRC.Core.Dicom\IRC.Core.Dicom.csproj", "{0545F0A5-D97B-4A47-92A6-A8A02A181322}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -57,6 +59,10 @@ Global
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Release|Any CPU.Build.0 = Release|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -67,7 +67,7 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
//利用主键作为任务Id
var jobId = $"{task.Id}_({task.BusinessScenarioEnum})";
HangfireJobHelper.AddOrUpdateSystemCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
}

View File

@ -31,7 +31,7 @@ namespace IRaCIS.Core.API
.Enrich.FromLogContext()
.Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day);
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,retainedFileCountLimit:60);
#region 根据环境配置是否打开错误发送邮件通知

View File

@ -7,10 +7,10 @@
}
},
"ConnectionStrings": {
//"RemoteNew": "Server=101.132.193.237,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
//"Hangfire": "Server=101.132.193.237,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
"RemoteNew": "Server=101.132.193.237,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
"Hangfire": "Server=101.132.193.237,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
//"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
//"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",

View File

@ -243,6 +243,7 @@ public static class ExcelExportHelper
}
public class DynamicColumnConfig
{
/// <summary>
@ -260,7 +261,9 @@ public static class ExcelExportHelper
/// </summary>
public int TempalteLastColumnIndex { get; set; }
public List<string> CDISCList { get; set; } = new List<string>();
public bool IsCDISCExport { get; set; }=false;
//public List<string> CDISCList { get; set; } = new List<string>();
/// <summary>
/// 动态的列名 如果Id 重复那么就按照名称填充否则就按照Id 填充列数据
@ -301,6 +304,8 @@ public static class ExcelExportHelper
public Guid Id { get; set; }
public string Name { get; set; }
public string CDISCCode { get; set; }
public override bool Equals(object obj)
{
if (obj is not ColumItem other) return false;
@ -489,7 +494,9 @@ public static class ExcelExportHelper
if (dynamicColumnConfig != null)
{
var isCdics = dynamicColumnConfig.CDISCList.Count > 0;
//var isCdics = dynamicColumnConfig.CDISCList.Count > 0;
var isCdics = dynamicColumnConfig.IsCDISCExport;
var sheet = workbook.GetSheetAt(0);
@ -544,9 +551,9 @@ public static class ExcelExportHelper
}
//创建新的列
for (int i = originTotalEndIndex; i < originTotalEndIndex + needAddCount; i++)
for (int i = originRemoveEndIndex; i < originRemoveEndIndex + needAddCount; i++)
{
titelRow.CreateCell(i + 1);
templateRow.CreateCell(i + 1);
@ -565,6 +572,11 @@ public static class ExcelExportHelper
titelRow.GetCell(i).SetCellValue(titelRow.GetCell(i - gap).StringCellValue);
templateRow.GetCell(i).SetCellValue(templateRow.GetCell(i - gap).StringCellValue);
if (isCdics)
{
cdicsRow.GetCell(i).SetCellValue(cdicsRow.GetCell(i - gap).StringCellValue);
}
}
@ -573,6 +585,7 @@ public static class ExcelExportHelper
for (int i = dynamicColunmStartIndex; i < dynamicColunmStartIndex + needAddCount; i++)
{
var name = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].Name;
titelRow.GetCell(i).SetCellValue(name);
@ -580,13 +593,15 @@ public static class ExcelExportHelper
if (isCdics)
{
var cdicsCode = dynamicColumnConfig.CDISCList[i - dynamicColunmStartIndex];
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].CDISCCode;
cdicsRow.GetCell(i).SetCellValue(cdicsCode);
}
}
}
using (var memoryStream2 = new MemoryStream())
{
workbook.Write(memoryStream2, true);
@ -609,8 +624,11 @@ public static class ExcelExportHelper
IgnoreTemplateParameterMissing = true,
};
//await MiniExcel.SaveAsByTemplateAsync("testmini.xlsx", templateStream.ToArray(), translateData);
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
memoryStream.Seek(0, SeekOrigin.Begin);
if (dynamicColumnConfig != null)
@ -907,7 +925,7 @@ public static class ExcelExportHelper
{
var name = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].Name;
var cdicsCode = dynamicColumnConfig.CDISCList[i - dynamicColunmStartIndex];
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].CDISCCode;
cdicsRow.GetCell(i).SetCellValue(cdicsCode);
titelRow.GetCell(i).SetCellValue(name);

View File

@ -69,6 +69,15 @@ public static class FileStoreHelper
return rootFolder;
}
public static string GetDonwnloadImageFolder(IWebHostEnvironment _hostEnvironment)
{
var rootPath = GetIRaCISRootPath(_hostEnvironment);
var rootFolder = Path.Combine(rootPath, StaticData.Folder.DownloadIamgeFolder);
return rootFolder;
}
//根据相对路径 获取具体文件物理地址
public static string GetPhysicalFilePath(IWebHostEnvironment _hostEnvironment, string relativePath)
{

View File

@ -83,15 +83,21 @@ namespace IRaCIS.Core.Application.Helper
}
public static void AddOrUpdateSystemCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
{
switch (businessScenario)
{
case EmailBusinessScenario.GeneralTraining_ExpirationNotification:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { }, default), emailCron);
break;
case EmailBusinessScenario.TrialTraining_ExpirationNotification:
Console.WriteLine("更新项目到期job");
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { }, default), emailCron);
break;
default:
break;

View File

@ -360,7 +360,11 @@ public class OSSService : IOSSService
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
{
GetObjectStoreTempToken();
if (isFirstCall)
{
GetObjectStoreTempToken();
isFirstCall = false;
}
ossRelativePath = ossRelativePath.TrimStart('/');
try

View File

@ -53,7 +53,7 @@
<PackageReference Include="MaxMind.GeoIP2" Version="5.2.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="MimeKit" Version="4.11.0" />
<PackageReference Include="MiniExcel" Version="1.40.0" />
<PackageReference Include="MiniExcel" Version="1.41.2" />
<PackageReference Include="Minio" Version="6.0.4" />
<PackageReference Include="MiniWord" Version="0.9.2" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">

View File

@ -603,11 +603,10 @@
<param name="addOrEditBasic"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.DictionaryService.GetChildList(System.Guid)">
<member name="M:IRaCIS.Core.Application.Service.DictionaryService.GetChildList(IRaCIS.Application.Contracts.ChildInQuery)">
<summary>
获取子项数组
</summary>
<param name="parentId"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.DictionaryService.DeleteDictionary(System.Guid)">
@ -1053,6 +1052,27 @@
PublishLogService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Service.TrialImageDownloadService">
<summary>
项目影像后台下载,不打包
</summary>
<param name="_trialRepository"></param>
<param name="_oSSService"></param>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialImageDownloadService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Application.Helper.IOSSService,Microsoft.AspNetCore.Hosting.IWebHostEnvironment)">
<summary>
项目影像后台下载,不打包
</summary>
<param name="_trialRepository"></param>
<param name="_oSSService"></param>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialImageDownloadService.DownloadTrialImage(System.Guid)">
<summary>
后端api swagger 下载项目影像
</summary>
<param name="trialId"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Service.AttachmentService">
<summary>
医生文档关联关系维护
@ -1530,6 +1550,7 @@
<param name="inDto"></param>
<returns></returns>
</member>
<!-- Badly formed XML comment ignored for member "M:IRaCIS.Core.Application.Service.TrialEmailNoticeConfigService.DealMedicalReviewTasKGenerateAndIsSendAsync(System.Guid,System.Nullable{System.Boolean},System.String,System.Collections.Generic.List{System.Guid},System.Collections.Generic.List{System.Guid})" -->
<member name="M:IRaCIS.Core.Application.Service.TrialEmailNoticeConfigService.BaseBusinessScenarioSendEmailAsync(System.Guid,System.Nullable{System.Boolean},IRaCIS.Core.Domain.Share.Common.EmailStoreSendMode,System.String)">
<summary>
测试邮件 带附件 填充word
@ -1556,9 +1577,19 @@
<param name="sendFileRelativePath"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialEmailNoticeConfigService.DealEnrollAnswer(System.Guid,System.Guid,IRaCIS.Core.Domain.Share.CriterionType,System.Guid)">
<summary>
获取入组结果
</summary>
<param name="visitTaskId"></param>
<param name="subjectVisitId"></param>
<param name="criterionType"></param>
<param name="trialReadingCriterionId"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialEmailNoticeConfigService.TranslatePdStateAsync(System.Guid,IRaCIS.Core.Domain.Share.ReadingCategory,IRaCIS.Core.Domain.Share.CriterionType,System.Nullable{System.Boolean})">
<summary>
获取PD 结果
</summary>
<param name="visitTaskId"> 任务Id</param>
<param name="readingCategory"> 任务类型</param>
@ -1701,6 +1732,44 @@
<param name="trialFinalRecordId"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Service.TrialHistoryRecordFileService">
<summary>
项目历史记录文件服务
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialHistoryRecordFileService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialHistoryRecordFile},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<summary>
项目历史记录文件服务
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialHistoryRecordFileService.GetTrialHistoryRecordFileList(IRaCIS.Core.Application.Service.DTO.TrialHistoryRecordFileQuery)">
<summary>
获取稽查记录文件列表
</summary>
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialHistoryRecordFileService.BatchAddTrialHistoryRecordFile(System.Collections.Generic.List{IRaCIS.Core.Application.Service.DTO.TrialHistoryRecordFileAddOrEdit})">
<summary>
批量新增稽查记录文件
</summary>
<param name="inDto"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialHistoryRecordFileService.AddOrUpdateTrialHistoryRecordFile(IRaCIS.Core.Application.Service.DTO.TrialHistoryRecordFileAddOrEdit)">
<summary>
新增或修改稽查记录文件
</summary>
<param name="addOrEditTrialHistoryRecordFile"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialHistoryRecordFileService.DeleteTrialHistoryRecordFile(System.Guid)">
<summary>
删除稽查记录文件
</summary>
<param name="trialHistoryRecordFileId"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Service.TrialNormalRecordService">
<summary>
项目-一般文件记录
@ -2002,6 +2071,14 @@
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.ImageAndDoc.DownloadAndUploadService.UpdateTaskStudyModality(System.Guid,System.String)">
<summary>
更新后处理上传的检查modality
</summary>
<param name="taskStudyId"></param>
<param name="modality"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.ImageAndDoc.DownloadAndUploadService.DownloadImageSuccess(System.Guid)">
<summary>
影像下载成功回调
@ -2016,12 +2093,18 @@
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.ImageAndDoc.DownloadAndUploadService.UpdateTaskStudyModality(System.Guid,System.String)">
<member name="M:IRaCIS.Core.Application.Service.ImageAndDoc.DownloadAndUploadService.GetTrialVisitImageStatInfo(System.Guid)">
<summary>
更新后处理上传的检查modality
获取项目影像统计有影像的subject 数量 访视数量
</summary>
<param name="taskStudyId"></param>
<param name="modality"></param>
<param name="trialId"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.ImageAndDoc.DownloadAndUploadService.GetExportSubjectVisitImageList(IRaCIS.Core.Application.Contracts.TrialExportImageCommand)">
<summary>
批量勾选访视 进行下载
</summary>
<param name="inCommand"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.ImageAndDoc.DownloadAndUploadService.GetSubejectOrVisitZipInfo(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Subject},IRaCIS.Core.Application.Contracts.SubejctZipInfoQuery)">
@ -2288,6 +2371,20 @@
批次Id
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.Inspection.InspectionService.SetTrialShowInspection(IRaCIS.Core.Application.Service.Inspection.DTO.SetTrialShowInspection)">
<summary>
设置项目稽查配置
</summary>
<param name="inDto"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.Inspection.InspectionService.GetTrialShowInspection(IRaCIS.Core.Application.Service.Inspection.DTO.GetTrialShowInspectionInDto)">
<summary>
获取项目稽查显示信息
</summary>
<param name="inDto"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.Inspection.InspectionService.RecordSing(IRaCIS.Core.Application.Contracts.SignDTO)">
<summary>
传入参数记录ID
@ -2314,7 +2411,7 @@
FrontAuditConfigService
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.FrontAuditConfigService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.FrontAuditConfig},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.QCChallengeDialog},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DataInspection},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.QCChallenge},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Dictionary},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.UserRole},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.CheckChallengeDialog},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<member name="M:IRaCIS.Core.Application.Service.FrontAuditConfigService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.FrontAuditConfig},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.QCChallengeDialog},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DataInspection},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.QCChallenge},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Dictionary},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialAuditShow},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.UserRole},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.CheckChallengeDialog},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<summary>
FrontAuditConfigService
</summary>
@ -2429,11 +2526,19 @@
<param name="item"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.FrontAuditConfigService.GetModuleTypeDescriptionList(System.Guid)">
<member name="M:IRaCIS.Core.Application.Service.FrontAuditConfigService.GetModuleTypeList(IRaCIS.Core.Application.ViewModel.GetModuleTypeListInDto)">
<summary>
获取模块类型列表
</summary>
<param name="inDto"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.FrontAuditConfigService.getModuleTypeDescriptionList(System.Guid,System.Guid)">
<summary>
获取Description
</summary>
<param name="moduleTypeId"></param>
<param name="trialId"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.FrontAuditConfigService.GetFrontAuditConfigList(IRaCIS.Core.Application.ViewModel.FrontAuditConfigQuery)">
@ -8674,6 +8779,11 @@
StudyId
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.QuestionMarkInfo.RowId">
<summary>
表格问题标记
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.GetReadingQuestionAndAnswerInDto.QuestionClassify">
<summary>
问题分类
@ -10229,6 +10339,21 @@
表格问题类型
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.ReadingTableQuestionTrialAddOrEdit.ImageMarkEnum">
<summary>
影像标记
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.ReadingTableQuestionTrialAddOrEdit.ImageTool">
<summary>
影像工具
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.ReadingTableQuestionTrialAddOrEdit.ImageToolAttribute">
<summary>
影像工具属性
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.ReadingTableQuestionTrialAddOrEdit.ClassifyType">
<summary>
分类问题类型
@ -10585,6 +10710,21 @@
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.ReadingQuestionTrialView.ImageMarkEnum">
<summary>
影像标记
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.ReadingQuestionTrialView.ImageTool">
<summary>
影像工具
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.ReadingQuestionTrialView.ImageToolAttribute">
<summary>
影像工具属性
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.ReadingQuestionTrialView.GroupName">
<summary>
分组
@ -11470,6 +11610,21 @@
问题英文分组
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.AddOrUpdateReadingQuestionTrialInDto.ImageMarkEnum">
<summary>
影像标记
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.AddOrUpdateReadingQuestionTrialInDto.ImageTool">
<summary>
影像工具
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.AddOrUpdateReadingQuestionTrialInDto.ImageToolAttribute">
<summary>
影像工具属性
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.AddOrUpdateReadingQuestionTrialInDto.Id">
<summary>
Id
@ -14433,6 +14588,17 @@
如果为null或空则发送给所有相关角色
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.TrialDocumentErverDayEvent">
<summary>
定时提醒
</summary>
</member>
<member name="P:IRaCIS.Core.Application.MassTransit.Consumer.TrialDocumentPublishEvent.NewUserTypeIds">
<summary>
新增的需要发送邮件的用户角色ID列表
如果为null或空则发送给所有相关角色
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.QCImageQuestionRecurringSchedule">
<summary>
QC 影像质疑待处理
@ -14478,6 +14644,26 @@
生效通知
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Recurring.TrialDocumentErverDayEventConsumer">
<summary>
定时过期提醒
</summary>
</member>
<member name="M:IRaCIS.Core.Application.MassTransit.Recurring.TrialDocumentErverDayEventConsumer.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocument},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.IdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocConfirmedIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Dictionary},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialUserRole},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocument},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Application.Contracts.ISystemDocumentService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocNeedConfirmedUserType},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocNeedConfirmedUserType},Microsoft.Extensions.DependencyInjection.IServiceScopeFactory,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocConfirmedIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.EmailNoticeConfig},Microsoft.Extensions.Options.IOptionsMonitor{IRaCIS.Core.Domain.Share.SystemEmailSendConfig})">
<summary>
定时过期提醒
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Recurring.TrialDocumentPublishEventConsumer">
<summary>
生效通知
</summary>
</member>
<member name="M:IRaCIS.Core.Application.MassTransit.Recurring.TrialDocumentPublishEventConsumer.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocument},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.IdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Dictionary},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialUserRole},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.EmailNoticeConfig},Microsoft.Extensions.Options.IOptionsMonitor{IRaCIS.Core.Domain.Share.SystemEmailSendConfig})">
<summary>
生效通知
</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.TaskAllocationRuleView">
<summary> TaskAllocationRuleView 列表视图模型 </summary>
</member>
@ -14714,6 +14900,11 @@
<member name="T:IRaCIS.Core.Application.ViewModel.FrontAuditConfigAddOrEdit">
<summary> FrontAuditConfigAddOrEdit 列表查询参数模型</summary>
</member>
<member name="P:IRaCIS.Core.Application.ViewModel.FrontAuditConfigAddOrEdit.IsDefaultChoice">
<summary>
是否默认选择
</summary>
</member>
<member name="P:IRaCIS.Core.Application.ViewModel.FrontAuditConfigAddOrEdit.CodeEn">
<summary>
字段的英文值
@ -15932,6 +16123,13 @@
系统邮件配置表
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Contracts.EmailNoticeConfigService.BatchUpdateEmail(System.Collections.Generic.List{IRaCIS.Core.Application.Contracts.BatchUpdateEmailTopicCommand})">
<summary>
批量更新邮件主题中英文
</summary>
<param name="inCommandList"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Contracts.ISystemBasicDataService">
<summary>
ISystemBasicDataService
@ -16013,6 +16211,21 @@
<member name="T:IRaCIS.Core.Application.Contracts.TrialDocumentAddOrEdit">
<summary> TrialDocumentAddOrEdit 列表查询参数模型</summary>
</member>
<member name="P:IRaCIS.Core.Application.Contracts.TrialDocumentAddOrEdit.CurrentStaffTrainDays">
<summary>
现有员工培训天数
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Contracts.TrialDocumentAddOrEdit.NewStaffTrainDays">
<summary>
新员工培训天数
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Contracts.TrialDocumentAddOrEdit.IsPublish">
<summary>
是否发布
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Contracts.ISystemDocumentService">
<summary>
ISystemDocumentService
@ -17183,11 +17396,44 @@
TrialDocumentService
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Services.TrialDocumentService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocument},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Application.Contracts.ISystemDocumentService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocConfirmedIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocNeedConfirmedUserType},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocNeedConfirmedUserType},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocument},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialUserRole},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.IdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocConfirmedIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<member name="M:IRaCIS.Core.Application.Services.TrialDocumentService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocument},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocumentAttachment},IRaCIS.Core.Application.Contracts.ISystemDocumentService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocConfirmedIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocNeedConfirmedUserType},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocNeedConfirmedUserType},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SystemDocument},Microsoft.Extensions.DependencyInjection.IServiceScopeFactory,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialUserRole},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.IdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialDocConfirmedIdentityUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<summary>
TrialDocumentService
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Services.TrialDocumentService.GetTrialDocumentAttachmentList(IRaCIS.Core.Application.Contracts.TrialDocumentAttachmentQuery)">
<summary>
获取项目文档附件列表
</summary>
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Services.TrialDocumentService.AddOrUpdateTrialDocumentAttachment(IRaCIS.Core.Application.Contracts.TrialDocumentAttachmentAddOrEdit)">
<summary>
添加或更新项目文档附件
</summary>
<param name="addOrEditTrialDocumentAttachment"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Services.TrialDocumentService.DeleteTrialDocumentAttachment(System.Guid)">
<summary>
删除项目文档附件
</summary>
<param name="trialDocumentAttachmentId"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Services.TrialDocumentService.PublishTrialDocument(IRaCIS.Core.Application.Contracts.PublishTrialDocumentInDto)">
<summary>
发布项目文档
</summary>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Services.TrialDocumentService.TestPush">
<summary>
测试定时发送
</summary>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Services.TrialDocumentService.GetTrialDocumentList(IRaCIS.Core.Application.Contracts.TrialDocumentQuery)">
<summary>
Setting 界面的 项目所有文档列表

View File

@ -28,10 +28,10 @@ public static class OldRecurringEmailHelper
Guid trialId, EmailBusinessScenario businessScenario,
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc,
Guid? trialSiteId = null, Guid? trialReadingCriterionId = null)
Guid? trialSiteId = null)
{
//找到配置
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.TrialReadingCriterionId == trialReadingCriterionId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
.Include(t => t.TrialEmailNoticeUserList).Include(t => t.TrialEmailBlackUserList).FirstOrDefaultAsync();

View File

@ -58,3 +58,24 @@ public class SystemDocumentPublishEvent : DomainEvent
}
/// <summary>
/// 定时提醒
/// </summary>
public class TrialDocumentErverDayEvent : DomainEvent
{
}
public class TrialDocumentPublishEvent : DomainEvent
{
public List<Guid> Ids { get; set; }
/// <summary>
/// 新增的需要发送邮件的用户角色ID列表
/// 如果为null或空则发送给所有相关角色
/// </summary>
public List<Guid> NewUserTypeIds { get; set; }
}

View File

@ -0,0 +1,307 @@
using DocumentFormat.OpenXml;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Models;
using MassTransit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using MimeKit;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reactive.Joins;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.MassTransit.Recurring
{
/// <summary>
/// 定时过期提醒
/// </summary>
public class TrialDocumentErverDayEventConsumer(
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<SystemDocument> _systemDocumentRepository,
IRepository<IdentityUser> _identityUserRepository,
IRepository<SystemDocConfirmedIdentityUser> _systemDocConfirmedUserRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialUserRole> _trialUserRoleRepository, IRepository<TrialDocument> _trialDocumentRepository,
IRepository<Trial> _trialRepository,
ISystemDocumentService _systemDocumentService,
IRepository<SystemDocNeedConfirmedUserType> _systemDocNeedConfirmedUserTypeRepository,
IRepository<TrialDocNeedConfirmedUserType> _trialDocNeedConfirmedUserTypeRepository,
IServiceScopeFactory serviceScopeFactory,
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
IRepository<TrialDocConfirmedIdentityUser> _trialDocConfirmedUserRepository,
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<TrialDocumentErverDayEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<TrialDocumentErverDayEvent> context)
{
DateTime now = DateTime.Now;
Console.WriteLine("发送定时项目过期提醒");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var trialDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(true)
join trialIdentityUser in _trialIdentityUserRepository.Where(x => x.IsDeleted == false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x => x.IsDeleted == false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
from identityUser in _identityUserRepository.AsQueryable(false)
.Where(t => t.Status == UserStateEnum.Enable &&
t.Id == trialIdentityUser.IdentityUserId &&
t.UserRoleList.Where(t => t.IsUserRoleDisabled == false)
.Any(t => trialDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
join confirm in _trialDocConfirmedUserRepository.Where() on
new { trialIdentityUser.IdentityUserId, TrialDocumentId = trialDoc.Id } equals new { IdentityUserId = confirm.ConfirmUserId, confirm.TrialDocumentId } into cc
from confirm in cc.DefaultIfEmpty()
select new TrialSignDocView()
{
TrialCode = trialDoc.Trial.TrialCode,
ResearchProgramNo = trialDoc.Trial.ResearchProgramNo,
ExperimentName = trialDoc.Trial.ExperimentName,
CurrentStaffTrainDays = trialDoc.CurrentStaffTrainDays,
NewStaffTrainDays = trialDoc.NewStaffTrainDays,
Id = trialDoc.Id,
IsSystemDoc = false,
CreateTime = trialDoc.CreateTime,
FullFilePath = trialDoc.Path,
IsDeleted = trialDoc.IsDeleted,
Name = trialDoc.Name,
Path = trialDoc.Path,
FileTypeId = trialDoc.FileTypeId,
UpdateTime = trialDoc.UpdateTime,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
//IsConfirmed = confirm.ConfirmTime != null,
ConfirmUserId = identityUser.Id,
ConfirmTime = confirm.ConfirmTime,
RealName = trialIdentityUser.IdentityUser.FullName,
UserName = trialIdentityUser.IdentityUser.UserName,
UserCreateTime= trialIdentityUser.CreateTime,
IdentityUserTypeList = trialIdentityUser.TrialUserRoleList.Select(t => t.UserRole.UserTypeRole.UserTypeShortName).ToList(),
DocNeedSignUserTypeList = trialDoc.NeedConfirmedUserTypeList.Select(t => t.UserTypeRole.UserTypeShortName).ToList(),
};
var datalist = await trialDocQuery.IgnoreQueryFilters().Where(t => t.IsDeleted == false && t.ConfirmTime == null&&t.ConfirmTime==null)
.ToListAsync();
datalist = datalist.Where(x => x.SuggestFinishTime != null && x.SuggestFinishTime.Value.Date == DateTime.Now.Date)
.ToList();
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync();
Console.WriteLine("发送定时项目过期提醒:人员数量" + userinfoList.Count);
int index = 1;
foreach (var userinfo in userinfoList)
{
try
{
Console.WriteLine($"{index}发送定时过期提醒,邮箱:{userinfo.EMail},姓名{userinfo.UserName}");
index++;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.UserName, // 用户名 {0}
_systemEmailConfig.SiteUrl
);
return (topicStr, htmlBodyStr);
};
var scenario = EmailBusinessScenario.TrialTraining_ExpirationNotification;
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
if (emailConfig != null)
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
catch (Exception)
{
}
}
}
}
/// <summary>
/// 生效通知
/// </summary>
public class TrialDocumentPublishEventConsumer(
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialDocument> _trialDocumentRepository,
IRepository<IdentityUser> _identityUserRepository,
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<TrialDocumentPublishEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<TrialDocumentPublishEvent> context)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
// 记录是否只发送给新增角色的日志
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
{
Console.WriteLine($"只发送给新增项目的角色,角色数量: {context.Message.NewUserTypeIds.Count}");
}
// 构建查询
IQueryable<UnionDocumentWithConfirmInfoView> systemDocQuery;
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
{
// 只查询新增角色的用户
systemDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
join trialIdentityUser in _trialIdentityUserRepository.Where(x=>x.IsDeleted==false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x => x.IsDeleted == false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
from identityUser in _identityUserRepository.AsQueryable(false)
.Where(t => t.Status == UserStateEnum.Enable &&
t.Id == trialIdentityUser.IdentityUserId &&
t.UserRoleList.Where(t => t.IsUserRoleDisabled == false)
.Any(t => context.Message.NewUserTypeIds.Contains(t.UserTypeId) &&
trialDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
select new UnionDocumentWithConfirmInfoView()
{
IsSystemDoc = true,
Id = trialDoc.Id,
CreateTime = trialDoc.CreateTime,
IsDeleted = trialDoc.IsDeleted,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
Name = trialDoc.Name,
Path = trialDoc.Path,
FileTypeId = trialDoc.FileTypeId,
UpdateTime = trialDoc.UpdateTime,
ConfirmUserId = identityUser.Id,
RealName = identityUser.FullName,
UserName = identityUser.UserName,
IsNeedSendEmial = identityUser.IsZhiZhun,
FullFilePath = trialDoc.Path
};
}
else
{
// 查询所有相关角色的用户
systemDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
join trialIdentityUser in _trialIdentityUserRepository.Where(x => x.IsDeleted == false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x=>x.IsDeleted==false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
from identityUser in _identityUserRepository.AsQueryable(false)
.Where(t => t.Status == UserStateEnum.Enable &&
t.Id== trialIdentityUser.IdentityUserId&&
t.UserRoleList.Where(t => t.IsUserRoleDisabled == false)
.Any(t => trialDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
select new UnionDocumentWithConfirmInfoView()
{
IsSystemDoc = false,
Id = trialDoc.Id,
CreateTime = trialDoc.CreateTime,
IsDeleted = trialDoc.IsDeleted,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
Name = trialDoc.Name,
Path = trialDoc.Path,
FileTypeId = trialDoc.FileTypeId,
UpdateTime = trialDoc.UpdateTime,
ConfirmUserId = identityUser.Id,
RealName = identityUser.FullName,
UserName = identityUser.UserName,
IsNeedSendEmial = identityUser.IsZhiZhun ,
FullFilePath = trialDoc.Path
};
}
var datalist = await systemDocQuery.IgnoreQueryFilters().ToListAsync();
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync();
int index = 1;
foreach (var userinfo in userinfoList)
{
string msg = $"{index}项目生效通知,邮箱:{userinfo.EMail},姓名{userinfo.UserName},";
index++;
try
{
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.UserName, // 用户名 {0}
_systemEmailConfig.SiteUrl
);
return (topicStr, htmlBodyStr);
};
var scenario = EmailBusinessScenario.TrialTraining_EffectiveNotification;
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable).FirstOrDefault();
if (emailConfig != null)
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
msg += "发送成功";
}
}
catch (Exception)
{
msg += "发送失败";
}
Console.WriteLine(msg);
}
}
}
}

View File

@ -237,7 +237,7 @@ namespace IRaCIS.Core.Application.ViewModel
//重阅原始编号
//public string ReReadingOriginalTaskCode { get; set; }
public string ApplicantName { get; set; }
public Guid Id { get; set; }

View File

@ -160,6 +160,7 @@ namespace IRaCIS.Core.Application.Service
CreateMap<VisitTaskReReading, ReReadingTaskView>()
.ForMember(o => o.ApplicantName, t => t.MapFrom(u => u.CreateUserRole.IdentityUser.FullName))
.ForMember(o => o.ReReadingNewTaskCode, t => t.MapFrom(u => u.NewReReadingTask.TaskCode))
.ForMember(o => o.OriginalReReadingTask, t => t.MapFrom(u => u.OriginalReReadingTask))
;

View File

@ -39,6 +39,10 @@ namespace IRaCIS.Application.Contracts
public string ValueCN { get; set; } = string.Empty;
}
public class ChildInQuery:SortInput
{
public Guid ParentId { get; set; }
}
public class AddOrEditBasicDic
{

View File

@ -96,9 +96,6 @@ namespace IRaCIS.Core.Application.Contracts
public bool IsDeleted { get; set; }
public CriterionType? CriterionTypeEnum { get; set; }
/// <summary> 业务模块 /// </summary>
public int BusinessModuleEnum { get; set; }
@ -140,6 +137,24 @@ namespace IRaCIS.Core.Application.Contracts
public int? EmailDelaySeconds { get; set; }
[Comment("邮件配置的多个标准")]
public List<CriterionType>? CriterionTypeList { get; set; }
//public CriterionType? CriterionTypeEnum { get; set; }
}
public class BatchUpdateEmailTopicCommand
{
[NotDefault]
public Guid Id { get; set; }
public string EmailTopic { get; set; } = string.Empty;
public string EmailTopicCN { get; set; } = string.Empty;
}

View File

@ -3,6 +3,7 @@
// 生成时间 2022-03-28 16:43:52
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using DocumentFormat.OpenXml.Wordprocessing;
using IRaCIS.Core.Infra.EFCore.Common;
using System.ComponentModel.DataAnnotations;
@ -63,6 +64,26 @@ namespace IRaCIS.Core.Application.ViewModel
}
public class ModuleTypeData
{
public bool IsShow { get; set; }
public Guid Id { get; set; }
public Guid? ParentId { get; set; }
public Guid DictionaryId { get; set; }
public int ShowOrder { get; set; }
public string DictionaryValue { get; set; }
}
public class GetModuleTypeListInDto
{
public Guid TrialId { get; set; }
}
/// <summary>
/// 复制其他对象到当前对象
/// </summary>
@ -148,6 +169,11 @@ namespace IRaCIS.Core.Application.ViewModel
public Guid UpdateUserId { get; set; }
public string Code { get; set; } = string.Empty;
/// <summary>
/// 是否默认选择
/// </summary>
public bool IsDefaultChoice { get; set; } = false;
/// <summary>
/// 字段的英文值
/// </summary>

View File

@ -182,13 +182,12 @@ namespace IRaCIS.Core.Application.Service
/// <summary>
/// 获取子项数组
/// </summary>
/// <param name="parentId"></param>
/// <returns></returns>
[HttpGet("{parentId:guid}")]
public async Task<List<BasicDicView>> GetChildList(Guid parentId)
[HttpPost]
public async Task<List<BasicDicView>> GetChildList(ChildInQuery inQuery)
{
return await _dicRepository.Where(t => t.ParentId == parentId)
.OrderBy(t => t.ShowOrder).ProjectTo<BasicDicView>(_mapper.ConfigurationProvider).ToListAsync();
return await _dicRepository.Where(t => t.ParentId == inQuery.ParentId)
.ProjectTo<BasicDicView>(_mapper.ConfigurationProvider).SortToListAsync(inQuery);
}

View File

@ -28,7 +28,7 @@ namespace IRaCIS.Core.Application.Contracts
.WhereIf(inQuery.SystemLevel != null, t => t.SystemLevel == inQuery.SystemLevel)
.WhereIf(inQuery.IsDistinguishCriteria != null, t => t.IsDistinguishCriteria == inQuery.IsDistinguishCriteria)
.WhereIf(inQuery.BusinessLevelEnum != null, t => t.BusinessLevelEnum == inQuery.BusinessLevelEnum)
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeEnum == inQuery.CriterionTypeEnum)
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeList.Any(t=>t==inQuery.CriterionTypeEnum))
.WhereIf(inQuery.BusinessModuleEnum != null, t => t.BusinessModuleEnum == inQuery.BusinessModuleEnum)
.WhereIf(inQuery.BusinessScenarioEnum != null, t => t.BusinessScenarioEnum == inQuery.BusinessScenarioEnum)
.WhereIf(inQuery.IsReturnRequired != null, t => t.IsReturnRequired == inQuery.IsReturnRequired)
@ -41,16 +41,77 @@ namespace IRaCIS.Core.Application.Contracts
return await emailNoticeConfigQueryable.ToPagedListAsync(inQuery);
}
/// <summary>
/// 批量更新邮件主题中英文
/// </summary>
/// <param name="inCommandList"></param>
/// <returns></returns>
public async Task<IResponseOutput> BatchUpdateEmail(List<BatchUpdateEmailTopicCommand> inCommandList)
{
var findIdList = inCommandList.Select(x => x.Id).ToList();
var regex = new Regex(@"\{\s*\d+\s*\}");
foreach (var inCommand in inCommandList)
{
if (regex.Matches($"{inCommand.EmailTopic}{inCommand.EmailTopicCN}")
.Any(t => t.Value.Contains(" ")))
{
//邮件模板占位符不允许有空格,请核查占位符的地方
return ResponseOutput.NotOk(I18n.T("EmailNoticeConfig_ContainEmpty"));
}
}
var list = _emailNoticeConfigrepository.Where(t => findIdList.Contains(t.Id), true).ToList();
foreach (var item in list)
{
var exist = inCommandList.FirstOrDefault(t => t.Id == item.Id);
if (exist != null)
{
item.EmailTopic = exist.EmailTopic;
item.EmailTopicCN = exist.EmailTopicCN;
}
}
await _emailNoticeConfigrepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
public async Task<IResponseOutput> AddOrUpdateEmailNoticeConfig(EmailNoticeConfigAddOrEdit addOrEditEmailNoticeConfig)
{
var verifyExp1 = new EntityVerifyExp<EmailNoticeConfig>()
//var verifyExp1 = new EntityVerifyExp<EmailNoticeConfig>()
//{
// VerifyExp = t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.CriterionTypeEnum == addOrEditEmailNoticeConfig.CriterionTypeEnum,
// VerifyMsg = _localizer["EmailNoticeConfig_RepeatEmailScenario"]
//};
var criterionList = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.IsEnable == true)
.Where(t => t.CriterionTypeList != null)
.WhereIf(addOrEditEmailNoticeConfig.Id != null, t => t.Id != addOrEditEmailNoticeConfig.Id)
.Select(t => t.CriterionTypeList).ToList();//不能使用selectMany 会当成关联对象,不能当成字符串
if (addOrEditEmailNoticeConfig.CriterionTypeList != null)
{
VerifyExp = t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.CriterionTypeEnum == addOrEditEmailNoticeConfig.CriterionTypeEnum,
foreach (var item in addOrEditEmailNoticeConfig.CriterionTypeList)
{
foreach (var itemList in criterionList)
{
if (itemList.Any(t => t == item))
{
return ResponseOutput.NotOk(_localizer["EmailNoticeConfig_RepeatEmailScenario"]);
}
}
}
}
VerifyMsg = _localizer["EmailNoticeConfig_RepeatEmailScenario"]
};
var verifyExp2 = new EntityVerifyExp<EmailNoticeConfig>()
{
@ -94,7 +155,7 @@ namespace IRaCIS.Core.Application.Contracts
}
await _emailNoticeConfigrepository.AddAsync(entity, true, verifyExp1, verifyExp2);
await _emailNoticeConfigrepository.AddAsync(entity, true, verifyExp2);
}
@ -117,7 +178,7 @@ namespace IRaCIS.Core.Application.Contracts
}
entity = await _emailNoticeConfigrepository.UpdateFromDTOAsync(addOrEditEmailNoticeConfig, true, false, verifyExp1, verifyExp2);
entity = await _emailNoticeConfigrepository.UpdateFromDTOAsync(addOrEditEmailNoticeConfig, true, false, verifyExp2);
@ -125,16 +186,16 @@ namespace IRaCIS.Core.Application.Contracts
}
if (entity.EmailCron != string.Empty)
if (addOrEditEmailNoticeConfig.EmailCron != string.Empty)
{
var jobId = $"{entity.Id}_({entity.BusinessScenarioEnum})";
var jobId = $"{entity.Id}_({addOrEditEmailNoticeConfig.BusinessScenarioEnum})";
//有的job 可能编辑控制直接不发,需要移除已存在的
HangfireJobHelper.RemoveCronJob(jobId);
//有的job 可能编辑控制直接不发,需要移除已存在的
if (entity.IsAutoSend && entity.IsEnable)
{
HangfireJobHelper.AddOrUpdateSystemCronJob(jobId, entity.BusinessScenarioEnum, entity.EmailCron);
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, addOrEditEmailNoticeConfig.BusinessScenarioEnum, addOrEditEmailNoticeConfig.EmailCron);
}
}

View File

@ -48,6 +48,54 @@ namespace IRaCIS.Core.Application.Service.Common
{
[HttpPost]
public async Task<IActionResult> GetTrialVisitImageStatList_Export(TrialVisitImageQuery inQuery,
[FromServices] IRepository<SubjectVisit> _subjectVisitRepository,
[FromServices] IRepository<Trial> _trialRepository,
[FromServices] IDictionaryService _dictionaryService)
{
var query = _subjectVisitRepository.Where(t => t.TrialId == inQuery.TrialId)
.WhereIf(inQuery.TrialSiteId != null, t => t.TrialSiteId == inQuery.TrialSiteId)
.WhereIf(inQuery.SubjectCode.IsNotNullOrEmpty(), t => t.Subject.Code.Contains(inQuery.SubjectCode))
.WhereIf(inQuery.BeginScanDate != null, t => t.LatestScanDate >= inQuery.BeginScanDate)
.WhereIf(inQuery.EndScanDate != null, t => t.LatestScanDate == inQuery.EndScanDate)
.Select(t => new TrialVisitImageStatView()
{
TrialId = t.TrialId,
SubjectVisitId = t.Id,
SubjectCode = t.Subject.Code,
TrialSiteCode = t.TrialSite.TrialSiteCode,
TrialSiteId = t.TrialSiteId,
VisitName = t.VisitName,
VisitNum = t.VisitNum,
EarliestScanDate = t.EarliestScanDate,
LatestScanDate = t.LatestScanDate,
IsHaveDicom = t.StudyList.Any(),
IsHaveNoneDicom = t.NoneDicomStudyList.Any(),
TotalStudyCount = t.StudyList.Count() + t.NoneDicomStudyList.Count(),
TotalImageCount = t.StudyList.Sum(t => t.InstanceCount) + t.NoneDicomStudyList.Sum(t => t.FileCount),
TotalImageSize = t.StudyList.SelectMany(t => t.InstanceList).Sum(t => t.FileSize) + t.NoneDicomStudyList.SelectMany(t => t.NoneDicomFileList).Sum(t => t.FileSize),
});
var list = await query.Where(t => t.TotalImageCount > 0).SortToListAsync(inQuery);
var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo<ExcelExportInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
return await ExcelExportHelper.DataExportAsync(StaticData.Export.TriaVisitImageStat_Export, exportInfo, exportInfo.TrialCode, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(TrialMaintenanceDTO));
}
#region 后续需要移动过来
@ -1686,7 +1734,7 @@ namespace IRaCIS.Core.Application.Service.Common
//.WhereIf(inQuery.SystemLevel == null, t => t.SystemLevel == SysEmailLevel.not_sys)
.WhereIf(inQuery.SystemLevel != null, t => t.SystemLevel == inQuery.SystemLevel)
.WhereIf(inQuery.IsDistinguishCriteria != null, t => t.IsDistinguishCriteria == inQuery.IsDistinguishCriteria)
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeEnum == inQuery.CriterionTypeEnum)
.WhereIf(inQuery.CriterionTypeEnum != null, t =>t.CriterionTypeList.Any(c=>c==inQuery.CriterionTypeEnum))
.WhereIf(inQuery.BusinessScenarioEnum != null, t => t.BusinessScenarioEnum == inQuery.BusinessScenarioEnum)
.WhereIf(inQuery.IsReturnRequired != null, t => t.IsReturnRequired == inQuery.IsReturnRequired)
.WhereIf(inQuery.IsEnable != null, t => t.IsEnable == inQuery.IsEnable)
@ -1827,7 +1875,7 @@ namespace IRaCIS.Core.Application.Service.Common
{
//两个人都做了
if (resultExceptJudgeList.Count(t => t.VisitTaskNum == item.VisitTaskNum && t.SubjectCode == item.SubjectCode) == 2)
if (resultExceptJudgeList.Where(t => t.VisitTaskNum == item.VisitTaskNum && t.SubjectCode == item.SubjectCode).Select(t => t.ArmEnum).Distinct().Count() == 2)
{
//如果没有产生裁判默认选择R1
if (item.ArmEnum == Arm.DoubleReadingArm1)
@ -1882,6 +1930,7 @@ namespace IRaCIS.Core.Application.Service.Common
//已完成的全局数量
finishedGlobalCount = resultExceptJudgeList.Where(t => t.SubjectCode == visitItem.SubjectCode && subjectReadingPeriondVisitNumList.Any(c => (c + addReadingPeriodNum) == t.VisitTaskNum)
/*&& t.ReadingCategory == ReadingCategory.Global*/)
.Select(t => new { t.VisitTaskNum, t.ArmEnum }).Distinct()
.GroupBy(t => t.VisitTaskNum).Where(g => g.Count() == 2).Select(g => g.Key).Count();
}
@ -1895,7 +1944,8 @@ namespace IRaCIS.Core.Application.Service.Common
var subjectMaxFinishedGlobalTaskNum = resultExceptJudgeList.Where(t => t.SubjectCode == visitItem.SubjectCode && subjectReadingPeriondVisitNumList.Any(c => (c + addReadingPeriodNum) == t.VisitTaskNum)
/*&& t.ReadingCategory == ReadingCategory.Global*/)
.GroupBy(t => t.VisitTaskNum).Where(g => g.Count() == 2).Select(g => g.Key).DefaultIfEmpty().Max();
.Select(t => new { t.VisitTaskNum, t.ArmEnum }).Distinct()
.GroupBy(t => t.VisitTaskNum).Where(g => g.Count() == 2).Select(g => g.Key).DefaultIfEmpty().Max();
//最大的完成的全局是否产生裁判
if (subjectJudgeList.Any(t => t.VisitTaskNum == (subjectMaxFinishedGlobalTaskNum + ReadingCommon.TaskNumDic[ReadingCategory.Judge])))
@ -2505,15 +2555,34 @@ namespace IRaCIS.Core.Application.Service.Common
// CDISC 导出 只管到 外层问题层级 和阅片结果表是保持一致
else if (inQuery.ReadingExportType == ExportResult.CDISC)
{
list = await query.ProjectTo<CommonEvaluationExport>(_mapper.ConfigurationProvider,
new
{
readingExportType = inQuery.ReadingExportType,
criterionType = criterion.CriterionType,
arbitrationRule = criterion.ArbitrationRule,
trialReadingCriterionId = inQuery.TrialReadingCriterionId,
isEn_Us = _userInfo.IsEn_Us
}).ToListAsync();
if (criterion.CriterionType == CriterionType.SelfDefine)
{
taskList = await query.ProjectTo<CommonLessionExport>(_mapper.ConfigurationProvider,
new
{
readingExportType = inQuery.ReadingExportType,
criterionType = criterion.CriterionType,
arbitrationRule = criterion.ArbitrationRule,
trialReadingCriterionId = inQuery.TrialReadingCriterionId,
isEn_Us = _userInfo.IsEn_Us
}).ToListAsync();
}
else
{
list = await query.ProjectTo<CommonEvaluationExport>(_mapper.ConfigurationProvider,
new
{
readingExportType = inQuery.ReadingExportType,
criterionType = criterion.CriterionType,
arbitrationRule = criterion.ArbitrationRule,
trialReadingCriterionId = inQuery.TrialReadingCriterionId,
isEn_Us = _userInfo.IsEn_Us
}).ToListAsync();
}
}
@ -2786,7 +2855,7 @@ namespace IRaCIS.Core.Application.Service.Common
//多表格增加的一列就用Guid.Empty 标识
var extralNameList = new List<DynamicColumnConfig.ColumItem>() { new DynamicColumnConfig.ColumItem() { Id = Guid.Empty, Name = _userInfo.IsEn_Us ? "Table Name" : "表格名称" } };
var tableQuestionNameList = trialConfigTableQuestionList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.TableName + "_" + t.QuestionName }).ToList();
var tableQuestionNameList = trialConfigTableQuestionList.OrderBy(t=>t.TableName).Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.TableName + "_" + t.QuestionName }).ToList();
configCoumNameList = fistLeveLNameList.Union(extralNameList).Union(tableQuestionNameList).ToList();
}
@ -2876,42 +2945,236 @@ namespace IRaCIS.Core.Application.Service.Common
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
CDISCList = new List<string>(),
//CDISCList = new List<string>(),
TranslateDicNameList = translateDicNameList
};
if (export_Template == StaticData.Export.ReadingLession_Export || export_Template == StaticData.Export.CommonJudgeReadingDetail_Export)
{
dynamicColumnConfig.TempalteLastColumnIndex = 8;
}
}
else
{
//CDISC 导出 只到外层问题级别 使用Id 填充Excel
var totalConfigCoumNameList = trialConfigQuestionList.ToList();
var configCoumNameList = totalConfigCoumNameList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.QuestionName }).ToList();
var translateDicList = totalConfigCoumNameList.Where(t => t.TranslateDicName.IsNotNullOrEmpty()).Select(t => t.TranslateDicName).Distinct().ToList();
var cdiscCodeList = totalConfigCoumNameList.Select(t => t.CDISCCode).ToList();
dynamicColumnConfig = new DynamicColumnConfig()
if (criterion.CriterionType == CriterionType.SelfDefine)
{
AutoColumnTitleRowIndex = 1,
AutoColumnStartIndex = 6,
TempalteLastColumnIndex = 10,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "QuestionValue",
DynamicItemTitleName = "QuestionName",
DynamicItemTitleId = "QuestionId",
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
CDISCList = cdiscCodeList,
TranslateDicNameList = translateDicList
};
//最终EXCEL 列
var configCoumNameList = new List<DynamicColumnConfig.ColumItem>();
//表格全问题名
var trialConfigTableQuestionList = _trialReadingTableQuestionRepository.Where(t => t.TrialId == trialId && t.TrialCriterionId == trialReadingCriterionId).Where(t => t.ExportResultStr.Contains(((int)inQuery.ReadingExportType).ToString()))
.Select(t => new ExportQuestionBasicInfo()
{
QuestionId = t.Id,
TableName = _userInfo.IsEn_Us ? t.ReadingQuestionTrial.QuestionEnName : t.ReadingQuestionTrial.QuestionName,
QuestionName = _userInfo.IsEn_Us ? t.QuestionEnName : t.QuestionName,
CDISCCode = t.CDISCCode,
TranslateDicName = t.DictionaryCode,
ShowOrder = t.ShowOrder
}).OrderBy(t => t.TableName).ThenBy(t => t.ShowOrder).ToList();
var isMutiTable = trialConfigTableQuestionList.Select(t => t.TableName).Distinct().Count() > 1;
//外层问题处理
var fistLeveLNameList = trialConfigQuestionList.Select(t => new DynamicColumnConfig.ColumItem()
{
Id = t.QuestionId,
Name = t.QuestionName,
CDISCCode = t.CDISCCode
}).ToList();
var translateDicNameList = trialConfigQuestionList.Union(trialConfigTableQuestionList).Where(t => t.TranslateDicName.IsNotNullOrEmpty()).Select(t => t.TranslateDicName).Distinct().ToList();
//单表格问题(直接用表格问题名称) 或者没有表格问题
if (isMutiTable == false)
{
//一个表格,或者没有表格
var tableNameList = trialConfigTableQuestionList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.QuestionName, CDISCCode = t.CDISCCode }).ToList();
configCoumNameList = fistLeveLNameList.Union(tableNameList).ToList();
//最终的病灶列表 要把裁判的也要加进去,需要处理裁判标记
foreach (var item in taskList.OrderBy(t => t.SubjectCode).ThenBy(t => t.ArmEnum).ThenBy(t => t.VisitTaskNum))
{
//有病灶,并且至少配置了一个病灶问题
if (item.LesionList.Count > 0 && item.LesionList.SelectMany(t => t.LessionAnswerList).Any())
{
foreach (var lession in item.LesionList.OrderBy(t => t.LessionType).ThenBy(t => t.LessionCode))
{
//病灶配置了问题 才进行导出
if (lession.LessionAnswerList.Count > 0)
{
var dynamicLessionInfoList = new List<CommonQuesionInfo>();
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
//两部分组成 外层问题+ 动态的表格问题
dynamicLessionInfoList = item.QuestionAnswerList.Union(dynamicPartialLessionInfoList).ToList();
var cloneItem = item.Clone();
cloneItem.QuestionAnswerList = dynamicLessionInfoList;
list.Add(cloneItem);
}
}
}
else
{
//要把裁判任务加进去 裁判任务上没有病灶
list.Add(item);
}
}
dynamicColumnConfig = new DynamicColumnConfig()
{
AutoColumnTitleRowIndex = 1,
AutoColumnStartIndex = 6,
TempalteLastColumnIndex = 10,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "QuestionValue",
DynamicItemTitleName = "QuestionName",
DynamicItemTitleId = "QuestionId",
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
IsCDISCExport = true,
//CDISCList = cdiscCodeList,
TranslateDicNameList = translateDicNameList
};
}
else
{
//多表格问题,需要用新的模板,并且用行的方式展示 不是之前表格名称_表格子问题名的方式
export_Template = StaticData.Export.CDISC_MutiTable_Reading_Export;
configCoumNameList = fistLeveLNameList.ToList();
var translateUnitList = await _dictionaryService.GetBasicDataSelect("ValueUnit");
foreach (var item in taskList.OrderBy(t => t.SubjectCode).ThenBy(t => t.ArmEnum).ThenBy(t => t.VisitTaskNum))
{
//有病灶,并且至少配置了一个病灶问题
if (item.LesionList.Count > 0 && item.LesionList.SelectMany(t => t.LessionAnswerList).Any())
{
foreach (var lession in item.LesionList.OrderBy(t => t.LessionType).ThenBy(t => t.LessionCode))
{
//病灶配置了问题 才进行导出
if (lession.LessionAnswerList.Count > 0)
{
//遍历病灶的每个问题
foreach (var lessionItem in lession.LessionAnswerList)
{
var cloneItem = item.Clone();
string unitText = lessionItem.Unit switch
{
ValueUnit.Custom => lessionItem.CustomUnit,
null => "",
_ => _userInfo.IsEn_Us ? translateUnitList.FirstOrDefault(t => t.Code == ((int)lessionItem.Unit).ToString())?.Value ?? ""
: translateUnitList.FirstOrDefault(t => t.Code == ((int)lessionItem.Unit).ToString())?.ValueCN ?? ""
};
cloneItem.Group = lessionItem.TableName;
cloneItem.TRLINKID = lessionItem.RowId.ToString();
cloneItem.TRORRES = lessionItem.QuestionValue;
cloneItem.TRTEST = lessionItem.CDISCCode;
cloneItem.TRORRESU = unitText;
list.Add(cloneItem);
}
//var dynamicLessionInfoList = new List<CommonQuesionInfo>();
//var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
////两部分组成 外层问题+ 动态的表格问题
//dynamicLessionInfoList = item.QuestionAnswerList.Union(dynamicPartialLessionInfoList).ToList();
}
}
}
else
{
//要把裁判任务加进去 裁判任务上没有病灶
list.Add(item);
}
}
dynamicColumnConfig = new DynamicColumnConfig()
{
AutoColumnTitleRowIndex = 1,
AutoColumnStartIndex = 6,
TempalteLastColumnIndex = 15,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "QuestionValue",
DynamicItemTitleName = "QuestionName",
DynamicItemTitleId = "QuestionId",
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
IsCDISCExport = true,
//CDISCList = cdiscCodeList,
TranslateDicNameList = translateDicNameList
};
}
}
else
{
//CDISC 导出 只到外层问题级别 使用Id 填充Excel
var totalConfigCoumNameList = trialConfigQuestionList.ToList();
var configCoumNameList = totalConfigCoumNameList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.QuestionName, CDISCCode = t.CDISCCode }).ToList();
var translateDicList = totalConfigCoumNameList.Where(t => t.TranslateDicName.IsNotNullOrEmpty()).Select(t => t.TranslateDicName).Distinct().ToList();
var cdiscCodeList = totalConfigCoumNameList.Select(t => t.CDISCCode).ToList();
dynamicColumnConfig = new DynamicColumnConfig()
{
AutoColumnTitleRowIndex = 1,
AutoColumnStartIndex = 6,
TempalteLastColumnIndex = 10,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "QuestionValue",
DynamicItemTitleName = "QuestionName",
DynamicItemTitleId = "QuestionId",
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
IsCDISCExport = true,
//CDISCList = cdiscCodeList,
TranslateDicNameList = translateDicList
};
}

View File

@ -31,5 +31,7 @@ namespace IRaCIS.Application.Interfaces
Task<Dictionary<string, List<BasicDicSelectCopy>>> GetBasicDataSelect(string[] searchArray);
Task<List<BasicDicSelect>> GetBasicDataSelect(string searchKey);
}
}

View File

@ -0,0 +1,181 @@
using DocumentFormat.OpenXml.EMMA;
using FellowOakDicom;
using IRaCIS.Core.Application.Helper;
using MassTransit;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using static IRaCIS.Core.Domain.Share.StaticData;
namespace IRaCIS.Core.Application.Service
{
/// <summary>
/// 项目影像后台下载,不打包
/// </summary>
/// <param name="_trialRepository"></param>
/// <param name="_oSSService"></param>
[ApiExplorerSettings(GroupName = "Common")]
public class TrialImageDownloadService(IRepository<Trial> _trialRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment) : BaseService
{
/// <summary>
/// 后端api swagger 下载项目影像
/// </summary>
/// <param name="trialId"></param>
/// <returns></returns>
[HttpPost]
[AllowAnonymous]
public async Task<IResponseOutput> DownloadTrialImage(Guid trialId)
{
var subjectCodeList = new List<string>() { "05002", "07006", "07026" };
var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new
{
t.ResearchProgramNo,
VisitList = t.SubjectVisitList.Where(t=>subjectCodeList.Contains(t.Subject.Code)).Select(sv => new
{
TrialSiteCode = sv.TrialSite.TrialSiteCode,
SubjectCode = sv.Subject.Code,
VisitName = sv.VisitName,
StudyList = sv.StudyList.Select(u => new
{
u.PatientId,
u.StudyTime,
u.StudyCode,
SeriesList = u.SeriesList.Select(z => new
{
z.Modality,
InstancePathList = z.DicomInstanceList.Select(k => new
{
k.Path
}).ToList()
})
}).ToList(),
NoneDicomStudyList = sv.NoneDicomStudyList.Select(nd => new
{
nd.Modality,
nd.StudyCode,
nd.ImageDate,
FileList = nd.NoneDicomFileList.Select(file => new
{
file.FileName,
file.Path,
file.FileType
}).ToList()
}).ToList()
}).ToList()
}).FirstOrDefault();
var count = downloadInfo.VisitList.SelectMany(t => t.NoneDicomStudyList).SelectMany(t => t.FileList).Count();
var count2 = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t=>t.InstancePathList).Count();
if (downloadInfo != null)
{
var rootFolder = FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment);
// 获取无效字符(系统定义的)
string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
// 用正则表达式替换所有非法字符为下划线或空字符
string pattern = $"[{Regex.Escape(invalidChars)}]";
var regexNo = Regex.Replace(downloadInfo.ResearchProgramNo, pattern, "_");
// 创建一个临时文件夹来存放文件
string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}_{NewId.NextGuid()}");
Directory.CreateDirectory(trialFolderPath);
foreach (var visitItem in downloadInfo.VisitList)
{
if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0)
{
continue;
}
#region 处理 中心受试者dicom non-dicom 文件夹层级
var siteFolderPath = Path.Combine(trialFolderPath, visitItem.TrialSiteCode);
if (!Directory.Exists(siteFolderPath))
{
Directory.CreateDirectory(siteFolderPath);
}
#endregion
foreach (var studyInfo in visitItem.StudyList)
{
// 遍历 Series
foreach (var seriesInfo in studyInfo.SeriesList)
{
string studyDicomFolderPath = Path.Combine(siteFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}_DICOM", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}");
// 创建 影像 文件夹
Directory.CreateDirectory(studyDicomFolderPath);
// 遍历 InstancePathList
foreach (var instanceInfo in seriesInfo.InstancePathList)
{
// 复制文件到相应的文件夹
string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path));
//下载到当前目录
await _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath);
}
}
}
foreach (var noneDicomStudy in visitItem.NoneDicomStudyList)
{
string studyNoneDicomFolderPath = Path.Combine(siteFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}_Non-DICOM", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}");
Directory.CreateDirectory(studyNoneDicomFolderPath);
foreach (var file in noneDicomStudy.FileList)
{
string destinationPath = Path.Combine(studyNoneDicomFolderPath, Path.GetFileName(file.FileName));
//下载到当前目录
await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath);
}
}
}
}
return ResponseOutput.Ok();
}
}
}

View File

@ -268,6 +268,8 @@ namespace IRaCIS.Application.Contracts
public Guid? HospitalId { get; set; }
public Guid EnrollId { get; set; }
}

View File

@ -3,6 +3,7 @@ using AutoMapper.EquivalencyExpression;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Contracts.Pay;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
namespace IRaCIS.Core.Application.Service
@ -212,6 +213,8 @@ namespace IRaCIS.Core.Application.Service
.ForMember(d => d.Code, u => u.MapFrom(s => s.DoctorUser.UserName))
.ForMember(d => d.HospitalName, u => u.MapFrom(s => s.Doctor.HospitalName))
.ForMember(d => d.HospitalNameCN, u => u.MapFrom(s => s.Doctor.HospitalNameCN))
.ForMember(d => d.EnrollId, u => u.MapFrom(s => s.Id))
;
CreateMap<Doctor, ConfirmationReviewerDTO>();
CreateMap<Hospital, ConfirmationReviewerDTO>();

View File

@ -365,6 +365,57 @@ namespace IRaCIS.Core.Application.Contracts
public List<Guid> Ids { get; set; }
}
public class PublishTrialDocumentInDto
{
public List<Guid> Ids { get; set; }
}
public class TrialDocumentAttachmentView : TrialDocumentAttachmentAddOrEdit
{
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
}
public class TrialDocumentAttachmentAddOrEdit
{
public Guid? Id { get; set; }
public string FileFormat { get; set; }
public string FileName { get; set; }
public string FilePath { get; set; }
public decimal? FileSize { get; set; }
public string Name { get; set; }
public bool OffLine { get; set; }
public Guid TrialDocumentId { get; set; }
}
public class TrialDocumentAttachmentQuery : PageInput
{
public string? FileFormat { get; set; }
public string? FileName { get; set; }
public string? FilePath { get; set; }
public decimal? FileSize { get; set; }
public string? Name { get; set; }
public bool? OffLine { get; set; }
public Guid? TrialDocumentId { get; set; }
}
public class AddOrEditSystemDocument : SystemDocumentAddOrEdit
{

View File

@ -13,6 +13,7 @@ namespace IRaCIS.Core.Application.Contracts
{
public string FullFilePath { get; set; } = String.Empty;
public int AttachmentCount { get; set; }
public bool IsSomeUserSigned { get; set; }
public DateTime CreateTime { get; set; }
@ -60,6 +61,24 @@ namespace IRaCIS.Core.Application.Contracts
public int SignViewMinimumMinutes { get; set; }
/// <summary>
/// 现有员工培训天数
/// </summary>
public int? CurrentStaffTrainDays { get; set; }
/// <summary>
/// 新员工培训天数
/// </summary>
public int? NewStaffTrainDays { get; set; }
/// <summary>
/// 是否发布
/// </summary>
public bool IsPublish { get; set; } = true;
}
public class AddOrEditTrialDocument : TrialDocumentAddOrEdit

View File

@ -99,7 +99,7 @@ namespace IRaCIS.Core.Application.ViewModel
public EmailBusinessScenario? BusinessScenarioEnum { get; set; }
//public CriterionType? CriterionTypeEnum { get; set; }
public CriterionType? CriterionTypeEnum { get; set; }
public Guid? TrialReadingCriterionId { get; set; }
@ -146,9 +146,6 @@ namespace IRaCIS.Core.Application.ViewModel
public Guid TrialId { get; set; }
public CriterionType? CriterionTypeEnum { get; set; }
public List<UserTypeEnum> ToUserTypeList { get; set; }
public List<UserTypeEnum> CopyUserTypeList { get; set; }
@ -197,6 +194,11 @@ namespace IRaCIS.Core.Application.ViewModel
public int? EmailDelaySeconds { get; set; }
[Comment("邮件配置的多个标准")]
public List<CriterionType>? CriterionTypeList { get; set; }
public bool IsDistinguishCriteria { get; set; }
}

View File

@ -14,7 +14,9 @@ namespace IRaCIS.Core.Application.ViewModel;
public class TrialFinalRecordView : TrialFinalRecordAddOrEdit
{
public int HistoryCount { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }

View File

@ -0,0 +1,56 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-06-09 02:09:12Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Domain.Share;
using System.Collections.Generic;
namespace IRaCIS.Core.Application.Service.DTO;
public class TrialHistoryRecordFileView : TrialHistoryRecordFileAddOrEdit
{
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
}
public class TrialHistoryRecordFileAddOrEdit
{
public Guid? Id { get; set; }
public string FileFormat { get; set; }
public string FileName { get; set; }
public string FilePath { get; set; }
public string FileSize { get; set; }
public Guid TrialFileTypeId { get; set; }
public Guid TrialRecordId { get; set; }
}
public class TrialHistoryRecordFileQuery:PageInput
{
public string? FileFormat { get; set; }
public string? FileName { get; set; }
public string? FilePath { get; set; }
public string? FileSize { get; set; }
public Guid? TrialFileTypeId { get; set; }
public Guid? TrialRecordId { get; set; }
}

View File

@ -11,7 +11,7 @@ namespace IRaCIS.Core.Application.ViewModel;
public class TrialNormalRecordView : TrialNormalRecordAddOrEdit
{
public int HistoryCount { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }

View File

@ -11,7 +11,7 @@ namespace IRaCIS.Core.Application.ViewModel;
public class TrialTrianingRecordView : TrialTrianingRecordAddOrEdit
{
public int HistoryCount { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }

View File

@ -11,7 +11,7 @@ namespace IRaCIS.Core.Application.Service
{
Task SendEnrollOrPdEmail(Guid visitTaskId, bool? isEnrollment, bool? isPDConfirm);
Task<(TrialEmailNoticeConfig?, SMTPEmailConfig?)> BuildEmailConfig(Guid trialId, EmailBusinessScenario businessScenario, Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc, Guid? siteId = null, Guid? trialReadingCriterionId = null);
Task<(TrialEmailNoticeConfig?, SMTPEmailConfig?)> BuildEmailConfig(Guid trialId, EmailBusinessScenario businessScenario, Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc, Guid? siteId = null);
}
public class EmailSendService(IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
@ -102,9 +102,9 @@ namespace IRaCIS.Core.Application.Service
public async Task SendTrialEmailAsync(Guid trialId, EmailBusinessScenario businessScenario, Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc, Guid? trialSiteId = null, Guid? trialReadingCriterionId = null, Func<TrialEmailNoticeConfig, SMTPEmailConfig, SMTPEmailConfig> emailAttachFunc = null)
public async Task SendTrialEmailAsync(Guid trialId, EmailBusinessScenario businessScenario, Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc, Guid? trialSiteId = null, Func<TrialEmailNoticeConfig, SMTPEmailConfig, SMTPEmailConfig> emailAttachFunc = null)
{
var (trialEmailConfig, sendEmailConfig) = await BuildEmailConfig(trialId, businessScenario, topicAndHtmlFunc, trialSiteId, trialReadingCriterionId);
var (trialEmailConfig, sendEmailConfig) = await BuildEmailConfig(trialId, businessScenario, topicAndHtmlFunc, trialSiteId);
if (sendEmailConfig != null)
{
@ -114,10 +114,10 @@ namespace IRaCIS.Core.Application.Service
}
public async Task<(TrialEmailNoticeConfig?, SMTPEmailConfig?)> BuildEmailConfig(Guid trialId, EmailBusinessScenario businessScenario, Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc, Guid? trialSiteId = null, Guid? trialReadingCriterionId = null)
public async Task<(TrialEmailNoticeConfig?, SMTPEmailConfig?)> BuildEmailConfig(Guid trialId, EmailBusinessScenario businessScenario, Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc, Guid? trialSiteId = null)
{
//找到配置
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.TrialReadingCriterionId == trialReadingCriterionId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
.Include(t => t.TrialEmailNoticeUserList).Include(t => t.TrialEmailBlackUserList).FirstOrDefaultAsync();

View File

@ -0,0 +1,23 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-06-09 02:09:12Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Infrastructure.Extention;
using System.Threading.Tasks;
using IRaCIS.Core.Application.Service.DTO;
namespace IRaCIS.Core.Application.Interfaces;
public interface ITrialHistoryRecordFileService
{
Task<PageOutput<TrialHistoryRecordFileView>> GetTrialHistoryRecordFileList(TrialHistoryRecordFileQuery inQuery);
Task<IResponseOutput> AddOrUpdateTrialHistoryRecordFile(TrialHistoryRecordFileAddOrEdit addOrEditTrialHistoryRecordFile);
Task<IResponseOutput> DeleteTrialHistoryRecordFile(Guid trialHistoryRecordFileId);
}

View File

@ -6,11 +6,14 @@
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using MassTransit.Mediator;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using NPOI.SS.Formula.Functions;
using System.Linq;
using System.Linq.Dynamic.Core;
@ -23,11 +26,13 @@ namespace IRaCIS.Core.Application.Services
[ApiExplorerSettings(GroupName = "Trial")]
public class TrialDocumentService(IRepository<TrialDocument> _trialDocumentRepository,
IRepository<Trial> _trialRepository,
IRepository<TrialDocumentAttachment> _trialDocumentAttachmentRepository,
ISystemDocumentService _systemDocumentService,
IRepository<SystemDocConfirmedIdentityUser> _systemDocConfirmedUserRepository,
IRepository<SystemDocNeedConfirmedUserType> _systemDocNeedConfirmedUserTypeRepository,
IRepository<TrialDocNeedConfirmedUserType> _trialDocNeedConfirmedUserTypeRepository,
IRepository<SystemDocument> _systemDocumentRepository,
IServiceScopeFactory serviceScopeFactory,
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<IdentityUser> _identityUserRepository,
@ -36,7 +41,101 @@ namespace IRaCIS.Core.Application.Services
{
/// <summary>
/// 获取项目文档附件列表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<TrialDocumentAttachmentView>> GetTrialDocumentAttachmentList(TrialDocumentAttachmentQuery inQuery)
{
var trialDocumentAttachmentQueryable = _trialDocumentAttachmentRepository
.WhereIf(inQuery.TrialDocumentId!=null,x=>x.TrialDocumentId==inQuery.TrialDocumentId)
.WhereIf(inQuery.Name != null, x => x.Name.Contains(inQuery.Name))
.WhereIf(inQuery.FileFormat != null, x => x.FileFormat == inQuery.FileFormat)
.ProjectTo<TrialDocumentAttachmentView>(_mapper.ConfigurationProvider);
var pageList = await trialDocumentAttachmentQueryable.ToPagedListAsync(inQuery);
return pageList;
}
/// <summary>
/// 添加或更新项目文档附件
/// </summary>
/// <param name="addOrEditTrialDocumentAttachment"></param>
/// <returns></returns>
public async Task<IResponseOutput> AddOrUpdateTrialDocumentAttachment(TrialDocumentAttachmentAddOrEdit addOrEditTrialDocumentAttachment)
{
// 在此处拷贝automapper 映射
var entity = await _trialDocumentAttachmentRepository.InsertOrUpdateAsync(addOrEditTrialDocumentAttachment, true);
return ResponseOutput.Ok(entity.Id.ToString());
}
/// <summary>
/// 删除项目文档附件
/// </summary>
/// <param name="trialDocumentAttachmentId"></param>
/// <returns></returns>
[HttpDelete("{trialDocumentAttachmentId:guid}")]
public async Task<IResponseOutput> DeleteTrialDocumentAttachment(Guid trialDocumentAttachmentId)
{
var success = await _trialDocumentAttachmentRepository.DeleteFromQueryAsync(t => t.Id == trialDocumentAttachmentId, true);
return ResponseOutput.Ok();
}
/// <summary>
/// 发布项目文档
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> PublishTrialDocument(PublishTrialDocumentInDto inDto)
{
await _trialDocumentRepository.BatchUpdateNoTrackingAsync(x => inDto.Ids.Contains(x.Id), x => new TrialDocument()
{
IsPublish = true,
IsDeleted = false,
});
Console.WriteLine("开始 发布项目文档");
Task.Run(async () =>
{
// 创建独立作用域
using (var scope = serviceScopeFactory.CreateScope())
{
// 从新作用域解析服务
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new TrialDocumentPublishEvent { Ids = inDto.Ids });
}
});
return ResponseOutput.Result(true);
}
/// <summary>
/// 测试定时发送
/// </summary>
/// <returns></returns>
public async Task<IResponseOutput> TestPush()
{
Task.Run(async () =>
{
// 创建独立作用域
using (var scope = serviceScopeFactory.CreateScope())
{
// 从新作用域解析服务
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new TrialDocumentErverDayEvent { });
}
});
return ResponseOutput.Result(true);
}
/// <summary>
/// Setting 界面的 项目所有文档列表
@ -74,6 +173,8 @@ namespace IRaCIS.Core.Application.Services
TrialCode = trialDoc.Trial.TrialCode,
ResearchProgramNo = trialDoc.Trial.ResearchProgramNo,
ExperimentName = trialDoc.Trial.ExperimentName,
CurrentStaffTrainDays = trialDoc.CurrentStaffTrainDays,
NewStaffTrainDays= trialDoc.NewStaffTrainDays,
Id = trialDoc.Id,
IsSystemDoc = false,
CreateTime = trialDoc.CreateTime,
@ -415,15 +516,27 @@ namespace IRaCIS.Core.Application.Services
var sysids = result.CurrentPageData.Where(t => t.IsSystemDoc == true).Select(t => t.Id).ToList();
var sysDataList = await _systemDocumentRepository.Where(x => sysids.Contains(x.Id)).Include(x => x.SystemDocumentAttachmentList).ToListAsync();
result.CurrentPageData.ForEach(x =>
{
if (sysDataList.Any(y => y.Id == x.Id))
{
x.AttachmentCount = sysDataList.Where(y => y.Id == x.Id).Select(x => x.SystemDocumentAttachmentList.Where(z => !z.OffLine).Count()).FirstOrDefault();
}
});
var trialids = result.CurrentPageData.Where(t => t.IsSystemDoc == false).Select(t => t.Id).ToList();
var trialDataList = await _trialDocumentRepository.Where(x => trialids.Contains(x.Id)).Include(x => x.TrialDocumentAttachmentList).ToListAsync();
result.CurrentPageData.ForEach(x =>
{
if (trialDataList.Any(y => y.Id == x.Id))
{
x.AttachmentCount = trialDataList.Where(y => y.Id == x.Id).Select(x => x.TrialDocumentAttachmentList.Where(z => !z.OffLine).Count()).FirstOrDefault();
}
});
#endregion
@ -944,7 +1057,9 @@ namespace IRaCIS.Core.Application.Services
var document = (await _trialDocumentRepository.Where(t => t.Id == addOrEditTrialDocument.Id, true).Include(t => t.NeedConfirmedUserTypeList).FirstOrDefaultAsync()).IfNullThrowException();
bool beforeIsPublish = document.IsPublish;
bool beforeIsDeleted = document.IsDeleted;
var beforeUserTypeIds = document.NeedConfirmedUserTypeList.Select(x => x.NeedConfirmUserTypeId).ToList();
_mapper.Map(addOrEditTrialDocument, document);
@ -973,7 +1088,37 @@ namespace IRaCIS.Core.Application.Services
var success = await _trialDocumentRepository.SaveChangesAsync();
// 检查是否需要发送邮件给新增的角色
if (beforeIsPublish && document.IsPublish && !beforeIsDeleted && !document.IsDeleted)
{
// 找出新增的用户角色ID
var newUserTypeIds = addOrEditTrialDocument.NeedConfirmedUserTypeIdList
.Where(id => !beforeUserTypeIds.Contains(id))
.ToList();
if (newUserTypeIds.Any() && newUserTypeIds.Count() > 0)
{
// 发送邮件给新增的角色
Console.WriteLine("开始 发送项目文档更新邮件给新增角色");
Console.WriteLine(string.Join(",", newUserTypeIds));
Task.Run(async () =>
{
// 创建独立作用域
using (var scope = serviceScopeFactory.CreateScope())
{
// 从新作用域解析服务
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
// 只发送给新增的角色
await mediator.Publish(new TrialDocumentPublishEvent
{
Ids = new List<Guid> { document.Id },
NewUserTypeIds = newUserTypeIds
});
}
});
}
}
return ResponseOutput.Ok(document.Id.ToString());
}
}

View File

@ -4,11 +4,13 @@
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using Amazon.Runtime;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Domain.Share.Common;
@ -116,21 +118,29 @@ namespace IRaCIS.Core.Application.Service
}
private async Task<bool> DealMedicalReviewTasKGenerateAndIsSendAsync(Guid trialId, bool? isHandSend, string pdAnswer, List<Guid> taskIdList, List<Guid> minUserIdList)
/// <summary>
///
/// </summary>
/// <param name="trialId"></param>
/// <param name="isHandSend">为空 代表 正常任务自动发送为true 代表医学审核手动发送 为false 代表医学审核自动发送 </param></param>
/// <param name="pdAnswer"></param>
/// <param name="taskIdList"></param>
/// <param name="minUserIdList"></param>
/// <returns></returns>
private async Task<bool> DealMedicalReviewTasKGenerateAndIsSendAsync(Guid trialId, bool? isHandSend, string answer, List<Guid> taskIdList, List<Guid> minUserIdList)
{
var isNeedSend = true;
//手动发送的时候,也有可能答案是是 此时是 这里不发送,发送已经生成的文件
if (pdAnswer == "是" && isHandSend == null)
if (answer == "是" && isHandSend == null)
{
isNeedSend = true;
}
else
{
//正常阅片为否的
//正常阅片自动发送,阅片为否的产生医学审核
if (isHandSend == null)
{
isNeedSend = false;
@ -153,12 +163,12 @@ namespace IRaCIS.Core.Application.Service
}
else if (isHandSend == true)
{
//手动发送
//医学审核手动发送
isNeedSend = false;
}
else
{
// 医学审核确认未否了 才发
// 医学审核自动发送
isNeedSend = true;
}
}
@ -167,6 +177,8 @@ namespace IRaCIS.Core.Application.Service
}
/// <summary>
/// 测试邮件 带附件 填充word
/// </summary>
@ -288,7 +300,7 @@ namespace IRaCIS.Core.Application.Service
};
var (trialEmailConfig, sendEmailConfig) = await _emailSendService.BuildEmailConfig(taskInfo.TrialId, businessScenarioEnum, topicAndHtmlFunc, taskInfo.TrialSiteId, taskInfo.TrialReadingCriterionId);
var (trialEmailConfig, sendEmailConfig) = await _emailSendService.BuildEmailConfig(taskInfo.TrialId, businessScenarioEnum, topicAndHtmlFunc, taskInfo.TrialSiteId);
#endregion
@ -298,7 +310,7 @@ namespace IRaCIS.Core.Application.Service
{
#region 不同标准 不同项目配置 发送邮件的时机 处理具体逻辑
var answer = "否";
var answer = string.Empty;
var isNeedSend = true;
var minUserIdList = _trialUserRoleRepository.Where(t => t.UserRole.UserTypeEnum == Domain.Share.UserTypeEnum.MIM && t.TrialId == taskInfo.TrialId).Select(t => t.UserId).ToList();
@ -307,11 +319,7 @@ namespace IRaCIS.Core.Application.Service
//入组确认 根据每个标准配置的是否自动发送,发送邮件与否
if (businessScenarioEnum == EmailBusinessScenario.EnrollConfirmed)
{
if (await _readingTableQuestionAnswerRepository.Where().AnyAsync(x => x.VisitTaskId == visitTaskId && x.Answer == TargetState.Exist.GetEnumInt() &&
x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.State && x.ReadingQuestionTrial.LesionType == LesionType.TargetLesion))
{
answer = "是";
}
//如果其他阅片人已经做了,说明发送了入组确认报告,第二个人做完就不发送了
@ -328,22 +336,11 @@ namespace IRaCIS.Core.Application.Service
}
else
{
bool isEnroll = false;
(answer, isEnroll) = await DealEnrollAnswer(visitTaskId, (Guid)taskInfo.SourceSubjectVisitId, taskInfo.CriterionType, taskInfo.TrialReadingCriterionId);
isNeedSend = await DealMedicalReviewTasKGenerateAndIsSendAsync(taskInfo.TrialId, isHandSend, answer, new List<Guid>() { visitTaskId }, minUserIdList);
if (answer == "是")
{
//把另外一个人的任务设置为不加急(如果项目加急是否 subject 加急是否)
var urgent = _subjectVisitRepository.Where(t => t.Id == taskInfo.SourceSubjectVisitId).Select(t => new { IsSubjectUrgent = t.Subject.IsUrgent, t.Trial.IsUrgent }).FirstOrDefault();
if (urgent?.IsUrgent == false || urgent?.IsSubjectUrgent == false)
{
await _visitTaskRepository.BatchUpdateNoTrackingAsync(t => t.SourceSubjectVisitId == taskInfo.SourceSubjectVisitId && t.TaskState == TaskState.Effect && t.IsAnalysisCreate == false &&
t.Id != visitTaskId && t.TrialReadingCriterionId == taskInfo.TrialReadingCriterionId, u => new VisitTask() { IsUrgent = false });
}
}
}
}
else if (businessScenarioEnum == EmailBusinessScenario.PDConfirmed)
@ -630,6 +627,7 @@ namespace IRaCIS.Core.Application.Service
var path = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, _userInfo.IsEn_Us ? trialEmailConfig.AttachPath : trialEmailConfig.AttachCNPath);
//获取从word 到 PDF的路径
var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetSubjectEnrollConfirmOrPDEmailPath(_hostEnvironment, Path.GetFileName(path), taskInfo.TrialId, taskInfo.TrialSiteId, taskInfo.SubjectId, true);
if (emailStoreMode == EmailStoreSendMode.StoreLocalSend || emailStoreMode == EmailStoreSendMode.OnlyStoreLocalNotSentEmail)
@ -653,10 +651,11 @@ namespace IRaCIS.Core.Application.Service
{
var wordStoreServerPath = Path.Combine(Path.GetDirectoryName(serverFilePath), Path.GetFileNameWithoutExtension(serverFilePath) + ".docx");
using (FileStream fileStream = new FileStream(wordStoreServerPath, FileMode.Create, FileAccess.Write))
{
wordMemoryStream.WriteTo(fileStream);
}
//using (FileStream fileStream = new FileStream(wordStoreServerPath, FileMode.Create, FileAccess.Write))
//{
// wordMemoryStream.WriteTo(fileStream);
//}
FileConvertHelper.ConvertWordToPdf(wordStoreServerPath, Path.GetDirectoryName(serverFilePath));
File.Delete(wordStoreServerPath);
@ -696,10 +695,10 @@ namespace IRaCIS.Core.Application.Service
{
var wordStoreServerPath = Path.Combine(Path.GetDirectoryName(serverFilePath), Path.GetFileNameWithoutExtension(serverFilePath) + ".docx");
using (FileStream fileStream = new FileStream(wordStoreServerPath, FileMode.Create, FileAccess.Write))
{
wordMemoryStream.WriteTo(fileStream);
}
//using (FileStream fileStream = new FileStream(wordStoreServerPath, FileMode.Create, FileAccess.Write))
//{
// wordMemoryStream.WriteTo(fileStream);
//}
FileConvertHelper.ConvertWordToPdf(wordStoreServerPath, Path.GetDirectoryName(serverFilePath));
File.Delete(wordStoreServerPath);
@ -1042,7 +1041,96 @@ namespace IRaCIS.Core.Application.Service
}
/// <summary>
///
/// 获取入组结果
/// </summary>
/// <param name="visitTaskId"></param>
/// <param name="subjectVisitId"></param>
/// <param name="criterionType"></param>
/// <param name="trialReadingCriterionId"></param>
/// <returns></returns>
private async Task<(string enrollAnswer, bool isEnroll)> DealEnrollAnswer(Guid visitTaskId, Guid subjectVisitId, CriterionType criterionType, Guid trialReadingCriterionId)
{
var enrollAnswer = string.Empty;
var isEnroll = false;
switch (criterionType)
{
case CriterionType.RECIST1Point1:
case CriterionType.RECIST1Pointt1_MB:
case CriterionType.IRECIST1Point1:
case CriterionType.mRECISTHCC:
case CriterionType.Lugano2014:
case CriterionType.Lugano2014WithoutPET:
if (await _readingTableQuestionAnswerRepository.Where().AnyAsync(x => x.VisitTaskId == visitTaskId && x.Answer == TargetState.Exist.GetEnumInt() &&
x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.State && x.ReadingQuestionTrial.LesionType == LesionType.TargetLesion))
{
enrollAnswer = _userInfo.IsEn_Us ? "Yes" : "是";
}
break;
case CriterionType.PCWG3:
break;
case CriterionType.RANO_BM:
break;
case CriterionType.RANO:
break;
case CriterionType.IWCLL2018:
break;
case CriterionType.Cheson2007:
break;
case CriterionType.IMWG2016:
break;
case CriterionType.mRECISTMesothelioma:
break;
case CriterionType.RECIL:
break;
case CriterionType.RECIST1Point0:
break;
case CriterionType.WHO:
break;
case CriterionType.PERCIST:
break;
case CriterionType.Forrest:
break;
case CriterionType.IVUS:
break;
case CriterionType.OCT:
break;
case CriterionType.MRIPDFF:
break;
case CriterionType.NoCriterion:
break;
case CriterionType.SelfDefine:
break;
default:
break;
}
if (enrollAnswer == "是" || enrollAnswer == "Yes")
{
//把另外一个人的任务设置为不加急(如果项目加急是否 subject 加急是否)
var urgent = _subjectVisitRepository.Where(t => t.Id == subjectVisitId).Select(t => new { IsSubjectUrgent = t.Subject.IsUrgent, t.Trial.IsUrgent }).FirstOrDefault();
if (urgent?.IsUrgent == false || urgent?.IsSubjectUrgent == false)
{
await _visitTaskRepository.BatchUpdateNoTrackingAsync(t => t.SourceSubjectVisitId == subjectVisitId && t.TaskState == TaskState.Effect && t.IsAnalysisCreate == false &&
t.Id != visitTaskId && t.TrialReadingCriterionId == trialReadingCriterionId, u => new VisitTask() { IsUrgent = false });
}
}
return (enrollAnswer, isEnroll);
}
/// <summary>
/// 获取PD 结果
/// </summary>
/// <param name="visitTaskId"> 任务Id</param>
/// <param name="readingCategory"> 任务类型</param>
@ -1057,132 +1145,325 @@ namespace IRaCIS.Core.Application.Service
ReadingTaskQuestionAnswer visitQuestionAnswer = null;
ReadingTaskQuestionAnswer globalQuestionAnswer = null;
ReadingGlobalTaskInfo readingGlobalTaskInfo = null;
if (readingCategory == ReadingCategory.Visit)
{
switch (criterionType)
{
case CriterionType.RECIST1Point1:
case CriterionType.RECIST1Pointt1_MB:
case CriterionType.IRECIST1Point1:
case CriterionType.mRECISTHCC:
visitQuestionAnswer = await _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == visitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.Tumor).FirstNotNullAsync();
answer = visitQuestionAnswer.Answer;
break;
case CriterionType.Lugano2014:
visitQuestionAnswer = await _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == visitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.ImgOncology).FirstNotNullAsync();
answer = visitQuestionAnswer.Answer;
break;
case CriterionType.Lugano2014WithoutPET:
visitQuestionAnswer = await _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == visitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.CTandMRI).FirstNotNullAsync();
answer = visitQuestionAnswer.Answer;
break;
case CriterionType.PCWG3:
visitQuestionAnswer = await _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == visitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.SiteVisitForTumorEvaluation).FirstNotNullAsync();
answer = visitQuestionAnswer.Answer;
break;
case CriterionType.SelfDefine:
case CriterionType.mRECISTMesothelioma:
case CriterionType.RECIL:
case CriterionType.RECIST1Point0:
case CriterionType.WHO:
case CriterionType.PERCIST:
case CriterionType.Forrest:
case CriterionType.RANO_BM:
case CriterionType.RANO:
case CriterionType.IWCLL2018:
case CriterionType.Cheson2007:
case CriterionType.IMWG2016:
default:
//---该标准任务还未定义PD获取逻辑联系业务和后台开发协商后补充
throw new BusinessValidationFailedException(_localizer["TrialEmailN_PDLogicNotDefined"]);
}
}
else if (readingCategory == ReadingCategory.Global)
{
switch (criterionType)
{
case CriterionType.RECIST1Point1:
case CriterionType.RECIST1Pointt1_MB:
case CriterionType.IRECIST1Point1:
case CriterionType.mRECISTHCC:
readingGlobalTaskInfo = await _readingGlobalTaskInfoRepository.Where(t => t.GlobalTaskId == visitTaskId && t.TrialReadingQuestion.QuestionType == QuestionType.Tumor).OrderByDescending(c => c.VisitTask.VisitTaskNum).FirstNotNullAsync();
answer = readingGlobalTaskInfo.Answer;
if (string.IsNullOrEmpty(readingGlobalTaskInfo.Answer))
{
answer = await _readingTaskQuestionAnswerRepository.Where(t => readingGlobalTaskInfo.TaskId == t.VisitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.Tumor)
.Select(t => t.Answer).FirstOrDefaultAsync();
}
break;
case CriterionType.Lugano2014:
readingGlobalTaskInfo = await _readingGlobalTaskInfoRepository.Where(t => t.GlobalTaskId == visitTaskId && t.TrialReadingQuestion.QuestionType == QuestionType.ImgOncology).OrderByDescending(c => c.VisitTask.VisitTaskNum).FirstNotNullAsync();
answer = readingGlobalTaskInfo.Answer;
if (string.IsNullOrEmpty(readingGlobalTaskInfo.Answer))
{
answer = await _readingTaskQuestionAnswerRepository.Where(t => readingGlobalTaskInfo.TaskId == t.VisitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.ImgOncology)
.Select(t => t.Answer).FirstOrDefaultAsync();
}
break;
case CriterionType.Lugano2014WithoutPET:
readingGlobalTaskInfo = await _readingGlobalTaskInfoRepository.Where(t => t.GlobalTaskId == visitTaskId && t.TrialReadingQuestion.QuestionType == QuestionType.CTandMRI).OrderByDescending(c => c.VisitTask.VisitTaskNum).FirstNotNullAsync();
answer = readingGlobalTaskInfo.Answer;
if (string.IsNullOrEmpty(readingGlobalTaskInfo.Answer))
{
answer = await _readingTaskQuestionAnswerRepository.Where(t => readingGlobalTaskInfo.TaskId == t.VisitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.CTandMRI)
.Select(t => t.Answer).FirstOrDefaultAsync();
}
break;
case CriterionType.PCWG3:
var questionAnsewer = await _readingGlobalTaskInfoRepository.Where(t => t.TaskId == visitTaskId && t.TrialReadingQuestion.QuestionType == QuestionType.SiteVisitForTumorEvaluation).FirstNotNullAsync();
answer = questionAnsewer.Answer;
break;
case CriterionType.RANO_BM:
break;
case CriterionType.RANO:
break;
case CriterionType.IWCLL2018:
break;
case CriterionType.Cheson2007:
break;
case CriterionType.IMWG2016:
break;
case CriterionType.mRECISTMesothelioma:
break;
case CriterionType.RECIL:
break;
case CriterionType.RECIST1Point0:
break;
case CriterionType.WHO:
break;
case CriterionType.PERCIST:
break;
case CriterionType.Forrest:
break;
case CriterionType.IVUS:
break;
case CriterionType.OCT:
break;
case CriterionType.MRIPDFF:
break;
case CriterionType.NoCriterion:
break;
case CriterionType.SelfDefine:
break;
default:
break;
}
}
else
{
//---不应有 除访视、裁判、全局其他类型的任务进行发送邮件,请核查业务逻辑
throw new BusinessValidationFailedException(_localizer["TrialEmailN_InvalidTaskTypeForEmailSending"]);
}
switch (criterionType)
{
case CriterionType.RECIST1Point1:
case CriterionType.RECIST1Pointt1_MB:
case CriterionType.mRECISTHCC:
if (readingCategory == ReadingCategory.Visit)
{
visitQuestionAnswer = await _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == visitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.Tumor).FirstNotNullAsync();
answer = visitQuestionAnswer.Answer;
}
else if (readingCategory == ReadingCategory.Global)
{
var questionAnsewer = await _readingGlobalTaskInfoRepository.Where(t => t.GlobalTaskId == visitTaskId && t.TrialReadingQuestion.QuestionType == QuestionType.Tumor).OrderByDescending(c => c.VisitTask.VisitTaskNum).FirstNotNullAsync();
answer = questionAnsewer.Answer;
if (string.IsNullOrEmpty(questionAnsewer.Answer))
{
answer = await _readingTaskQuestionAnswerRepository.Where(t => questionAnsewer.TaskId == t.VisitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.Tumor)
.Select(t => t.Answer).FirstOrDefaultAsync();
}
}
//else if (readingCategory == ReadingCategory.Judge)
//{
// var judgeResultTaskId = await _visitTaskRepository.Where(t => t.Id == visitTaskId).Select(t => t.JudgeResultTaskId).FirstNotNullAsync();
// var questionAnsewer = await _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == judgeResultTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.Tumor).FirstNotNullAsync();
// answer = questionAnsewer.Answer;
//}
else
{
//---不应有 除访视、裁判、全局其他类型的任务进行发送邮件,请核查业务逻辑
throw new BusinessValidationFailedException(_localizer["TrialEmailN_InvalidTaskTypeForEmailSending"]);
}
if (answer == OverallAssessment.PD.GetEnumInt())
{
answer = "是";
answer = _userInfo.IsEn_Us ? "Yes" : "是";
}
else
{
answer = "否";
}
break;
case CriterionType.PCWG3:
if (readingCategory == ReadingCategory.Visit)
{
visitQuestionAnswer = await _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == visitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.SiteVisitForTumorEvaluation).FirstNotNullAsync();
answer = visitQuestionAnswer.Answer;
}
else if (readingCategory == ReadingCategory.Global)
{
var questionAnsewer = await _readingGlobalTaskInfoRepository.Where(t => t.TaskId == visitTaskId && t.TrialReadingQuestion.QuestionType == QuestionType.SiteVisitForTumorEvaluation).FirstNotNullAsync();
answer = questionAnsewer.Answer;
}
//else if (readingCategory == ReadingCategory.Judge)
//{
// //var judgeResultTaskId = await _visitTaskRepository.Where(t => t.Id == visitTaskId).Select(t => t.JudgeResultTaskId).FirstNotNullAsync();
// var questionAnsewer = await _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == visitTaskId && t.ReadingQuestionTrial.QuestionType == QuestionType.SiteVisitForTumorEvaluation).FirstNotNullAsync();
// answer = questionAnsewer.Answer;
//}
else
{
//---不应有 除访视、裁判、全局其他类型的任务进行发送邮件,请核查业务逻辑
throw new BusinessValidationFailedException(_localizer["TrialEmailN_InvalidTaskTypeForEmailSending"]);
}
if (answer == VisitTumorEvaluation.PD.GetEnumInt())
{
answer = "是";
}
if (answer == VisitTumorEvaluation.ND.GetEnumInt())
else if (answer == OverallAssessment.ND.GetEnumInt())
{
answer = "ND";
}
if (answer == VisitTumorEvaluation.NE.GetEnumInt())
else if (answer == OverallAssessment.NE.GetEnumInt())
{
answer = "NE";
}
else
{
answer = "否";
answer = _userInfo.IsEn_Us ? "No" : "否";
}
break;
case CriterionType.IRECIST1Point1:
if (answer == OverallAssessment.iCPD.GetEnumInt())
{
answer = _userInfo.IsEn_Us ? "Yes" : "是";
}
else if (answer == OverallAssessment.ND.GetEnumInt())
{
answer = "ND";
}
else if (answer == OverallAssessment.NE.GetEnumInt())
{
answer = "NE";
}
else
{
answer = _userInfo.IsEn_Us ? "No" : "否";
}
break;
case CriterionType.Lugano2014WithoutPET:
if (answer == CTMRIOverallAssessment.PD.GetEnumInt())
{
answer = _userInfo.IsEn_Us ? "Yes" : "是";
}
else if (answer == CTMRIOverallAssessment.ND.GetEnumInt())
{
answer = "ND";
}
else if (answer == CTMRIOverallAssessment.NE.GetEnumInt())
{
answer = "NE";
}
else
{
answer = _userInfo.IsEn_Us ? "No" : "否";
}
break;
case CriterionType.Lugano2014:
if (answer == ImagingOverallAssessment_Lugano.PMDPD.GetEnumInt())
{
answer = _userInfo.IsEn_Us ? "Yes" : "是";
}
else if (answer == ImagingOverallAssessment_Lugano.ND.GetEnumInt())
{
answer = "ND";
}
else if (answer == ImagingOverallAssessment_Lugano.NE.GetEnumInt())
{
answer = "NE";
}
else
{
answer = _userInfo.IsEn_Us ? "No" : "否";
}
break;
case CriterionType.PCWG3:
if (answer == VisitTumorEvaluation.PD.GetEnumInt())
{
answer = _userInfo.IsEn_Us ? "Yes" : "是";
}
else if (answer == VisitTumorEvaluation.ND.GetEnumInt())
{
answer = "ND";
}
else if (answer == VisitTumorEvaluation.NE.GetEnumInt())
{
answer = "NE";
}
else
{
answer = _userInfo.IsEn_Us ? "No" : "否";
}
break;
case CriterionType.SelfDefine:
case CriterionType.mRECISTMesothelioma:
case CriterionType.RECIL:
case CriterionType.RECIST1Point0:
case CriterionType.WHO:
case CriterionType.PERCIST:
case CriterionType.Forrest:
case CriterionType.Lugano2014:
case CriterionType.IRECIST1Point1:
case CriterionType.RANO_BM:
break;
case CriterionType.RANO:
break;
case CriterionType.IWCLL2018:
case CriterionType.mRECISTHCC:
break;
case CriterionType.Cheson2007:
break;
case CriterionType.IMWG2016:
break;
case CriterionType.mRECISTMesothelioma:
break;
case CriterionType.RECIL:
break;
case CriterionType.RECIST1Point0:
break;
case CriterionType.WHO:
break;
case CriterionType.PERCIST:
break;
case CriterionType.Forrest:
break;
case CriterionType.IVUS:
break;
case CriterionType.OCT:
break;
case CriterionType.MRIPDFF:
break;
case CriterionType.NoCriterion:
break;
case CriterionType.SelfDefine:
break;
default:
//---该标准任务还未定义PD获取逻辑联系业务和后台开发协商后补充
throw new BusinessValidationFailedException(_localizer["TrialEmailN_PDLogicNotDefined"]);
break;
}
return answer;
@ -1314,7 +1595,7 @@ namespace IRaCIS.Core.Application.Service
}).FirstNotNullAsync();
var list = await _readingQuestionCriterionTrialRepository.Where(t => t.TrialId == trialId && t.IsConfirm).Select(t => new { t.CriterionType, TrialReadingCriterionId = t.Id }).ToListAsync();
//var list = await _readingQuestionCriterionTrialRepository.Where(t => t.TrialId == trialId && t.IsConfirm).Select(t => new { t.CriterionType, TrialReadingCriterionId = t.Id }).ToListAsync();
var addList = _mapper.Map<List<TrialEmailNoticeConfig>>(batchAddList);
@ -1333,7 +1614,7 @@ namespace IRaCIS.Core.Application.Service
item.IsAutoSend = true;
item.IsEnable = true;
item.TrialReadingCriterionId = list.FirstOrDefault(t => t.CriterionType == item.CriterionTypeEnum)?.TrialReadingCriterionId;
//item.TrialReadingCriterionId = list.FirstOrDefault(t => t.CriterionType == item.CriterionTypeEnum)?.TrialReadingCriterionId;
item.TrialEmailNoticeUserList.AddRange(batchAddList.Where(t => t.Id == item.SysEmailNoticeConfigId)
.SelectMany(t => t.ToUserTypeList).Select(t => new TrialEmailNoticeUser() { EmailUserType = EmailUserType.To, UserType = t }));
@ -1349,7 +1630,7 @@ namespace IRaCIS.Core.Application.Service
await _trialEmailNoticeConfigRepository.SaveChangesAsync();
//处理定时任务
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId== trialId && t.EmailCron != string.Empty && t.IsAutoSend)
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
.ToListAsync();
@ -1376,9 +1657,9 @@ namespace IRaCIS.Core.Application.Service
var trialConfig = _trialRepository.Where(t => t.Id == inQuery.TrialId).Select(t => new { t.IsEnrollementQualificationConfirm, t.IsPDProgressView }).First();
var trialEmailNoticeConfigQueryable = _trialEmailNoticeConfigRepository.Where(t => t.TrialId == inQuery.TrialId)
.WhereIf(inQuery.IsDistinguishCriteria == false, t => t.TrialReadingCriterionId == null)
.WhereIf(inQuery.IsDistinguishCriteria == true, t => t.CriterionTypeEnum != null)
.WhereIf(inQuery.TrialReadingCriterionId != null, t => t.TrialReadingCriterionId == inQuery.TrialReadingCriterionId)
.WhereIf(inQuery.IsDistinguishCriteria == false, t => t.IsDistinguishCriteria == false)
.WhereIf(inQuery.IsDistinguishCriteria == true, t => t.IsDistinguishCriteria == true)
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeList.Any(c => c == inQuery.CriterionTypeEnum))
.WhereIf(inQuery.BusinessScenarioEnum != null, t => t.BusinessScenarioEnum == inQuery.BusinessScenarioEnum)
.WhereIf(inQuery.EmailUrgentEnum != null, t => t.EmailUrgentEnum == inQuery.EmailUrgentEnum)
.WhereIf(inQuery.IsEnable != null, t => t.IsEnable == inQuery.IsEnable)

View File

@ -0,0 +1,94 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-06-09 02:09:09Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Domain.Models;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Infrastructure.Extention;
using System.Threading.Tasks;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Application.Service.DTO;
using IRaCIS.Core.Application.MassTransit.Command;
namespace IRaCIS.Core.Application.Service;
/// <summary>
/// 项目历史记录文件服务
/// </summary>
[ApiExplorerSettings(GroupName = "FileRecord")]
public class TrialHistoryRecordFileService(IRepository<TrialHistoryRecordFile> _trialHistoryRecordFileRepository,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer): BaseService, ITrialHistoryRecordFileService
{
/// <summary>
/// 获取稽查记录文件列表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<TrialHistoryRecordFileView>> GetTrialHistoryRecordFileList(TrialHistoryRecordFileQuery inQuery)
{
var trialHistoryRecordFileQueryable =_trialHistoryRecordFileRepository
.WhereIf(inQuery.TrialRecordId!=null,x=>x.TrialRecordId==inQuery.TrialRecordId)
.WhereIf(inQuery.TrialFileTypeId != null, x => x.TrialFileTypeId == inQuery.TrialFileTypeId)
.WhereIf(inQuery.FileName.IsNotNullOrEmpty(), x => x.FileName.Contains(x.FileName))
.ProjectTo<TrialHistoryRecordFileView>(_mapper.ConfigurationProvider);
var pageList= await trialHistoryRecordFileQueryable.ToPagedListAsync(inQuery);
return pageList;
}
/// <summary>
/// 批量新增稽查记录文件
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> BatchAddTrialHistoryRecordFile(List<TrialHistoryRecordFileAddOrEdit> inDto)
{
List<TrialHistoryRecordFile> entities = _mapper.Map<List<TrialHistoryRecordFile>>(inDto);
await _trialHistoryRecordFileRepository.AddRangeAsync(entities);
await _trialHistoryRecordFileRepository.SaveChangesAsync();
return ResponseOutput.Ok(true);
}
/// <summary>
/// 新增或修改稽查记录文件
/// </summary>
/// <param name="addOrEditTrialHistoryRecordFile"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> AddOrUpdateTrialHistoryRecordFile(TrialHistoryRecordFileAddOrEdit addOrEditTrialHistoryRecordFile)
{
// 在此处拷贝automapper 映射
var entity = await _trialHistoryRecordFileRepository.InsertOrUpdateAsync(addOrEditTrialHistoryRecordFile, true);
return ResponseOutput.Ok(entity.Id.ToString());
}
/// <summary>
/// 删除稽查记录文件
/// </summary>
/// <param name="trialHistoryRecordFileId"></param>
/// <returns></returns>
[HttpDelete("{trialHistoryRecordFileId:guid}")]
public async Task<IResponseOutput> DeleteTrialHistoryRecordFile(Guid trialHistoryRecordFileId)
{
var success = await _trialHistoryRecordFileRepository.DeleteFromQueryAsync(t => t.Id == trialHistoryRecordFileId,true);
return ResponseOutput.Ok();
}
}

View File

@ -2,6 +2,7 @@
using AutoMapper.EquivalencyExpression;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Service.DTO;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Infrastructure.Extention;
@ -16,6 +17,8 @@ namespace IRaCIS.Core.Application.Service
var isEn_Us = false;
// 在此处拷贝automapper 映射
CreateMap<TrialDocumentAttachment, TrialDocumentAttachmentView>();
CreateMap<TrialDocumentAttachment, TrialDocumentAttachmentAddOrEdit>().ReverseMap();
CreateMap<SystemDocumentAttachment, SystemDocumentAttachmentView>();
CreateMap<SystemDocumentAttachment, SystemDocumentAttachmentAddOrEdit>().ReverseMap();
@ -40,6 +43,7 @@ namespace IRaCIS.Core.Application.Service
.ForMember(dest => dest.CreateUserRole, opt => opt.Ignore());
CreateMap<TrialDocument, TrialDocumentView>()
.ForMember(d => d.AttachmentCount, u => u.MapFrom(s =>s.TrialDocumentAttachmentList.Count()))
.ForMember(d => d.FileType, u => u.MapFrom(s => isEn_Us ? s.FileType.Value : s.FileType.ValueCN))
.ForMember(d => d.IsSomeUserSigned, u => u.MapFrom(s => s.TrialDocConfirmedUserList.Any(t => t.ConfirmTime != null)))
.ForMember(d => d.FullFilePath, u => u.MapFrom(s => s.Path));
@ -121,7 +125,8 @@ namespace IRaCIS.Core.Application.Service
CreateMap<TrialFileType, TrialFileTypeAddOrEdit>().ReverseMap();
CreateMap<TrialFinalRecord, TrialFinalRecordView>();
CreateMap<TrialFinalRecord, TrialFinalRecordView>()
.ForMember(d => d.HistoryCount, c => c.MapFrom(t => t.TrialHistoryRecordFileList.Count));
CreateMap<TrialFinalRecordAddOrEdit, TrialFinalRecord>();
//.ForMember(d => d.HistoryFileRecord.TrialFileTypeId, c => c.MapFrom(t => t.TrialFileTypeId))
//.ForMember(d => d.SignFileRecord.TrialFileTypeId, c => c.MapFrom(t => t.TrialFileTypeId))
@ -129,15 +134,18 @@ namespace IRaCIS.Core.Application.Service
//.ForMember(d => d.PDFFileRecord.TrialFileTypeId, c => c.MapFrom(t => t.TrialFileTypeId));
CreateMap<TrialNormalRecord, TrialNormalRecordView>()
.ForMember(d => d.HistoryCount, c => c.MapFrom(t => t.TrialHistoryRecordFileList.Count))
.ForMember(d => d.FileName, c => c.MapFrom(t => t.TrialFileRecord.FileName));
CreateMap<TrialNormalRecord, TrialNormalRecordAddOrEdit>().ReverseMap();
CreateMap<TrialTrianingRecord, TrialTrianingRecordView>()
.ForMember(d => d.HistoryCount, c => c.MapFrom(t => t.TrialHistoryRecordFileList.Count))
.ForMember(d => d.FileName, c => c.MapFrom(t => t.TrialFileRecord.FileName));
CreateMap<TrialTrianingRecord, TrialTrianingRecordAddOrEdit>().ReverseMap();
CreateMap<TrialHistoryRecordFile, TrialHistoryRecordFileView>();
CreateMap<TrialHistoryRecordFile, TrialHistoryRecordFileAddOrEdit>().ReverseMap();
}
}

View File

@ -66,6 +66,8 @@ namespace IRaCIS.Core.Application.Contracts.Dicom.DTO
public Guid? InstanceId { get; set; }
public int? NumberOfFrames { get; set; }
public DateTime CreateTime { get; set; }
}
public class InstanceBasicInfo
{
@ -98,6 +100,8 @@ namespace IRaCIS.Core.Application.Contracts.Dicom.DTO
public string WindowCenter { get; set; }
[JsonIgnore]
public string WindowWidth { get; set; }
public DateTime? RowDate { get; set; }
}
public class DicomSeriesWithLabelDTO : DicomSeriesDTO

View File

@ -546,6 +546,115 @@ namespace IRaCIS.Core.Application.Contracts
}
public class TrialExportImageCommand
{
[NotDefault]
public Guid TrialId { get; set; }
public List<Guid> SubjectVisitIdList { get; set; }
public bool IsKeyImage { get; set; }
}
public class TrialVisitImageQuery : PageInput
{
[NotDefault]
public Guid TrialId { get; set; }
public Guid? TrialSiteId { get; set; }
public string? SubjectCode { get; set; }
public DateTime? BeginScanDate { get; set; }
public DateTime? EndScanDate { get; set; }
}
public class TrialVisitImageStatView
{
public Guid TrialId { get; set; }
public Guid SubjectVisitId { get; set; }
public Guid TrialSiteId { get; set; }
public string TrialSiteCode { get; set; }
public string SubjectCode { get; set; }
public string VisitName { get; set; }
public decimal VisitNum { get; set; }
public DateTime? EarliestScanDate { get; set; }
public DateTime? LatestScanDate { get; set; }
public int TotalStudyCount { get; set; }
public int TotalImageCount { get; set; }
public long? TotalImageSize { get; set; }
public string TotalImageSizeStr => TotalImageSize.HasValue
? $"{TotalImageSize.Value / 1024d / 1024d:F3} MB"
: "0.000 MB";
public string ImageTypeStr => $"{(IsHaveDicom ? "DICOM" : "")}{(IsHaveNoneDicom&&IsHaveDicom?" , ":"")}{(IsHaveNoneDicom ? "Non-DICOM" : "")}";
public bool IsHaveDicom { get; set; }
public bool IsHaveNoneDicom { get; set; }
#region 废弃,为了字段排序
//public int TotalStudyCount => DicomStudyCount + NoneDicomStudyCount;
//public int TotalImageCount => DicomImageCount + NoneDicomImageCount;
//public long? TotalImageSize => DicomImageSize + NoneDicomImageSize;
//public int DicomStudyCount { get; set; }
//public int DicomImageCount { get; set; }
//public long? DicomImageSize { get; set; }
//public int NoneDicomStudyCount { get; set; }
//public int NoneDicomImageCount { get; set; }
//public long? NoneDicomImageSize { get; set; }
#endregion
}
public class TrialImageStatInfo
{
public int SubjectCount { get; set; }
public int SubjectVisitCount { get; set; }
public long? TotalImageSize { get; set; }
public string TotalImageSizeStr => TotalImageSize.HasValue
? $"{TotalImageSize.Value / 1024d / 1024d:F3} MB"
: "0.000 MB";
public string SubjectImageAVGSizeStr => TotalImageSize.HasValue
? $"{TotalImageSize.Value / SubjectCount / 1024d / 1024d:F3} MB"
: "0.000 MB";
public string SubjectVisitImageAVGSizeStr => TotalImageSize.HasValue
? $"{TotalImageSize.Value / SubjectVisitCount / 1024d / 1024d:F3} MB"
: "0.000 MB";
}
public class TrialImageDownloadView
{
public Guid TrialId { get; set; }

View File

@ -1119,7 +1119,18 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
}
/// <summary>
/// 更新后处理上传的检查modality
/// </summary>
/// <param name="taskStudyId"></param>
/// <param name="modality"></param>
/// <returns></returns>
[HttpPut]
public async Task<IResponseOutput> UpdateTaskStudyModality(Guid taskStudyId, string modality)
{
await _taskStudyRepository.UpdatePartialFromQueryAsync(t => t.Id == taskStudyId, u => new TaskStudy() { ModalityForEdit = modality }, true);
return ResponseOutput.Ok();
}
/// <summary>
/// 影像下载成功回调
@ -1156,19 +1167,183 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
return await query.ToPagedListAsync(inQuery);
}
/// <summary>
/// 更新后处理上传的检查modality
/// </summary>
/// <param name="taskStudyId"></param>
/// <param name="modality"></param>
/// <returns></returns>
[HttpPut]
public async Task<IResponseOutput> UpdateTaskStudyModality(Guid taskStudyId, string modality)
#region 影像汇总页面
[HttpPost]
public async Task<IResponseOutput<PageOutput<TrialVisitImageStatView>>> GetTrialVisitImageStatList(TrialVisitImageQuery inQuery)
{
await _taskStudyRepository.UpdatePartialFromQueryAsync(t => t.Id == taskStudyId, u => new TaskStudy() { ModalityForEdit = modality }, true);
return ResponseOutput.Ok();
var query = _subjectVisitRepository.Where(t => t.TrialId == inQuery.TrialId)
.WhereIf(inQuery.TrialSiteId != null, t => t.TrialSiteId == inQuery.TrialSiteId)
.WhereIf(inQuery.SubjectCode.IsNotNullOrEmpty(), t => t.Subject.Code.Contains(inQuery.SubjectCode))
.WhereIf(inQuery.BeginScanDate != null, t => t.LatestScanDate >= inQuery.BeginScanDate)
.WhereIf(inQuery.EndScanDate != null, t => t.LatestScanDate == inQuery.EndScanDate)
.Select(t => new TrialVisitImageStatView()
{
TrialId = t.TrialId,
SubjectVisitId = t.Id,
SubjectCode = t.Subject.Code,
TrialSiteCode = t.TrialSite.TrialSiteCode,
TrialSiteId = t.TrialSiteId,
VisitName = t.VisitName,
VisitNum = t.VisitNum,
EarliestScanDate = t.EarliestScanDate,
LatestScanDate = t.LatestScanDate,
IsHaveDicom = t.StudyList.Any(),
IsHaveNoneDicom = t.NoneDicomStudyList.Any(),
TotalStudyCount = t.StudyList.Count() + t.NoneDicomStudyList.Count(),
TotalImageCount = t.StudyList.Sum(t => t.InstanceCount) + t.NoneDicomStudyList.Sum(t => t.FileCount),
TotalImageSize = t.StudyList.SelectMany(t => t.InstanceList).Sum(t => t.FileSize) + t.NoneDicomStudyList.SelectMany(t => t.NoneDicomFileList).Sum(t => t.FileSize),
//DicomStudyCount = t.StudyList.Count(),
//NoneDicomStudyCount = t.NoneDicomStudyList.Count(),
//DicomImageCount = t.StudyList.Sum(t => t.InstanceCount),
//NoneDicomImageCount = t.NoneDicomStudyList.Sum(t => t.FileCount),
//DicomImageSize = t.StudyList.SelectMany(t => t.InstanceList).Sum(t => t.FileSize),
//NoneDicomImageSize = t.NoneDicomStudyList.SelectMany(t => t.NoneDicomFileList).Sum(t => t.FileSize)
});
var pagelist = await query.Where(t => t.TotalImageCount > 0).ToPagedListAsync(inQuery);
return ResponseOutput.Ok(pagelist);
}
/// <summary>
/// 获取项目影像统计有影像的subject 数量 访视数量
/// </summary>
/// <param name="trialId"></param>
/// <returns></returns>
public async Task<IResponseOutput<TrialImageStatInfo>> GetTrialVisitImageStatInfo(Guid trialId)
{
var subjectImageList = _subjectVisitRepository.Where(t => t.TrialId == trialId)
.Where(t => t.StudyList.Sum(t => t.InstanceCount) > 0 || t.NoneDicomStudyList.Sum(t => t.FileCount) > 0)
.GroupBy(t => t.SubjectId)
.Select(g => new
{
SubjectId = g.Key,
VisitCount = g.Count(),
ImageSize = g.SelectMany(t => t.NoneDicomStudyList).SelectMany(t => t.NoneDicomFileList).Sum(t => t.FileSize)
+ g.SelectMany(t => t.StudyList).SelectMany(t => t.InstanceList).Sum(t => t.FileSize)
})
.ToList();
var subjectCount = subjectImageList.Count;
var subjectVisitCount = subjectImageList.Sum(t => t.VisitCount);
var totalImageSize = subjectImageList.Sum(t => t.ImageSize);
return ResponseOutput.Ok(new TrialImageStatInfo { SubjectCount = subjectCount, SubjectVisitCount = subjectVisitCount, TotalImageSize = totalImageSize });
}
/// <summary>
/// 批量勾选访视 进行下载
/// </summary>
/// <param name="inCommand"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> GetExportSubjectVisitImageList(TrialExportImageCommand inCommand)
{
if (inCommand.IsKeyImage)
{
var downloadInfo = _visitTaskRepository.Where(t => t.TrialId == inCommand.TrialId && t.ReadingCategory == ReadingCategory.Visit && t.ReadingTaskState == ReadingTaskState.HaveSigned && t.IsAnalysisCreate == false)
.Where(t => inCommand.SubjectVisitIdList.Contains((Guid)t.SourceSubjectVisitId))
.Select(t => new
{
t.Trial.ResearchProgramNo,
CriterionName = t.TrialReadingCriterion.CriterionName,
TrialSiteCode = t.Subject.TrialSite.TrialSiteCode,
SubjectCode = t.Subject.Code,
VisitName = (string?)t.SourceSubjectVisit.VisitName,
ArmEnum = t.ArmEnum,
QuestionMarkPictureList = t.ReadingTaskQuestionMarkList.Select(c => new { c.PicturePath, c.OtherPicturePath }).ToList(),
TableQuestionRowPictureList = t.LesionList.Select(c => new { c.PicturePath, c.OtherPicturePath }).ToList(),
IsJudgeSelect = t.JudgeResultTaskId == t.Id
}).ToList();
return ResponseOutput.Ok(downloadInfo);
}
else
{
var downloadInfo = _trialRepository.Where(t => t.Id == inCommand.TrialId).Select(t => new
{
t.ResearchProgramNo,
VisitList = t.SubjectVisitList.Where(t => inCommand.SubjectVisitIdList.Contains(t.Id)).Select(sv => new
{
TrialSiteCode = sv.TrialSite.TrialSiteCode,
SubjectCode = sv.Subject.Code,
VisitName = sv.VisitName,
StudyList = sv.StudyList.Select(u => new
{
u.PatientId,
u.StudyTime,
u.StudyCode,
SeriesList = u.SeriesList.Select(z => new
{
z.Modality,
InstancePathList = z.DicomInstanceList.Select(k => new
{
k.Path
})
})
}),
NoneDicomStudyList = sv.NoneDicomStudyList.Select(nd => new
{
nd.Modality,
nd.StudyCode,
nd.ImageDate,
FileList = nd.NoneDicomFileList.Select(file => new
{
file.FileName,
file.Path,
file.FileType
})
})
}).ToList()
}).FirstOrDefault();
return ResponseOutput.Ok(downloadInfo);
}
}
#endregion
#region 之前后端下载废弃

View File

@ -349,6 +349,8 @@ namespace IRaCIS.Core.Application.Service.Inspection.DTO
public class GetDataInspectionOutDto : DataInspection
{
public bool IsShow { get; set; }
public string TrialReadingCriterionName { get; set; }
public string BlindName { get; set; }
@ -420,11 +422,47 @@ namespace IRaCIS.Core.Application.Service.Inspection.DTO
public bool isAbandon { get; set; }
}
public class GetTrialShowInspectionOutDto
{
public Guid FrontAuditConfigId { get; set; }
public Guid? ParentId { get; set; }
public int Sort { get; set; }
public string Description { get; set; }
public string DescriptionCN { get; set; }
public bool IsShow { get; set; }
public List<GetTrialShowInspectionOutDto> Children { get; set; }
}
public class SetTrialShowInspection
{
public Guid TrialId { get; set; }
public List<TrialShowInspection> TrialShowInspectionList { get; set; }
}
public class TrialShowInspection
{
public Guid FrontAuditConfigId { get; set; }
public bool IsShow { get; set; }
}
public class GetTrialShowInspectionInDto
{
public Guid TrialId { get; set; }
}
public class GetDataInspectionDto : PageInput
{
public Guid? FrontAuditConfigId { get; set; }
/// <summary>
/// 项目iD
/// </summary>

View File

@ -7,7 +7,7 @@
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore.Common;
using MassTransit;
using Microsoft.AspNetCore.Mvc;
@ -29,6 +29,7 @@ namespace IRaCIS.Core.Application.Service
IRepository<QCChallenge> _qCChallengeRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<Trial> _trialRepository,
IRepository<TrialAuditShow> _trialAuditShowRepository,
IRepository<UserRole> _userRoleRepository,
IRepository<CheckChallengeDialog> _checkChallengeDialogRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IFrontAuditConfigService
@ -1134,18 +1135,81 @@ namespace IRaCIS.Core.Application.Service
return ResponseOutput.Ok();
}
/// <summary>
/// 获取模块类型列表
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<object> GetModuleTypeList(GetModuleTypeListInDto inDto)
{
var allfront = await (from data in _frontAuditConfigRepository.Where(x=>x.IsEnable && x.ConfigType == "M")
join dic in _dictionaryRepository.Where(x => x.Parent.Code == "ModuleType" && x.IsEnable) on data.ModuleTypeId equals dic.Id
join trialshow in _trialAuditShowRepository.Where(x => x.TrialId == inDto.TrialId) on data.Id equals trialshow.FrontAuditConfigId into trialshowtemp
from lefttrialshow in trialshowtemp.DefaultIfEmpty()
select new ModuleTypeData()
{
IsShow = lefttrialshow == null ? data.IsDefaultChoice : lefttrialshow.IsShow,
Id = data.Id,
ParentId = data.ParentId,
DictionaryId = dic.Id,
ShowOrder= dic.ShowOrder,
DictionaryValue = _userInfo.IsEn_Us ? dic.Value : dic.ValueCN,
}).ToListAsync();
var result = allfront.Where(x => x.IsShow && x.ParentId!=null).ToList();
FindParent(result, result.Select(x => x.ParentId).ToList());
void FindParent(List<ModuleTypeData> re, List<Guid?> Parentids)
{
var parentList = allfront.Where(x => Parentids.Contains(x.Id)).ToList();
if (parentList.Count > 0)
{
re.AddRange(parentList);
FindParent(re, parentList.Select(x => x.ParentId).ToList());
}
}
return result.OrderBy(x => x.ShowOrder).Select(x => new {
x.DictionaryId,
x.DictionaryValue
}).Distinct().ToList();
}
/// <summary>
/// 获取Description
/// </summary>
/// <param name="moduleTypeId"></param>
/// <param name="trialId"></param>
/// <returns></returns>
[HttpGet]
public async Task<List<string>> GetModuleTypeDescriptionList(Guid moduleTypeId)
public async Task<object> getModuleTypeDescriptionList(Guid moduleTypeId,Guid trialId)
{
var result = (await _frontAuditConfigRepository.Where(x => x.ModuleTypeId == moduleTypeId && x.ObjectTypeId != null && x.OptTypeId != null && x.Description.Length > 0).Select(x => new { x.Description, x.DescriptionCN, x.Sort }).OrderBy(t => t.Sort).ToListAsync()
).Select(t => _userInfo.IsEn_Us ? t.Description : t.DescriptionCN).Distinct().ToList();
return result;
var result = from data in _frontAuditConfigRepository.Where(x =>x.IsEnable && x.ModuleTypeId == moduleTypeId && x.ObjectTypeId != null && x.OptTypeId != null && x.Description.Length > 0)
join trialshow in _trialAuditShowRepository.Where(x => x.TrialId == trialId) on data.Id equals trialshow.FrontAuditConfigId into trialshowtemp
from lefttrialshow in trialshowtemp.DefaultIfEmpty()
select new
{
IsShow= lefttrialshow==null? data.IsDefaultChoice : lefttrialshow.IsShow,
FrontAuditConfigId = data.Id,
Description = _userInfo.IsEn_Us ? data.Description : data.DescriptionCN
};
return result.Where(x=>x.IsShow).Distinct().ToList();
}
/// <summary>
@ -1192,7 +1256,7 @@ namespace IRaCIS.Core.Application.Service
Sort = data.Sort,
ValueCN = data.ValueCN,
ChildrenTypeValue = leftchildrenType.Value,
IsDefaultChoice= data.IsDefaultChoice,
DictionaryKey = data.DictionaryKey,
EnumType = data.EnumType,
ObjectTypeId = data.ObjectTypeId,

View File

@ -1,18 +1,21 @@
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Service.Inspection.DTO;
using IRaCIS.Core.Application.Service.Inspection.Interface;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using Panda.DynamicWebApi.Attributes;
namespace IRaCIS.Core.Application.Service.Inspection
{
[NonDynamicWebApi]
[ApiExplorerSettings(GroupName = "Reviewer")]
public class InspectionService(IRepository<DataInspection> _dataInspectionRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialSign> _trialSignRepository,
IRepository<IdentityUser> _identityUserRepository,
IRepository<TrialAuditShow> _trialAuditShowRepository,
IRepository<UserRole> _userRoleRepository,
IRepository<TrialSite> _trialSiteRepository,
IRepository<Trial> _trialRepository,
@ -23,7 +26,63 @@ namespace IRaCIS.Core.Application.Service.Inspection
IRepository<FrontAuditConfig> _frontAuditConfigRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IInspectionService
{
/// <summary>
/// 设置项目稽查配置
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> SetTrialShowInspection(SetTrialShowInspection inDto)
{
await _trialAuditShowRepository.BatchDeleteNoTrackingAsync(x => x.TrialId == inDto.TrialId);
List<TrialAuditShow> trialAuditShows = inDto.TrialShowInspectionList.Select(x => new TrialAuditShow()
{
FrontAuditConfigId = x.FrontAuditConfigId,
TrialId = inDto.TrialId,
IsShow = x.IsShow,
}).ToList();
await _trialAuditShowRepository.AddRangeAsync(trialAuditShows);
await _trialAuditShowRepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
/// <summary>
/// 获取项目稽查显示信息
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<List<GetTrialShowInspectionOutDto>> GetTrialShowInspection(GetTrialShowInspectionInDto inDto)
{
var query = from data in _frontAuditConfigRepository.Where(x => x.IsEnable && x.ConfigType == "M")
join trialdata in _trialAuditShowRepository.Where(x => x.TrialId == inDto.TrialId) on data.Id equals trialdata.FrontAuditConfigId into trialdatatemp
from lefttrialdata in trialdatatemp.DefaultIfEmpty()
select new GetTrialShowInspectionOutDto()
{
Description = data.Description,
DescriptionCN = data.DescriptionCN,
FrontAuditConfigId = data.Id,
ParentId= data.ParentId,
Sort=data.Sort,
IsShow = lefttrialdata != null ? lefttrialdata.IsShow : data.IsDefaultChoice
};
var frontAuditList =await query.ToListAsync();
var result= frontAuditList.Where(x=>x.ParentId==default(Guid)).OrderBy(x=>x.Sort).ToList();
result.ForEach(x =>
{
x.Children = frontAuditList.Where(y => y.ParentId == x.FrontAuditConfigId).OrderBy(x => x.Sort).ToList();
});
return result;
}
public async Task<PageOutput<GetDataInspectionOutDto>> GetInspectionList(GetDataInspectionDto inQuery)
@ -61,26 +120,6 @@ namespace IRaCIS.Core.Application.Service.Inspection
join visttask in _visitTaskRepository.Where().IgnoreQueryFilters() on data.VisitTaskId equals visttask.Id into visttasktemp
from leftvisttask in visttasktemp.DefaultIfEmpty()
//join trialCriterion in _repository.GetQueryable<ReadingQuestionCriterionTrial>().IgnoreQueryFilters() on data.TrialReadingCriterionId equals trialCriterion.Id into criterion
//from leftCriterion in criterion.DefaultIfEmpty()
//join moduleTyped in _dictionaryRepository.Where().Where(x => x.Code == "ModuleType") on 1 equals 1
//join moduleTypec in _dictionaryRepository.Where() on new { ParentId = moduleTyped.Id, ModuleType = data.ModuleType } equals new { ParentId = moduleTypec.ParentId.Value, ModuleType = moduleTypec.Value } into moduleTypectemp
//join childrenTyped in _dictionaryRepository.Where().Where(x => x.Code == "ChildrenType") on 1 equals 1
//join childrenTypec in _dictionaryRepository.Where() on new { ParentId = childrenTyped.Id, ModuleType = data.ChildrenType } equals new { ParentId = childrenTypec.ParentId.Value, ModuleType = childrenTypec.Value } into childrenTypectemp
//from leftchildrenTypec in childrenTypectemp.DefaultIfEmpty()
//join ObjectTyped in _dictionaryRepository.Where().Where(x => x.Code == "ObjectType") on 1 equals 1
//join ObjectTypec in _dictionaryRepository.Where() on new { ParentId = ObjectTyped.Id, ModuleType = data.ObjectType } equals new { ParentId = ObjectTypec.ParentId.Value, ModuleType = ObjectTypec.Value } into objectTypetemp
//from leftObjectType in objectTypetemp.DefaultIfEmpty()
//join OptTyped in _dictionaryRepository.Where().Where(x => x.Code == "OptType") on 1 equals 1
//join OptTypec in _dictionaryRepository.Where() on new { ParentId = OptTyped.Id, ModuleType = data.OptType } equals new { ParentId = OptTypec.ParentId.Value, ModuleType = OptTypec.Value } into optTypetemp
//from leftOptType in optTypetemp.DefaultIfEmpty()
join trialSign in _trialSignRepository.Where().IgnoreQueryFilters() on data.SignId equals trialSign.Id into trialSigntemp
from lefttrialSign in trialSigntemp.DefaultIfEmpty()
join leftfrontAuditConfig in _frontAuditConfigRepository.Where().Where(x => x.ConfigType == "M" && x.Identification != null && x.IsEnable == true) on
@ -91,9 +130,11 @@ namespace IRaCIS.Core.Application.Service.Inspection
from leftmoduleTypec in moduleTypectemp.DefaultIfEmpty()
join OptTypec in _dictionaryRepository.Where() on new { ModuleType = leftfrontAuditConfig.OptTypeId!.Value } equals new { ModuleType = OptTypec.Id } into optTypetemp
from leftOptType in optTypetemp.DefaultIfEmpty()
join trialShow in _trialAuditShowRepository.Where(x => x.TrialId == inQuery.TrialId) on leftfrontAuditConfig.Id equals trialShow.FrontAuditConfigId into trialShowtemp
from lefttrialShow in trialShowtemp.DefaultIfEmpty()
select new GetDataInspectionOutDto()
{
IsShow = lefttrialShow != null ? lefttrialShow.IsShow : leftfrontAuditConfig.IsDefaultChoice,
CreateTime = data.CreateTime,
CreateUserId = data.CreateUserId,
ModuleTypeId = leftmoduleTypec.Id,
@ -167,7 +208,8 @@ namespace IRaCIS.Core.Application.Service.Inspection
query = query.WhereIf(inQuery.TrialSiteId != null, x => x.TrialSiteId == inQuery.TrialSiteId)
//.Where(x => (x.TrialId == dto.TrialId) || (x.TrialId == null && x.CreateTime >= trialData.CreateTime && x.CreateTime <= trialData.TrialFinishTime))
.Where(x => x.TrialId == inQuery.TrialId)
.Where(x => x.IsShow)
.WhereIf(inQuery.FrontAuditConfigId != null, x => x.FrontAuditConfigId == inQuery.FrontAuditConfigId)
#region 废弃
// .WhereIf(dto.BatchId != null && dto.ObjectRelationParentId == null && dto.GeneralId == null, x => x.BatchId == dto.BatchId)
// .WhereIf(dto.BatchId != null && dto.GeneralId != null && dto.ObjectRelationParentId == null, x => x.BatchId == dto.BatchId ||

View File

@ -263,7 +263,7 @@ namespace IRaCIS.Core.Application.Contracts
//临床数据收集
public string ClinicalDataCollect => $"{DicomStudyCount},{NoneDicomStudyCount}{(IsBaseLine ? (ClinicalInformationTransmissionEnum > 0 && IsHaveClinicalData ? ",w/" : ",w/o") : "")}";
public string ClinicalDataCollect => $"{DicomStudyCount},{NoneDicomStudyCount}{(IsHaveClinicalData ? ",w/" : ",w/o")}";
public int? DicomStudyCount { get; set; }
public int? NoneDicomStudyCount { get; set; }
@ -907,7 +907,7 @@ namespace IRaCIS.Core.Application.Contracts
public string ApplicantName { get; set; }
public string? ReReadingNewTaskCode { get; set; }
@ -1222,6 +1222,8 @@ namespace IRaCIS.Core.Application.Contracts
public string CDISCCode { get; set; }
public string GroupName { get; set; }
public int ShowOrder { get; set; }
}
@ -1310,6 +1312,7 @@ namespace IRaCIS.Core.Application.Contracts
//阅片完成时间
public DateTime? SignTime { get; set; }
#endregion
#region 肿瘤学结果
@ -1322,6 +1325,23 @@ namespace IRaCIS.Core.Application.Contracts
public string OncologyUserName { get; set; }
#endregion
#region CDISC 固定字段
//组
public string Group { get; set; }
//行标识
public string TRLINKID { get; set; }
//列名
public string TRTEST { get; set; }
//值
public string TRORRES { get; set; }
//单位
public string TRORRESU { get; set; }
#endregion
}
public class CommonLessionExport : CommonEvaluationExport
@ -1356,6 +1376,17 @@ namespace IRaCIS.Core.Application.Contracts
public string CDISCCode { get; set; }
//病灶Id
public Guid RowId { get; set; }
//如果是4 就取CustomUnit 否则就是字典翻译
[Comment("单位")]
public ValueUnit? Unit { get; set; }
[Comment("自定义单位")]
public string CustomUnit { get; set; } = string.Empty;
#endregion

View File

@ -33,9 +33,11 @@ namespace IRaCIS.Core.Application.Service
.ForMember(d => d.SubmitUserName, u => u.MapFrom(s => s.SubmitUser.FullName))
.ForMember(d => d.ClinicalInformationTransmissionEnum, u => u.MapFrom(s => s.Trial.ClinicalInformationTransmissionEnum))
.ForMember(d => d.IsHaveClinicalData, u => u.MapFrom(t => t.IsBaseLine ? t.PreviousHistoryList.Any() || t.PreviousOtherList.Any() || t.PreviousSurgeryList.Any()
|| t.ReadingClinicalDataList.Any(x => x.ClinicalDataTrialSet.UploadRole == Domain.Share.UploadRole.CRC && x.ReadingClinicalDataPDFList.Count > 0)
: false))
.ForMember(d => d.IsHaveClinicalData,
u => u.MapFrom(t => t.PreviousHistoryList.Any() ||
t.PreviousOtherList.Any() || t.PreviousSurgeryList.Any() ||
t.Subject.ClinicalFormList.Any(x => x.ClinicalDataTrialSet.UploadRole == UploadRole.CRC && x.ReadingId == t.Id) ||
t.ReadingClinicalDataList.Any(x => x.ClinicalDataTrialSet.UploadRole == Domain.Share.UploadRole.CRC && x.ReadingClinicalDataPDFList.Count > 0)))
.ForMember(d => d.DicomStudyCount, u => u.MapFrom(t => t.StudyList.Count()))
.ForMember(d => d.NoneDicomStudyCount, u => u.MapFrom(t => t.NoneDicomStudyList.Count(t => t.NoneDicomFileList.Any())));
@ -156,6 +158,7 @@ namespace IRaCIS.Core.Application.Service
CreateMap<VisitTaskReReading, ReReadingTaskExportDto>().IncludeMembers(t => t.OriginalReReadingTask)
.ForMember(o => o.ReReadingNewTaskCode, t => t.MapFrom(u => u.NewReReadingTask.TaskCode))
.ForMember(o => o.ApplicantName, t => t.MapFrom(u => u.CreateUserRole.IdentityUser.FullName))
/*.ForMember(o => o.ApplyTask, t => t.MapFrom(u => u.OriginalReReadingTask))*/;
CreateMap<VisitTask, ReReadingTaskExportDto>()
@ -299,8 +302,11 @@ namespace IRaCIS.Core.Application.Service
CreateMap<ReadingTableQuestionAnswer, CommonLessionQuestionAnswerInfo>()
//.ForMember(o => o.LessionCode, t => t.MapFrom(u => u.Lesion.RowMark))
//.ForMember(o => o.LessionType, t => t.MapFrom(u => (int?)u.ReadingQuestionTrial.LesionType))
.ForMember(o => o.CustomUnit, t => t.MapFrom(u => u.ReadingTableQuestionTrial.CustomUnit))
.ForMember(o => o.Unit, t => t.MapFrom(u => u.ReadingTableQuestionTrial.Unit))
.ForMember(o => o.TableQuesionId, t => t.MapFrom(u => u.ReadingTableQuestionTrial.Id))
.ForMember(o => o.QuestionMark, t => t.MapFrom(u => u.ReadingTableQuestionTrial.QuestionMark))
.ForMember(o => o.TableQuesionId, t => t.MapFrom(u => u.ReadingTableQuestionTrial.Id))
.ForMember(o => o.QuestionName, t => t.MapFrom(u => isEn_Us ? u.ReadingTableQuestionTrial.QuestionEnName : u.ReadingTableQuestionTrial.QuestionName))
.ForMember(o => o.QuestionValue, t => t.MapFrom(u => u.Answer))
.ForMember(o => o.CDISCCode, t => t.MapFrom(u => u.ReadingTableQuestionTrial.CDISCCode))
@ -545,10 +551,10 @@ namespace IRaCIS.Core.Application.Service
.ForMember(d => d.Age, u => u.MapFrom(t => t.Subject.Age))
.ForMember(d => d.IsHaveClinicalData,
u => u.MapFrom(t => t.PreviousHistoryList.Any() ||
t.PreviousOtherList.Any() ||
t.PreviousOtherList.Any() || t.PreviousSurgeryList.Any() ||
t.Subject.ClinicalFormList.Any(x => x.ClinicalDataTrialSet.UploadRole == UploadRole.CRC && x.ReadingId == t.Id) ||
t.ReadingClinicalDataList.Any(x => x.ClinicalDataTrialSet.UploadRole == Domain.Share.UploadRole.CRC && x.ReadingClinicalDataPDFList.Count > 0) ||
t.PreviousSurgeryList.Any()))
t.ReadingClinicalDataList.Any(x => x.ClinicalDataTrialSet.UploadRole == Domain.Share.UploadRole.CRC && x.ReadingClinicalDataPDFList.Count > 0)
))
.ForMember(d => d.IsHaveUploadFailed, u => u.MapFrom(t => t.StudyList.SelectMany(c => c.DicomStudyMonitorList).Any(h => h.FailedFileCount > 0)))

View File

@ -1281,6 +1281,13 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public int? OtherNumberOfFrames { get; set; }
public string? OtherMeasureData { get; set; }
/// <summary>
/// 表格问题标记
/// </summary>
public Guid? RowId { get; set; }
public Guid? TableQuestionId { get; set; }
}
public class GetReadingQuestionAndAnswerInDto
{

View File

@ -319,6 +319,20 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public class ReadingTableQuestionTrialAddOrEdit
{
/// <summary>
/// 影像标记
/// </summary>
public ImageMark? ImageMarkEnum { get; set; }
/// <summary>
/// 影像工具
/// </summary>
public string ImageTool { get; set; } = string.Empty;
/// <summary>
/// 影像工具属性
/// </summary>
public string ImageToolAttribute { get; set; } = string.Empty;
public Guid? ClassifyTableQuestionId { get; set; }
@ -924,6 +938,20 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public class ReadingQuestionTrialView
{
/// <summary>
/// 影像标记
/// </summary>
public ImageMark? ImageMarkEnum { get; set; }
/// <summary>
/// 影像工具
/// </summary>
public string ImageTool { get; set; } = string.Empty;
/// <summary>
/// 影像工具属性
/// </summary>
public string ImageToolAttribute { get; set; } = string.Empty;
public Guid Id { get; set; }
@ -2345,6 +2373,22 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public class AddOrUpdateReadingQuestionTrialInDto
{
/// <summary>
/// 影像标记
/// </summary>
public ImageMark? ImageMarkEnum { get; set; }
/// <summary>
/// 影像工具
/// </summary>
public string ImageTool { get; set; } = string.Empty;
/// <summary>
/// 影像工具属性
/// </summary>
public string ImageToolAttribute { get; set; } = string.Empty;
/// <summary>
/// Id
/// </summary>

View File

@ -1460,7 +1460,26 @@ namespace IRaCIS.Core.Application.Service
[HttpPost]
public async Task<NeedSynchronize> VerifyeCriterionNeedSynchronize(VerifyeCriterionNeedSynchronizeInDto inDto)
{
var trialCriterion = await _readingQuestionCriterionTrialRepository.Where(x => x.Id == inDto.TrialReadingCriterionId).FirstOrDefaultAsync();
#region 验证阅片工具配置
var trialCriterion = await _readingQuestionCriterionTrialRepository.Where(x => x.Id == inDto.TrialReadingCriterionId).FirstNotNullAsync();
var toolList = await _readingQuestionTrialRepository.Where(x => x.ReadingQuestionCriterionTrialId == inDto.TrialReadingCriterionId).Where(x => x.ImageTool != string.Empty).Select(x => x.ImageTool).ToListAsync();
var tabletoolList = await _readingTableQuestionTrialRepository.Where(x => x.ReadingQuestionTrial.ReadingQuestionCriterionTrialId == inDto.TrialReadingCriterionId).Where(x => x.ImageTool != string.Empty).Select(x => x.ImageTool).ToListAsync();
toolList.AddRange(tabletoolList);
toolList = toolList.Distinct().ToList();
if (tabletoolList.Except(trialCriterion.ReadingToolList).Count() > 0)
{
//---问题的阅片工具不在标准配置的阅片工具列表中
throw new BusinessValidationFailedException(_localizer["TrialConfig_TableToolNotInStdTool"]);
}
#endregion
if (trialCriterion == null)
{
return NeedSynchronize.NotNeed;

View File

@ -286,6 +286,8 @@ namespace IRaCIS.Core.Application.Service
public async Task<List<ReadingCustomTagDto>> GetCustomTag(GetCustomTagInDto inQuery)
{
var result = await _readingCustomTagRepository.Where(x => x.VisitTaskId == inQuery.VisitTaskId).ProjectTo<ReadingCustomTagDto>(_mapper.ConfigurationProvider).ToListAsync();
result.AddRange(await _readingTaskQuestionMarkRepository.Where(x => x.VisitTaskId == inQuery.VisitTaskId).ProjectTo<ReadingCustomTagDto>(_mapper.ConfigurationProvider).ToListAsync());
return result;
}
@ -1188,6 +1190,17 @@ namespace IRaCIS.Core.Application.Service
var organIds = tableAnsweRowInfos.Where(x => x.OrganInfoId != null).Select(x => x.OrganInfoId).Distinct().ToList();
var organList = await _organInfoRepository.Where(x => organIds.Contains(x.Id)).ToListAsync();
// 删除病灶不删除病灶标记 所以查询question的时候 已经删除的病灶对应的标记的信息要排除
var rowids = await _readingTableAnswerRowInfoRepository.Where(x => x.Id == inDto.VisitTaskId).Select(x => x.Id).ToListAsync();
List<Guid?> rowidnull = rowids.Select(x => (Guid?)x).ToList();
var tableQuestionMarkList = await _readingTaskQuestionMarkRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId
&& x.RowId == null || rowidnull.Contains(x.RowId)
).ProjectTo<QuestionMarkInfo>(_mapper.ConfigurationProvider).ToListAsync();
return (await GetReadingTableQuestion(
new GetReadingTableQuestionOrAnswerInDto()
{
@ -1203,7 +1216,8 @@ namespace IRaCIS.Core.Application.Service
), new
{
IsBaseline = taskInfo.SourceSubjectVisit != null && taskInfo.SourceSubjectVisit.IsBaseLine,
ReadingTaskState = taskInfo.ReadingTaskState
ReadingTaskState = taskInfo.ReadingTaskState,
QuestionMarkInfoList = tableQuestionMarkList,
});
}
@ -2066,7 +2080,6 @@ namespace IRaCIS.Core.Application.Service
var index = await _readingCalculateService.GetDeleteLesionStatrIndex(inDto);
await _readingTableQuestionAnswerRepository.BatchUpdateNoTrackingAsync(x => x.RowId == inDto.RowId, x => new ReadingTableQuestionAnswer()
{

View File

@ -79,6 +79,7 @@ namespace IRaCIS.Core.Application.Service
CreateMap<TrialDataFromSystem, ReadingMedicineTrialQuestion>().ForMember(dest => dest.CreateUserRole, opt => opt.Ignore());
CreateMap<ReadingCustomTag, ReadingCustomTagDto>();
CreateMap<ReadingTaskQuestionMark, ReadingCustomTagDto>();
CreateMap<ReadingCustomTagDto, ReadingCustomTag>().ForMember(dest => dest.CreateUserRole, opt => opt.Ignore());
CreateMap<UserWLTemplate, UserWLTemplateView>();

View File

@ -79,7 +79,7 @@ namespace IRaCIS.Core.Application.Contracts
/// </summary>
public List<StudyName> StudyNameList { get; set; }
public List<TrialObjectNameConfig> TrialObjectNameList { get; set; }
@ -1040,7 +1040,7 @@ namespace IRaCIS.Core.Application.Contracts
public class TrialSPMConfigCommand
{
public Guid Id { get; set; }
public Guid Id { get; set; }
[Comment("SPM 参与中心调研")]
public bool IsSPMJoinSiteSurvey { get; set; }
@ -1115,6 +1115,12 @@ namespace IRaCIS.Core.Application.Contracts
public List<string> ClinicalDataSetNames { get; set; } = new List<string>();
public List<string> StudyUseModalityList { get; set; }
public List<string> StudyUseStudyNameList { get; set; }
//public bool IsTrialStart { get; set; } = false;
@ -1184,6 +1190,9 @@ namespace IRaCIS.Core.Application.Contracts
public class TrialBodyPartView
{
public bool IsStudyUse { get; set; }
public string Code { get; set; }

View File

@ -484,7 +484,7 @@ namespace IRaCIS.Core.Application
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<(GetTrialReadingCriterionInfoOutDto, bool)> GetTrialReadingCriterionInfo(GetTrialReadingInfoInDto inDto)
public async Task<(GetTrialReadingCriterionInfoOutDto, object)> GetTrialReadingCriterionInfo(GetTrialReadingInfoInDto inDto)
{
GetTrialReadingCriterionInfoOutDto result = new GetTrialReadingCriterionInfoOutDto();
result.ReadingCriterionPageList = await _readingCriterionPageRepository.Where(x => x.TrialId == inDto.TrialId).ProjectTo<ReadingCriterionPageDto>(_mapper.ConfigurationProvider).OrderBy(x => x.ShowOrder).ToListAsync();
@ -503,7 +503,10 @@ namespace IRaCIS.Core.Application
&& x.ReadingCriterionPageId == null)
.ProjectTo<TrialReadQuestion>(_mapper.ConfigurationProvider).OrderBy(x => x.ShowOrder).ToListAsync();
return (result, true);
return (result, new
{
ReadingVersionEnum = trialCriterion.ReadingVersionEnum
});
}
@ -553,6 +556,27 @@ namespace IRaCIS.Core.Application
[TrialGlobalLimit("AfterStopCannNotOpt")]
public async Task<IResponseOutput> SetCriterionReadingInfo(SetCriterionReadingInfoInDto inDto)
{
#region 验证阅片工具配置
var trialCriterion = await _readingQuestionCriterionTrialRepository.Where(x => x.Id == inDto.TrialReadingCriterionId).FirstNotNullAsync();
var toolList = await _readingQuestionTrialRepository.Where(x => x.ReadingQuestionCriterionTrialId == inDto.TrialReadingCriterionId).Where(x => x.ImageTool != string.Empty).Select(x => x.ImageTool).ToListAsync();
var tabletoolList = await _readingTableQuestionTrialRepository.Where(x => x.ReadingQuestionTrial.ReadingQuestionCriterionTrialId == inDto.TrialReadingCriterionId).Where(x => x.ImageTool != string.Empty).Select(x => x.ImageTool).ToListAsync();
toolList.AddRange(tabletoolList);
toolList = toolList.Distinct().ToList();
if (tabletoolList.Except(trialCriterion.ReadingToolList).Count() > 0)
{
//---问题的阅片工具不在标准配置的阅片工具列表中
throw new BusinessValidationFailedException(_localizer["TrialConfig_TableToolNotInStdTool"]);
}
#endregion
ArbitrationRule arbitration = ArbitrationRule.NA;
if (inDto.IsArbitrationReading)
@ -562,7 +586,7 @@ namespace IRaCIS.Core.Application
await _readingQuestionCriterionTrialRepository.UpdatePartialFromQueryAsync(inDto.TrialReadingCriterionId, x => new ReadingQuestionCriterionTrial()
{
ReadingToolList=inDto.ReadingToolList,
ReadingToolList = inDto.ReadingToolList,
IsImageFilter = inDto.IsImageFilter,
ImageDownloadEnum = inDto.ImageDownloadEnum,
ImageUploadEnum = inDto.ImageUploadEnum,
@ -1250,12 +1274,38 @@ namespace IRaCIS.Core.Application
[HttpGet("{trialId:guid}")]
public async Task<TrialConfigDTO> GetTrialConfigInfo(Guid trialId)
{
return await _trialRepository.Where(t => t.Id == trialId).ProjectTo<TrialConfigDTO>(_mapper.ConfigurationProvider, new
var result= await _trialRepository.Where(t => t.Id == trialId).ProjectTo<TrialConfigDTO>(_mapper.ConfigurationProvider, new
{
isEn_Us = _userInfo.IsEn_Us
})
.FirstOrDefaultAsync().IfNullThrowException();
var use = _trialRepository.Where(t => t.Id == trialId).Select(t => new
{
DicomModalityList = t.StudyList.Where(t => t.ModalityForEdit != "")
.Select(c => c.ModalityForEdit)
.Distinct(),
DicomStudyNameList= t.StudyList.Where(t => t.StudyName != "")
.Select(c => c.StudyName)
.Distinct(),
NoneDicomStudyNameList = t.NoneDicomStudyList.Where(t => t.StudyName != "")
.Select(c => c.StudyName)
.Distinct(),
NoneDicomModalityList = t.NoneDicomStudyList.Where(t => t.Modality != "")
.Select(c => c.Modality)
.Distinct(),
}).FirstOrDefault();
result.StudyUseModalityList= use.DicomModalityList.Union(use.NoneDicomModalityList).Distinct().ToList();
result.StudyUseStudyNameList = use.DicomStudyNameList.Union(use.NoneDicomStudyNameList).Distinct().ToList();
return result;
}
[TrialGlobalLimit("BeforeOngoingCantOpt", "AfterStopCannNotOpt")]
@ -1311,7 +1361,35 @@ namespace IRaCIS.Core.Application
/// <returns></returns>
public async Task<IResponseOutput<List<TrialBodyPartView>>> GetTrialBodyPartList(Guid trialId)
{
var list = await _trialRepository.Where(t => t.Id == trialId).SelectMany(t => t.TrialBodyPartList).Select(t => new TrialBodyPartView() { Code = t.Code, Name = _userInfo.IsEn_Us ? t.Name : t.NameCN, Id = t.Id, IsHandAdd = t.IsHandAdd }).OrderBy(t=>t.Name).ToListAsync();
var list = await _trialRepository.Where(t => t.Id == trialId)
.SelectMany(t => t.TrialBodyPartList)
.Select(t => new TrialBodyPartView() { Code = t.Code, Name = _userInfo.IsEn_Us ? t.Name : t.NameCN, Id = t.Id, IsHandAdd = t.IsHandAdd })
.OrderBy(t => t.Name).ToListAsync();
var useBodyPart = _trialRepository.Where(t => t.Id == trialId).Select(t => new
{
DicomBodyPartList = t.StudyList.Where(t => t.BodyPartForEdit != "")
.Select(c => c.BodyPartForEdit)
.Distinct(),
NoneDicomBodyPartList = t.NoneDicomStudyList.Where(t => t.BodyPart != "")
.Select(c => c.BodyPart)
.Distinct(),
}).FirstOrDefault();
foreach (var item in list)
{
var useBodyPartList = new List<string>();
if (useBodyPart != null)
{
useBodyPartList = useBodyPart.DicomBodyPartList.Union(useBodyPart.NoneDicomBodyPartList).Distinct().ToList();
}
item.IsStudyUse = useBodyPartList.Any(t => t == item.Name);
}
return ResponseOutput.Ok(list);
}
@ -1394,7 +1472,7 @@ namespace IRaCIS.Core.Application
[HttpPost]
public async Task<IResponseOutput> UpdateTrialStudyNameList(UpdateTrialStudyNameListInDto inDto)
{
var trial = await _trialRepository.FirstOrDefaultAsync(x => x.Id == inDto.TrialId);

View File

@ -569,6 +569,7 @@ namespace IRaCIS.Core.Application.Services
StudyId = x.StudyId,
InstanceId = x.InstanceId,
NumberOfFrames = x.NumberOfFrames,
CreateTime=x.CreateTime,
}).ToListAsync();
@ -581,6 +582,7 @@ namespace IRaCIS.Core.Application.Services
StudyId = x.StudyId,
InstanceId = x.InstanceId,
NumberOfFrames = x.NumberOfFrames,
CreateTime = x.CreateTime,
}).ToListAsync();
rowInfoList.AddRange(customoList);
@ -627,7 +629,7 @@ namespace IRaCIS.Core.Application.Services
item.InstanceInfoList.ForEach(x =>
{
x.RowDate = rowInfoList.FirstOrDefault(y => y.InstanceId == x.Id)?.CreateTime ?? DateTime.Now;
var keyFramesList = rowInfoList.Where(y => y.InstanceId == x.Id && y.NumberOfFrames != 0 && y.NumberOfFrames != null).Select(y => y.NumberOfFrames).Distinct().ToList();
if (keyFramesList.Count() > 0)
@ -644,7 +646,7 @@ namespace IRaCIS.Core.Application.Services
});
item.InstanceInfoList.OrderBy(x => x.ShowOrder).ThenBy(x => x.RowIndex).ToList();
item.InstanceInfoList = item.InstanceInfoList.OrderBy(x => x.RowDate).ToList();
item.InstanceCount = item.InstanceInfoList.Count;

View File

@ -312,8 +312,15 @@ namespace IRaCIS.Core.Domain.Share
/// </summary>
ReviewerSelection_SPMCPMApproval = 70,
/// <summary>
/// 项目培训 - 生效通知
/// </summary>
TrialTraining_EffectiveNotification = 71,
/// <summary>
/// 项目培训 - 到期通知
/// </summary>
TrialTraining_ExpirationNotification = 72,
}

View File

@ -425,6 +425,23 @@ namespace IRaCIS.Core.Domain.Share
}
/// <summary>
/// 影像标记
/// </summary>
public enum ImageMark
{
/// <summary>
/// 不需要
/// </summary>
NotRequire = 0,
/// <summary>
/// 必须
/// </summary>
Must = 1,
}
/// <summary>
/// 导出结果
/// </summary>

View File

@ -9,6 +9,11 @@ namespace IRaCIS.Core.Domain.Models;
public class VisitTask : BaseFullAuditEntity
{
#region 导航属性
[JsonIgnore]
public List<ReadingTaskQuestionMark> ReadingTaskQuestionMarkList { get; set; }
[JsonIgnore]
public List<TaskStudy> TaskStudyList { get; set; }

View File

@ -33,9 +33,6 @@ public class EmailNoticeConfig : BaseFullDeleteAuditEntity
public string Code { get; set; } = null!;
[Comment("标准枚举")]
public CriterionType? CriterionTypeEnum { get; set; }
public string Description { get; set; } = null!;
[Comment("发送周期")]
@ -68,6 +65,9 @@ public class EmailNoticeConfig : BaseFullDeleteAuditEntity
public bool IsReturnRequired { get; set; }
public SysEmailLevel SystemLevel { get; set; }
[Comment("邮件配置的多个标准")]
public List<CriterionType>? CriterionTypeList { get; set; }
}
[Comment("后台 - 邮件配置用户类型表(需要同步)")]
[Table("EmailNoticeUserType")]

View File

@ -117,7 +117,34 @@ public class FrontAuditConfig : BaseFullAuditEntity
public string ValueCN { get; set; } = null!;
/// <summary>
/// 是否默认选择
/// </summary>
public bool IsDefaultChoice { get; set; } = false;
}
/// <summary>
/// 项目稽查显示
/// </summary>
public class TrialAuditShow: BaseFullAuditEntity
{
/// <summary>
/// 系统配置Id
/// </summary>
public Guid FrontAuditConfigId { get; set; }
/// <summary>
/// 项目Id
/// </summary>
public Guid TrialId { get; set; }
/// <summary>
/// 是否显示
/// </summary>
public bool IsShow { get; set; }
}
public class UrlConfig
{
public bool IsRoute { get; set; }

View File

@ -12,6 +12,10 @@ public class TrialDocument : BaseFullDeleteAuditEntity
[JsonIgnore]
public Trial Trial { get; set; }
[JsonIgnore]
public List<TrialDocumentAttachment> TrialDocumentAttachmentList { get; set; }
[JsonIgnore]
[ForeignKey("FileTypeId")]
public Dictionary FileType { get; set; }
@ -28,5 +32,65 @@ public class TrialDocument : BaseFullDeleteAuditEntity
public string Description { get; set; } = string.Empty;
public int SignViewMinimumMinutes { get; set; }
/// <summary>
/// 现有员工培训天数
/// </summary>
public int? CurrentStaffTrainDays { get; set; }
/// <summary>
/// 新员工培训天数
/// </summary>
public int? NewStaffTrainDays { get; set; }
/// <summary>
/// 是否发布
/// </summary>
public bool IsPublish { get; set; } = true;
}
[Comment("项目签署文档附件")]
[Table("TrialDocumentAttachment")]
public class TrialDocumentAttachment : BaseFullAuditEntity
{
[JsonIgnore]
[ForeignKey("TrialDocumentId")]
public TrialDocument TrialDocument { get; set; }
/// <summary>
/// 项目文档Id
/// </summary>
public Guid TrialDocumentId { get; set; }
public string Name { get; set; } = string.Empty;
/// <summary>
/// 文件名称
/// </summary>
public string FileName { get; set; } = string.Empty;
/// <summary>
/// 是否下线
/// </summary>
public bool OffLine { get; set; }
/// <summary>
/// 文件路径
/// </summary>
[StringLength(1000)]
public string FilePath { get; set; } = string.Empty;
/// <summary>
/// 文件大小
/// </summary>
public decimal? FileSize { get; set; }
/// <summary>
/// 文件类型
/// </summary>
public string FileFormat { get; set; } = string.Empty;
}

View File

@ -42,8 +42,6 @@ public class TrialEmailNoticeConfig : BaseFullDeleteAuditEntity
public string Code { get; set; } = null!;
public CriterionType? CriterionTypeEnum { get; set; }
public string Description { get; set; } = null!;
public string EmailCron { get; set; } = null!;
@ -80,12 +78,23 @@ public class TrialEmailNoticeConfig : BaseFullDeleteAuditEntity
public Guid? SysEmailNoticeConfigId { get; set; }
public Guid? TrialReadingCriterionId { get; set; }
[Comment("邮件延时秒数比如一个事件触发延迟多少s后才发邮件")]
public int? EmailDelaySeconds { get; set; } = null!;
[Comment("后续删除,需要维护数据")]
public CriterionType? CriterionTypeEnum { get; set; }
[Comment("下次版本删除")]
public Guid? TrialReadingCriterionId { get; set; }
[Comment("邮件配置的多个标准")]
public List<CriterionType>? CriterionTypeList { get; set; }
}
[Comment("项目 - 项目邮件用户黑名单")]

View File

@ -101,7 +101,9 @@ public enum SubIdentification
}
/// <summary>
/// 文件类型表 就是5类里面的各个小项 如( 报告里面的 项目总结报告,一致性分析报告...... 文档里面的 项目合同.......)
/// </summary>
[Comment("后台 - 系统文件类型表")]
[Table("SysFileType")]
public class SysFileType : BaseFullAuditEntity
@ -135,6 +137,10 @@ public class SysFileType : BaseFullAuditEntity
}
/// <summary>
/// 文件类型表 就是5类里面的各个小项 如( 报告里面的 项目总结报告,一致性分析报告...... 文档里面的 项目合同.......)
/// </summary>
[Comment("项目文件 - 文件类型表")]
[Table("TrialFileType")]
public class TrialFileType : BaseFullAuditEntity

View File

@ -41,6 +41,14 @@ public class TrialFinalRecord : BaseFullAuditEntity
[JsonIgnore]
[ForeignKey("HistoryFileRecordId")]
public TrialFile HistoryFileRecord { get; set; }
/// <summary>
/// 历史记录
/// </summary>
[JsonIgnore]
public List<TrialHistoryRecordFile> TrialHistoryRecordFileList { get; set; }
#endregion
@ -89,6 +97,11 @@ public class TrialNormalRecord : BaseFullAuditEntity
{
public Guid TrialId { get; set; }
/// <summary>
/// 历史记录
/// </summary>
[JsonIgnore]
public List<TrialHistoryRecordFile> TrialHistoryRecordFileList { get; set; }
/// <summary>
/// 一般文件记录
@ -140,6 +153,13 @@ public class TrialTrianingRecord : BaseFullAuditEntity
[ForeignKey("TrialFileRecordId")]
public TrialFile TrialFileRecord { get; set; }
/// <summary>
/// 历史记录
/// </summary>
[JsonIgnore]
public List<TrialHistoryRecordFile> TrialHistoryRecordFileList { get; set; }
/// <summary>
/// 历史文件记录
/// </summary>
@ -183,7 +203,6 @@ public class TrialTrianingRecord : BaseFullAuditEntity
[Table("TrialFile")]
public class TrialFile : BaseFullAuditEntity
{
/// <summary>
/// TrialFinalRecord
/// </summary>
@ -202,7 +221,48 @@ public class TrialFile : BaseFullAuditEntity
public string FileSize { get; set; }
public string FileFormat { get; set; }
}
/// <summary>
/// 项目历史记录文件表
/// </summary>
public class TrialHistoryRecordFile : BaseFullAuditEntity
{
#region MyRegion
/// <summary>
/// TrialFinalRecord
/// </summary>
[JsonIgnore]
[ForeignKey("TrialFileTypeId")]
public TrialFileType TrialFileType { get; set; }
[JsonIgnore]
[ForeignKey("TrialRecordId")]
public TrialFinalRecord? TrialFinalRecord { get; set; }
[JsonIgnore]
[ForeignKey("TrialRecordId")]
public TrialNormalRecord? TrialNormalRecord { get; set; }
[JsonIgnore]
[ForeignKey("TrialRecordId")]
public TrialTrianingRecord? TrialTrianingRecord { get; set; }
#endregion
/// <summary>
/// 项目记录Id TrialFinalRecord,TrialNormalRecord,TrialTrianingRecord
/// </summary>
public Guid TrialRecordId { get; set; }
[Comment("关联项目文件类型")]
public Guid TrialFileTypeId { get; set; }
public string FileName { get; set; }
[StringLength(1000)]
public string FilePath { get; set; }
public string FileSize { get; set; }
public string FileFormat { get; set; }
}

View File

@ -9,6 +9,11 @@ public class ClinicalForm : BaseAddAuditEntity
[ForeignKey("ClinicalDataTrialSetId")]
public ClinicalDataTrialSet ClinicalDataTrialSet { get; set; }
[JsonIgnore]
[ForeignKey("ReadingId")]
public SubjectVisit SubjectVisit { get; set; }
[JsonIgnore]
[ForeignKey("SubjectId")]
public Subject Subject { get; set; }

View File

@ -279,6 +279,21 @@ public class ReadingQuestionTrial : BaseAddAuditEntity
/// </summary>
public string CDISCCode { get; set; } = string.Empty;
/// <summary>
/// 影像标记
/// </summary>
public ImageMark? ImageMarkEnum { get; set; }
/// <summary>
/// 影像工具
/// </summary>
public string ImageTool { get; set; } = string.Empty;
/// <summary>
/// 影像工具属性
/// </summary>
public string ImageToolAttribute { get; set; } = string.Empty;
[NotMapped]
public List<ExportResult> ExportResult
{

View File

@ -182,6 +182,21 @@ public class ReadingTableQuestionTrial : BaseAddAuditEntity
/// </summary>
public string CDISCCode { get; set; } = string.Empty;
/// <summary>
/// 影像标记
/// </summary>
public ImageMark? ImageMarkEnum { get; set; }
/// <summary>
/// 影像工具
/// </summary>
public string ImageTool { get; set; } = string.Empty;
/// <summary>
/// 影像工具属性
/// </summary>
public string ImageToolAttribute { get; set; } = string.Empty;
[NotMapped]
public List<ExportResult> ExportResult
{

View File

@ -48,6 +48,9 @@ public partial class Trial : BaseFullDeleteAuditEntity
public List<Subject> SubjectList { get; set; } = new List<Subject>();
[JsonIgnore]
public List<SubjectVisit> SubjectVisitList { get; set; } = new List<SubjectVisit>();
[JsonIgnore]
public List<NoneDicomStudy> NoneDicomStudyList { get; set; }
[JsonIgnore]
public List<DicomStudy> StudyList { get; set; } = new List<DicomStudy>();
[JsonIgnore]

View File

@ -10,6 +10,9 @@ public class SubjectVisit : BaseFullDeleteAuditEntity
[JsonIgnore]
public TrialSite TrialSite { get; set; }
[JsonIgnore]
public List<ClinicalForm> ClinicalFormList { get; set; }
[JsonIgnore]
[ForeignKey("OutPlanPreviousVisitId")]
public SubjectVisit OutPlanPreviousVisit { get; set; }

View File

@ -1045,7 +1045,7 @@ namespace IRaCIS.Core.Infra.EFCore.Common
await InsertInspection<TrialEmailNoticeConfig>(entity, type, x => new InspectionConvertDTO()
{
IsDistinctionInterface = false,
ObjectRelationParentId = x.TrialReadingCriterionId,
//ObjectRelationParentId = x.TrialReadingCriterionId,
ExtraIndentification = extraIdentification
},
new

View File

@ -532,6 +532,8 @@ public class IRaCISDBContext : DbContext
public virtual DbSet<SystemDocument> SystemDocument { get; set; }
public virtual DbSet<SystemDocumentAttachment> SystemDocumentAttachment { get; set; }
public virtual DbSet<TrialDocument> TrialDocument { get; set; }
public virtual DbSet<TrialDocumentAttachment> TrialDocumentAttachment { get; set; }
public virtual DbSet<TrialDocNeedConfirmedUserType> TrialDocUserTypeConfirm { get; set; }
public virtual DbSet<SystemDocNeedConfirmedUserType> SystemDocNeedConfirmedUserType { get; set; }
@ -562,8 +564,8 @@ public class IRaCISDBContext : DbContext
public virtual DbSet<DataInspection> DataInspection { get; set; }
public virtual DbSet<FrontAuditConfig> FrontAuditConfig { get; set; }
public virtual DbSet<TrialAuditShow> TrialAuditShow { get; set; }
public virtual DbSet<InspectionFile> InspectionFile { get; set; }
@ -657,6 +659,8 @@ public class IRaCISDBContext : DbContext
public virtual DbSet<TrialNormalRecord> TrialNormalRecord { get; set; }
public virtual DbSet<TrialTrianingRecord> TrialTrianingRecord { get; set; }
public virtual DbSet<TrialFile> TrialFile { get; set; }
public virtual DbSet<TrialHistoryRecordFile> TrialHistoryRecordFile { get; set; }
#endregion

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IRaCIS.Core.Infra.EFCore.Migrations
{
/// <inheritdoc />
public partial class ImageMarkEnum : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ImageMarkEnum",
table: "ReadingTableQuestionTrial",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "ImageMarkEnum",
table: "ReadingQuestionTrial",
type: "int",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ImageMarkEnum",
table: "ReadingTableQuestionTrial");
migrationBuilder.DropColumn(
name: "ImageMarkEnum",
table: "ReadingQuestionTrial");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IRaCIS.Core.Infra.EFCore.Migrations
{
/// <inheritdoc />
public partial class CurrentStaffTrainDays : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "CurrentStaffTrainDays",
table: "TrialDocument",
type: "int",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsPublish",
table: "TrialDocument",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "NewStaffTrainDays",
table: "TrialDocument",
type: "int",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CurrentStaffTrainDays",
table: "TrialDocument");
migrationBuilder.DropColumn(
name: "IsPublish",
table: "TrialDocument");
migrationBuilder.DropColumn(
name: "NewStaffTrainDays",
table: "TrialDocument");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IRaCIS.Core.Infra.EFCore.Migrations
{
/// <inheritdoc />
public partial class IsDefaultChoice : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsDefaultChoice",
table: "FrontAuditConfig",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsDefaultChoice",
table: "FrontAuditConfig");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IRaCIS.Core.Infra.EFCore.Migrations
{
/// <inheritdoc />
public partial class TrialAuditShow : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TrialAuditShow",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
FrontAuditConfigId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TrialId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
IsShow = table.Column<bool>(type: "bit", nullable: false),
CreateUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreateTime = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdateUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UpdateTime = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TrialAuditShow", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_TrialAuditShow_CreateUserId",
table: "TrialAuditShow",
column: "CreateUserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TrialAuditShow");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IRaCIS.Core.Infra.EFCore.Migrations
{
/// <inheritdoc />
public partial class ImageTool : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ImageTool",
table: "ReadingTableQuestionTrial",
type: "nvarchar(400)",
maxLength: 400,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "ImageToolAttribute",
table: "ReadingTableQuestionTrial",
type: "nvarchar(400)",
maxLength: 400,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "ImageTool",
table: "ReadingQuestionTrial",
type: "nvarchar(400)",
maxLength: 400,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "ImageToolAttribute",
table: "ReadingQuestionTrial",
type: "nvarchar(400)",
maxLength: 400,
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ImageTool",
table: "ReadingTableQuestionTrial");
migrationBuilder.DropColumn(
name: "ImageToolAttribute",
table: "ReadingTableQuestionTrial");
migrationBuilder.DropColumn(
name: "ImageTool",
table: "ReadingQuestionTrial");
migrationBuilder.DropColumn(
name: "ImageToolAttribute",
table: "ReadingQuestionTrial");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IRaCIS.Core.Infra.EFCore.Migrations
{
/// <inheritdoc />
public partial class TrialDocumentAttachment : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "TrialDocumentId",
table: "SystemDocumentAttachment",
type: "uniqueidentifier",
nullable: true);
migrationBuilder.CreateTable(
name: "TrialDocumentAttachment",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TrialDocumentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(400)", maxLength: 400, nullable: false),
FileName = table.Column<string>(type: "nvarchar(400)", maxLength: 400, nullable: false),
OffLine = table.Column<bool>(type: "bit", nullable: false),
FilePath = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
FileSize = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
FileFormat = table.Column<string>(type: "nvarchar(400)", maxLength: 400, nullable: false),
CreateUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreateTime = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdateUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UpdateTime = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TrialDocumentAttachment", x => x.Id);
},
comment: "项目签署文档附件");
migrationBuilder.CreateIndex(
name: "IX_SystemDocumentAttachment_TrialDocumentId",
table: "SystemDocumentAttachment",
column: "TrialDocumentId");
migrationBuilder.CreateIndex(
name: "IX_TrialDocumentAttachment_CreateUserId",
table: "TrialDocumentAttachment",
column: "CreateUserId");
migrationBuilder.CreateIndex(
name: "IX_TrialDocumentAttachment_TrialDocumentId",
table: "TrialDocumentAttachment",
column: "TrialDocumentId");
migrationBuilder.AddForeignKey(
name: "FK_SystemDocumentAttachment_TrialDocument_TrialDocumentId",
table: "SystemDocumentAttachment",
column: "TrialDocumentId",
principalTable: "TrialDocument",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SystemDocumentAttachment_TrialDocument_TrialDocumentId",
table: "SystemDocumentAttachment");
migrationBuilder.DropTable(
name: "TrialDocumentAttachment");
migrationBuilder.DropIndex(
name: "IX_SystemDocumentAttachment_TrialDocumentId",
table: "SystemDocumentAttachment");
migrationBuilder.DropColumn(
name: "TrialDocumentId",
table: "SystemDocumentAttachment");
}
}
}

Some files were not shown because too many files have changed in this diff Show More