Compare commits

..

384 Commits

Author SHA1 Message Date
hang bbcab237d4 周期性任务事件根据环境配置,增加默认语言,方便触发的时候,后台知道以什么语言发送邮件
continuous-integration/drone/push Build is failing
2026-02-12 22:36:36 +08:00
hang 010367d018 导表模板修改
continuous-integration/drone/push Build is passing
2026-02-12 03:57:42 -05:00
hang ea43698ee9 修改通知列表人
continuous-integration/drone/push Build is passing
2026-02-12 03:32:26 -05:00
hang 7155f6c985 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
2026-02-12 03:25:00 -05:00
hang 3bdba35501 增加周期性邮件默认Culture,后台触发邮件报错通知企业微信 2026-02-12 03:24:58 -05:00
he ebcf3c95e2 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
2026-02-12 03:15:56 -05:00
he 75c3c9ee06 邮件发送修改 2026-02-12 03:15:54 -05:00
hang 048193e4f3 项目导表查询数据过滤问题
continuous-integration/drone/push Build is passing
2026-02-11 22:08:43 -05:00
hang 7879504b71 优化生成DIR 查询判断 是否存在TransferSytaxUID为空的记录
continuous-integration/drone/push Build is passing
2026-02-11 20:22:02 -05:00
hang 93558164de sql 超时设置为60s
continuous-integration/drone/push Build is passing
2026-02-11 04:57:17 -05:00
hang 83d123a31e 修改工作台统计文档签署数量,加了发布字段,这里漏处理
continuous-integration/drone/push Build is passing
2026-02-11 04:33:31 -05:00
hang 733de4c942 修改启动配置监听地址,内网其他人可以访问
continuous-integration/drone/push Build is passing
2026-02-11 01:24:30 -05:00
hang 0e7bd5f5dd 解析UserId 加一层判断,不知道前端其他啥其他误调用该接口
continuous-integration/drone/push Build is passing
2026-02-10 03:53:07 -05:00
hang 0ef7ef675c 修改退出登录接口,允许角色为null
continuous-integration/drone/push Build is passing
2026-02-09 21:17:47 -05:00
hang ee76366a1c 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
2026-02-09 20:58:46 -05:00
hang 369a0803f9 MFA 返回两种邮箱 2026-02-09 20:58:45 -05:00
he 1f7b96d9f0 修改
continuous-integration/drone/push Build is passing
2026-02-09 20:36:30 -05:00
he 72ff2bbf15 修改邮件
continuous-integration/drone/push Build is running
2026-02-09 20:32:19 -05:00
he 0862182f8e 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
2026-02-09 17:12:20 +08:00
he 746a8de799 修改 2026-02-09 17:12:12 +08:00
hang 37e8545eba 后端增加微信通知控制开关,同时前后端错误分别通知不同的人处理
continuous-integration/drone/push Build is passing
2026-02-06 21:12:52 +08:00
hang 570318cf3b 增加堆栈错误长度
continuous-integration/drone/push Build is passing
2026-02-05 23:28:48 +08:00
hang 7e13edc440 增加异常通知
continuous-integration/drone/push Build is passing
2026-02-05 23:20:35 +08:00
hang 5579181f54 irc项目打包增加企业微信机器人通知
continuous-integration/drone/push Build is passing
2026-02-05 17:00:40 +08:00
he 39b840f8fc 维护doctorCode
continuous-integration/drone/push Build is passing
2026-02-05 15:15:48 +08:00
he 02fb4c9753 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
2026-02-05 14:42:07 +08:00
he cf15883d52 添加doctor code 2026-02-05 14:42:05 +08:00
hang 84380a0d21 去掉异步
continuous-integration/drone/push Build is passing
2026-02-05 10:48:36 +08:00
hang 76993b4c8b 多个线程使用一个dbcontext bug
continuous-integration/drone/push Build is running
2026-02-05 10:44:52 +08:00
hang 0a4f00e63d 邮件模板修改提交
continuous-integration/drone/push Build is passing
2026-02-05 10:08:33 +08:00
he 9a52e30172 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
2026-02-02 13:24:03 +08:00
he 120d583a5a 重阅复制表单 2026-02-02 13:24:01 +08:00
hang dd8e244e64 密码连续错误次数判断位置修改
continuous-integration/drone/push Build is passing
2026-02-02 10:25:54 +08:00
hang 5d7a083f4a 修改打包配置
continuous-integration/drone/push Build is passing
2026-01-30 22:37:29 +08:00
he b7f22245e1 修改
continuous-integration/drone/push Build is passing
2026-01-30 13:42:54 +08:00
he 876deb102c 修改
continuous-integration/drone/push Build is passing
2026-01-30 11:07:32 +08:00
he 49741e5c44 修改
continuous-integration/drone/push Build is passing
2026-01-30 10:16:35 +08:00
he 29ee2ba800 创建医生简历
continuous-integration/drone/push Build is passing
2026-01-30 09:18:54 +08:00
he 65e2bee680 字段添加
continuous-integration/drone/push Build is passing
2026-01-29 17:41:04 +08:00
he d45871a0db 字段添加
continuous-integration/drone/push Build is passing
2026-01-29 14:05:08 +08:00
he ab3b9bddc8 图表修改
continuous-integration/drone/push Build is running
2026-01-29 14:03:28 +08:00
he 7a5ad83cbb 修改简历
continuous-integration/drone/push Build is passing
2026-01-29 11:37:13 +08:00
he 2d2e0f6f2f 修改
continuous-integration/drone/push Build is passing
2026-01-29 11:18:33 +08:00
he 3b5a735abf 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
2026-01-29 11:17:47 +08:00
he e00f8b3549 修改 2026-01-29 11:17:44 +08:00
hang dee8c90319 修改项目文档编辑
continuous-integration/drone/push Build is passing
2026-01-29 09:09:29 +08:00
hang 47fd242200 CRC 提交调研表发送邮件bug
continuous-integration/drone/push Build is running
2026-01-29 09:03:33 +08:00
he 1181540302 添加IsImageQualityControl
continuous-integration/drone/push Build is passing
2026-01-28 16:08:18 +08:00
hang e6b3c662e2 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
2026-01-27 15:18:51 +08:00
hang 3a06ecd66d DIR 生成修改PatientId 2026-01-27 15:18:50 +08:00
hang 7121799900 个人忘记密码后重置 错误次数提交
continuous-integration/drone/push Build is passing
2026-01-26 16:31:46 +08:00
hang c814736427 优化dron打包流程步骤,防止服务器一下子负载过大崩溃 2026-01-26 16:13:02 +08:00
hang 4ac9e83903 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8 2026-01-26 10:58:31 +08:00
hang 782270d462 用户MFA 缓存增加tag,重置密码,修改密码清楚该tag的缓存,从而登录时需要验证MFA 2026-01-26 10:58:29 +08:00
he ad77b66bdd 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
2026-01-23 17:40:57 +08:00
he 77538ab7c2 修改 2026-01-23 17:40:56 +08:00
hang 2e1e042658 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
2026-01-23 14:41:30 +08:00
hang 6736fa96fd 更新影像缩略图 2026-01-23 14:41:26 +08:00
he a965ab9336 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
2026-01-23 13:55:20 +08:00
he 2ce112720a 添加IsBeMark 2026-01-23 13:55:20 +08:00
hang f8b7e7d764 修改时间支持带时区返回
continuous-integration/drone/push Build is passing
2026-01-21 11:48:13 +08:00
hang 1aa1189ae8 时间格式验证增加测试
continuous-integration/drone/push Build is passing
2026-01-21 10:23:30 +08:00
hang 9b90ded79f 修改时间验证
continuous-integration/drone/push Build is passing
2026-01-21 09:42:51 +08:00
hang 61ab33b3f3 efcore 重复跟踪问题解决,以及数组json列,无数据的时候会更新奇怪问题发现
continuous-integration/drone/push Build is passing
2026-01-20 13:18:46 +08:00
hang e6458e66df 修复时间格式bug
continuous-integration/drone/push Build is passing
2026-01-19 20:52:42 +08:00
hang 0ef7fd85d4 修改生成dir路径
continuous-integration/drone/push Build is passing
2026-01-19 17:58:47 +08:00
hang 2bf47515e7 修改模型绑定逻辑
continuous-integration/drone/push Build is passing
2026-01-19 15:33:57 +08:00
hang 296ecfa9d2 修改json 序列化绑定值
continuous-integration/drone/push Build is passing
2026-01-19 15:00:35 +08:00
hang 473d10533a 修改绑定模型逻辑
continuous-integration/drone/push Build is passing
2026-01-19 14:25:59 +08:00
hang 59ba1a6193 修改 可空类型传参处理,添加预处理模型绑定逻辑
continuous-integration/drone/push Build is passing
2026-01-19 13:52:06 +08:00
hang cf2b3f5954 修改有序退回,影响无序退回任务bug
continuous-integration/drone/push Build is passing
2026-01-19 11:03:36 +08:00
hang 7fed9cdceb 修改签署文档
continuous-integration/drone/push Build is passing
2026-01-16 17:23:09 +08:00
hang 0585966215 修改导表bug测试
continuous-integration/drone/push Build is passing
2026-01-16 16:53:09 +08:00
hang 7f9d6afc62 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
2026-01-16 16:50:51 +08:00
hang 6dce301ff7 外部人员管理端查看培训记录 2026-01-16 16:50:48 +08:00
he 7a2d95a77c 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 failing
2026-01-16 16:42:03 +08:00
he 74a8ac7575 添加文档发布时间 2026-01-16 16:42:00 +08:00
hang e9c6d123d3 修改导表
continuous-integration/drone/push Build is failing
2026-01-16 15:57:17 +08:00
hang 310c4a0f70 修改spm 同意重阅,影像bm重复跟踪问题-测试
continuous-integration/drone/push Build is passing
2026-01-15 17:17:23 +08:00
hang 571f81f271 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
2026-01-15 13:47:37 +08:00
hang c3a9ca3ca2 增加日志记录 2026-01-15 13:47:36 +08:00
he 6874459046 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
2026-01-15 10:28:02 +08:00
he f4af6d3218 修改 2026-01-15 10:28:00 +08:00
hang ec87ed1caf 返回前端其他部位
continuous-integration/drone/push Build is passing
2026-01-15 10:13:01 +08:00
hang 0bb5d09a23 修改入组PD报告
continuous-integration/drone/push Build is passing
2026-01-14 10:56:08 +08:00
hang f43ac9e649 修改异常返回
continuous-integration/drone/push Build is passing
2026-01-14 09:36:06 +08:00
hang 9e3180b326 修改配置文件
continuous-integration/drone/push Build is passing
2026-01-14 09:06:29 +08:00
hang 1011e704b9 修改aws配置
continuous-integration/drone/push Build is passing
2026-01-13 10:32:13 +08:00
hang 0afcaa7d70 清楚dir缓存修改
continuous-integration/drone/push Build is passing
2026-01-12 15:13:26 +08:00
hang 5fb889bac9 修改路径增加随机
continuous-integration/drone/push Build is passing
2026-01-12 14:48:09 +08:00
hang febf8868f9 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
2026-01-12 11:39:55 +08:00
hang 665d9a27fb 修改IRC scp 匿名化影响 2026-01-12 11:39:51 +08:00
hang ccf16423d2 后期表索引类型更改,同时不影响之前的表
continuous-integration/drone/push Build is passing
2026-01-10 19:30:47 +08:00
hang a5155457b1 更改项目编号,修改邮件
continuous-integration/drone/push Build is passing
2026-01-09 14:46:39 +08:00
hang e358601232 EA 只看已完成的
continuous-integration/drone/push Build is running
2026-01-09 14:44:11 +08:00
hang 1514694e37 修改irc 匿名化
continuous-integration/drone/push Build is passing
2026-01-08 15:18:08 +08:00
hang fbd128138d 影像恢复代码
continuous-integration/drone/push Build is passing
2026-01-08 10:09:19 +08:00
hang eca7871684 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
2026-01-07 16:41:59 +08:00
hang 8eb31d0c81 修改项目停止,归档 2026-01-07 16:41:56 +08:00
he 051094a742 修改
continuous-integration/drone/push Build is passing
2026-01-07 09:16:50 +08:00
he 9012b9c35c 修改
continuous-integration/drone/push Build is passing
2026-01-07 09:08:55 +08:00
he c49cd2e75e 修改
continuous-integration/drone/push Build is passing
2026-01-06 17:48:31 +08:00
he f8b36df86d 修改
continuous-integration/drone/push Build is running
2026-01-06 17:45:52 +08:00
he 8e5b7fb57c 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
2026-01-06 17:31:31 +08:00
he b5a76804ad 修改 2026-01-06 17:31:30 +08:00
hang d4e79e0905 修改多帧处理
continuous-integration/drone/push Build is passing
2026-01-06 15:16:53 +08:00
hang 11e4c39dba 增加fursionCache 修改
continuous-integration/drone/push Build is passing
2026-01-06 15:05:17 +08:00
hang 02dd26188d 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
2026-01-06 14:32:51 +08:00
hang 4f439c6ee7 scp缩略图修改 2026-01-06 14:32:50 +08:00
he bb36240c3c 修改
continuous-integration/drone/push Build is running
2026-01-06 14:32:08 +08:00
he e17b26b41d 代码修改
continuous-integration/drone/push Build is passing
2026-01-06 14:14:15 +08:00
he 3fce7b772e 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
2026-01-06 13:39:41 +08:00
he 8ca740bae3 修改 2026-01-06 13:39:40 +08:00
hang ead07cb1ca Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build encountered an error
2026-01-06 10:27:53 +08:00
hang 951081edb7 修改IRC 多帧文件处理 2026-01-06 10:27:51 +08:00
he 7ceca5d802 修改
continuous-integration/drone/push Build is passing
2026-01-05 18:01:07 +08:00
he 86a3f1d2a5 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
2026-01-05 17:46:27 +08:00
he b0a349b5ff 修改 2026-01-05 17:46:25 +08:00
hang 7523f646fe 修改orr 统计
continuous-integration/drone/push Build is passing
2026-01-05 16:26:10 +08:00
hang fcd0dd9b08 pcwg3 NA字典不一样
continuous-integration/drone/push Build is passing
2026-01-05 16:03:06 +08:00
hang 208cb40525 修改PCWG3
continuous-integration/drone/push Build is passing
2026-01-05 15:51:55 +08:00
hang aa2861c399 修改视图模型
continuous-integration/drone/push Build is passing
2026-01-05 15:39:17 +08:00
hang 4ee064dd14 查询,更新接口修改
continuous-integration/drone/push Build is passing
2026-01-05 15:13:58 +08:00
hang 8803a5f5b2 计算天数按照30.44算,增加控制字段
continuous-integration/drone/push Build is passing
2026-01-05 14:27:23 +08:00
hang 834f6be548 增加中心ORR统计
continuous-integration/drone/push Build is passing
2026-01-05 14:02:15 +08:00
hang df8292b2a5 修改统计值
continuous-integration/drone/push Build is passing
2026-01-05 13:27:54 +08:00
he 312c96ef8f 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
2026-01-05 10:00:12 +08:00
he de29bd7d15 邮件发送修改 2026-01-05 10:00:09 +08:00
hang 3a2c78fe37 修改统计质询
continuous-integration/drone/push Build is passing
2026-01-04 16:46:10 +08:00
he 4465e19987 修改
continuous-integration/drone/push Build is passing
2026-01-04 13:17:10 +08:00
he afe84fdb5b 修改
continuous-integration/drone/push Build is passing
2026-01-04 13:04:46 +08:00
he 728727af54 修改
continuous-integration/drone/push Build is passing
2026-01-04 12:29:22 +08:00
he 987667f59f 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
2026-01-04 10:41:54 +08:00
he af1ca2ffc1 修改 2026-01-04 10:41:52 +08:00
hang e71682f8fa 合并
continuous-integration/drone/push Build is passing
2026-01-04 10:37:37 +08:00
hang 339414694d 中心调研修改,oss测试 2026-01-04 10:35:15 +08:00
he 3ad6fad018 修改
continuous-integration/drone/push Build is passing
2025-12-31 16:05:40 +08:00
he adf82e862d 修改
continuous-integration/drone/push Build is passing
2025-12-31 14:52:33 +08:00
he c9e4f63e03 修改验证
continuous-integration/drone/push Build is passing
2025-12-31 11:48:28 +08:00
he ade2ffd9c0 修改
continuous-integration/drone/push Build is passing
2025-12-30 17:59:47 +08:00
he 3919d6a8a3 结构修改
continuous-integration/drone/push Build is failing
2025-12-30 17:38:15 +08:00
he 0fab295995 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 pending
2025-12-30 17:14:17 +08:00
he aa7eac50a5 报错修改 2025-12-30 17:14:16 +08:00
hang 23e0fae6ac 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
2025-12-30 14:13:40 +08:00
hang 27233c1e01 修改日志保留天数以及统计 2025-12-30 14:13:38 +08:00
he c16905101f 修改
continuous-integration/drone/push Build is passing
2025-12-30 11:36:52 +08:00
he 6ef9708bb7 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
2025-12-30 10:19:22 +08:00
he cd9769b39c 获取Series信息 修改 2025-12-30 10:19:21 +08:00
hang 109bdd263e 修改统计
continuous-integration/drone/push Build is passing
2025-12-30 10:09:20 +08:00
he c8e5a5173c 修改
continuous-integration/drone/push Build is passing
2025-12-29 16:58:24 +08:00
he e94e875a8a 修改
continuous-integration/drone/push Build is passing
2025-12-29 16:47:21 +08:00
he 9d7bd3c35b 图表修改
continuous-integration/drone/push Build is passing
2025-12-29 16:40:34 +08:00
he 5725e684c0 修改
continuous-integration/drone/push Build is passing
2025-12-29 13:42:35 +08:00
he 7927ad4161 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
2025-12-29 13:37:10 +08:00
he e070a78872 图表功能添加 2025-12-29 13:37:08 +08:00
hang 1a6ec6b315 中心调研登录
continuous-integration/drone/push Build is passing
2025-12-29 11:07:10 +08:00
he 64ea537dae 修改
continuous-integration/drone/push Build is passing
2025-12-26 17:39:39 +08:00
he 990cf0ec41 修改
continuous-integration/drone/push Build is passing
2025-12-26 15:10:24 +08:00
he 9cdd70f491 发布
continuous-integration/drone/push Build is passing
2025-12-26 14:29:33 +08:00
he a8623ade44 修改
continuous-integration/drone/push Build is passing
2025-12-26 14:18:48 +08:00
he c0e19eaab4 修改
continuous-integration/drone/push Build is passing
2025-12-26 13:30:26 +08:00
he 43241709af 修改
continuous-integration/drone/push Build is passing
2025-12-26 11:16:24 +08:00
he 292263a60c 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
2025-12-26 10:49:17 +08:00
he 760f75061e 修改 2025-12-26 10:49:16 +08:00
hang e11ca4a244 uat-靶段多选-1
continuous-integration/drone/push Build is passing
2025-12-26 10:42:02 +08:00
he 0266072c6a 医学审核单位添加
continuous-integration/drone/push Build is running
2025-12-26 10:41:12 +08:00
he 99b76704d3 修改
continuous-integration/drone/push Build is passing
2025-12-25 17:07:57 +08:00
he b39edee3eb 修改
continuous-integration/drone/push Build is passing
2025-12-25 16:12:13 +08:00
he 9e426308d0 修改
continuous-integration/drone/push Build is passing
2025-12-25 16:02:55 +08:00
he 416d76e03a 发布
continuous-integration/drone/push Build is passing
2025-12-25 15:51:53 +08:00
he ae867ae230 修改
continuous-integration/drone/push Build is passing
2025-12-25 15:44:05 +08:00
he e83dc93dca 修改
continuous-integration/drone/push Build is passing
2025-12-25 14:35:42 +08:00
he da220a3f5a 修改
continuous-integration/drone/push Build is passing
2025-12-25 14:15:05 +08:00
he 8141c64f87 修改
continuous-integration/drone/push Build is passing
2025-12-25 13:35:19 +08:00
he 5aaedffe20 修改融合
continuous-integration/drone/push Build is passing
2025-12-25 13:21:03 +08:00
he 9ec53e39ec 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
2025-12-25 10:05:41 +08:00
he c93807d67f 修改答案 2025-12-25 10:05:39 +08:00
hang 6430f11188 返回标准类型-uat
continuous-integration/drone/push Build is passing
2025-12-25 10:01:20 +08:00
he bc4c523d88 修改
continuous-integration/drone/push Build is passing
2025-12-25 09:56:33 +08:00
he b7aaeee13e 修改拷贝病灶
continuous-integration/drone/push Build is passing
2025-12-24 17:46:55 +08:00
he 504304c341 融合修改
continuous-integration/drone/push Build is passing
2025-12-24 17:28:11 +08:00
he 941bddb771 维护分裂和融合病灶关系
continuous-integration/drone/push Build is passing
2025-12-24 15:17:01 +08:00
he 1990cebd58 修改融合验证
continuous-integration/drone/push Build is passing
2025-12-24 14:37:34 +08:00
he 041d6724e2 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
2025-12-24 13:49:44 +08:00
he 0086ed4723 修改融合判定 2025-12-24 13:49:43 +08:00
hang b83dd29b20 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
2025-12-24 10:53:47 +08:00
hang aa01c68887 修改拷贝病灶状态 2025-12-24 10:53:45 +08:00
he 00f5fa993a 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
2025-12-24 09:31:21 +08:00
he 6b98e23e50 复制病灶修改 2025-12-24 09:31:18 +08:00
hang 3810b33c9e 修改模态
continuous-integration/drone/push Build is passing
2025-12-24 09:15:04 +08:00
he e58b0e5315 默认密码修改
continuous-integration/drone/push Build is passing
2025-12-24 09:05:26 +08:00
he a2548c4f95 邮件名称修改
continuous-integration/drone/push Build is passing
2025-12-23 17:36:59 +08:00
he 0151305ca3 修改
continuous-integration/drone/push Build is passing
2025-12-23 14:55:41 +08:00
he fbdac497fa 修改导入
continuous-integration/drone/push Build is passing
2025-12-23 10:25:44 +08:00
he 052ac1ff8d 导入修改
continuous-integration/drone/push Build is passing
2025-12-23 09:48:00 +08:00
he e113bc9fe1 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
2025-12-23 09:26:49 +08:00
he 03b7f00bcd 修改计算 2025-12-23 09:26:47 +08:00
hang 3bedea2aba 修改邮件默认值
continuous-integration/drone/push Build is passing
2025-12-22 16:02:30 +08:00
hang 90a7e20be4 项目邮件配置修改
continuous-integration/drone/push Build is passing
2025-12-22 14:04:03 +08:00
he 4a02ae2e98 修改
continuous-integration/drone/push Build is passing
2025-12-22 10:19:36 +08:00
he 9852cacb01 导入修改
continuous-integration/drone/push Build is passing
2025-12-19 18:27:46 +08:00
he eda79fd455 修改导入验证
continuous-integration/drone/push Build is passing
2025-12-19 18:20:39 +08:00
he 45af1d7ba6 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
2025-12-19 18:08:52 +08:00
he 7a4520086f 修改导入 2025-12-19 18:08:51 +08:00
hang a252142cef 下载展示列表-2
continuous-integration/drone/push Build is passing
2025-12-19 17:42:07 +08:00
hang 12a0f7ad82 修改退回过滤-ivus-靶段标注处理 2025-12-19 17:42:02 +08:00
hang 3ef3586bf7 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
2025-12-19 17:06:43 +08:00
hang 4aecfb3bed 修改导表结果 2025-12-19 17:06:39 +08:00
hang 9a31e04fad linux服务器部署 / \ 对于路径的影响
continuous-integration/drone/push Build is passing
2025-12-19 13:30:02 +08:00
hang af333e72f5 测试dir 文件名\替换为/ 2025-12-19 13:29:58 +08:00
hang 26f1a2b8ee DIR 增加项目编号匹配 2025-12-19 13:29:53 +08:00
he 497d91f4a4 任务检查部位查看
continuous-integration/drone/push Build is passing
2025-12-19 10:17:53 +08:00
he bd2d698d9f 导入验证修改
continuous-integration/drone/push Build is passing
2025-12-19 10:05:01 +08:00
he b4c884a367 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
2025-12-18 17:46:02 +08:00
he 337dec37b8 检查部位查看 2025-12-18 17:46:01 +08:00
hang c12ecfdcf0 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
2025-12-18 17:42:57 +08:00
hang 919e2bcdd7 修改统计bug 2025-12-18 17:42:54 +08:00
he 943dbb28a6 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
2025-12-18 17:42:12 +08:00
he 0926da24f7 修改查看裁判 检查部位 2025-12-18 17:42:10 +08:00
hang 6ad80f540d 项目邮件默认值
continuous-integration/drone/push Build is passing
2025-12-18 17:08:42 +08:00
hang 77b9b14cb6 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
2025-12-18 16:57:58 +08:00
hang b9c55871b8 完整返回字典选项 2025-12-18 16:57:55 +08:00
he 848581452a 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
2025-12-18 16:43:18 +08:00
he 2e53cdf29b 修改 2025-12-18 16:43:18 +08:00
hang d9bc12c8d4 增加百分号
continuous-integration/drone/push Build is passing
2025-12-18 16:39:20 +08:00
hang 234f8b074b oss-天然不支持真流式上传,除非大文件在本地盘,否则会占用内存 2025-12-18 16:39:17 +08:00
he 079c11a7e3 修改邮件
continuous-integration/drone/push Build is passing
2025-12-18 15:47:36 +08:00
he 9d9d65e381 修改文档发布
continuous-integration/drone/push Build is passing
2025-12-18 15:02:26 +08:00
he 2517c57778 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
2025-12-18 14:43:48 +08:00
he 5d15ae6fe3 稽查修改 2025-12-18 14:43:47 +08:00
hang f9fbb59c53 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
2025-12-18 13:50:23 +08:00
hang c55f7a427c 修改斑块明细表,增加单位-修改1 2025-12-18 13:47:54 +08:00
he 70b8c23e41 绑定标记
continuous-integration/drone/push Build is passing
2025-12-18 13:46:22 +08:00
he 2591a794bf 稽查调整
continuous-integration/drone/push Build is passing
2025-12-18 10:53:23 +08:00
he 1c4be1012a 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
2025-12-18 10:45:50 +08:00
he 030eeb663b 稽查调整 2025-12-18 10:45:49 +08:00
hang 4b3180bdfe uat-ivus-oct-再次修改15
continuous-integration/drone/push Build is passing
2025-12-18 10:32:34 +08:00
hang 28b7cdb187 uat-ivus-oct-再次修改14 2025-12-18 10:32:31 +08:00
hang f57f6bb389 uat-ivus-oct-再次修改13 2025-12-18 10:32:28 +08:00
hang 673abdfec2 uat-ivus-oct-再次修改12 2025-12-18 10:32:25 +08:00
hang 1d4d068e08 uat-ivus-oct-再次修改11 2025-12-18 10:32:22 +08:00
hang 72d83e9cfd uat-ivus-oct-再次修改10 2025-12-18 10:32:18 +08:00
hang 883783250f uat-ivus-oct-再次修改9 2025-12-18 10:32:08 +08:00
he cc1ae55480 稽查调整
continuous-integration/drone/push Build is running
2025-12-18 10:30:04 +08:00
he 1bc3e77c1a 修改验证
continuous-integration/drone/push Build is passing
2025-12-17 14:17:40 +08:00
he e24d865ce3 修改验证1
continuous-integration/drone/push Build is passing
2025-12-17 14:06:20 +08:00
he 2762c83d84 验证修改
continuous-integration/drone/push Build is passing
2025-12-17 13:52:52 +08:00
he c58ad6142f 验证提交
continuous-integration/drone/push Build is passing
2025-12-17 13:41:17 +08:00
he c45f5ca083 验证修改
continuous-integration/drone/push Build is passing
2025-12-17 11:06:01 +08:00
he fc5c792328 验证修改
continuous-integration/drone/push Build is passing
2025-12-17 10:52:17 +08:00
he 1b00209c55 ivus计算触发
continuous-integration/drone/push Build is passing
2025-12-17 10:37:25 +08:00
he f199d18c4b Ivus 计算修改
continuous-integration/drone/push Build is passing
2025-12-17 10:17:47 +08:00
he 3f1d07727b 添加PAV其他问题答案
continuous-integration/drone/push Build is passing
2025-12-16 17:58:30 +08:00
he 64d70269f9 导入编号限制 只能123
continuous-integration/drone/push Build is passing
2025-12-16 17:37:46 +08:00
hang f0a71f8e70 ivus oct 全量一致性核查过滤空检查
continuous-integration/drone/push Build is passing
2025-12-16 13:30:24 +08:00
hang 8901479dd5 uat-ivus-oct-再次修改8 2025-12-16 13:21:38 +08:00
hang 389456c46b uat-ivus-oct-再次修改7 2025-12-16 13:21:34 +08:00
hang 40483dd4ee 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
2025-12-16 09:57:29 +08:00
hang fe1d7724bc uat-ivus-oct-再次修改6 2025-12-16 09:57:22 +08:00
hang 9204183add uat-ivus-oct-再次修改5 2025-12-16 09:56:53 +08:00
hang db9895077c uat-ivus-oct-再次修改4 2025-12-16 09:55:36 +08:00
hang 36c086136f uat-ivus-oct-再次修改3 2025-12-16 09:55:32 +08:00
hang f3c78c1892 uat-ivus-oct-再次修改2 2025-12-16 09:55:28 +08:00
hang 6d538bf250 uat-ivus-oct-再次修改1 2025-12-16 09:55:24 +08:00
he 7a84eebf98 复制病灶
continuous-integration/drone/push Build is passing
2025-12-16 09:04:48 +08:00
he 064101b155 修改稽查
continuous-integration/drone/push Build is passing
2025-12-15 10:51:52 +08:00
he 91e7d04e4d 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
2025-12-12 16:58:19 +08:00
he 711fa5a2b8 稽查管理 2025-12-12 16:58:17 +08:00
hang 9f7bc4be75 修改返回数据信息
continuous-integration/drone/push Build is passing
2025-12-12 15:16:51 +08:00
hang df7022b2e3 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
2025-12-12 13:31:50 +08:00
hang 6ee51a7f43 增加搜索条件 2025-12-12 13:31:49 +08:00
he aed8a77e13 修改
continuous-integration/drone/push Build is passing
2025-12-12 10:40:10 +08:00
he bcfa22a818 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
2025-12-11 17:19:47 +08:00
he 0840d57689 修改计算 2025-12-11 17:19:45 +08:00
hang deb7e77263 项目文档默认禁用
continuous-integration/drone/push Build is passing
2025-12-11 10:26:16 +08:00
hang aba477a49c 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
2025-12-11 09:37:45 +08:00
hang a9e5a7754e 数据统计修改 2025-12-11 09:37:43 +08:00
he a2d4d100d3 修改邮件
continuous-integration/drone/push Build is passing
2025-12-10 16:36:24 +08:00
he 7f4f5e9d08 修改邮件发送
continuous-integration/drone/push Build is passing
2025-12-10 15:24:57 +08:00
he 55a2301bb5 稽查翻译修改 OCT06
continuous-integration/drone/push Build is passing
2025-12-10 13:59:18 +08:00
he 14ac852eee 修改
continuous-integration/drone/push Build is passing
2025-12-10 13:16:36 +08:00
he d2e53c7b8c 修改
continuous-integration/drone/push Build is passing
2025-12-10 13:11:45 +08:00
he 4a335d2a28 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
2025-12-10 10:43:22 +08:00
he 999fde1f5c 仲裁验证修改 2025-12-10 10:43:19 +08:00
hang 024e62103a ivus-oct-导表修改-uat-4
continuous-integration/drone/push Build is failing
2025-12-09 17:49:13 +08:00
hang 73889c51ae ivus-oct-导表修改-uat-3 2025-12-09 17:49:10 +08:00
hang 7a0b29bda6 ivus-oct-导表修改-uat-2
continuous-integration/drone/push Build is failing
2025-12-09 17:32:43 +08:00
hang a592a39fae ivus-oct-导表修改-uat-1 2025-12-09 17:32:40 +08:00
hang aa96ef66c6 修改多选导表
continuous-integration/drone/push Build is failing
2025-12-09 13:10:46 +08:00
he 2b24ce723e OCT05
continuous-integration/drone/push Build is failing
2025-12-09 10:02:44 +08:00
he 867ee32f91 稽查修改 OCT4
continuous-integration/drone/push Build is failing
2025-12-08 17:48:12 +08:00
he 662001accd 稽查修改 OCT4
continuous-integration/drone/push Build is failing
2025-12-08 17:17:57 +08:00
he 334b2fe440 OCT修改3
continuous-integration/drone/push Build is failing
2025-12-08 16:55:47 +08:00
he 2f728629f3 OCT 修改2
continuous-integration/drone/push Build is failing
2025-12-08 16:15:29 +08:00
he eb8628da5b OCT 添加1
continuous-integration/drone/push Build is failing
2025-12-08 15:41:09 +08:00
he 1427940224 邮件日志修改
continuous-integration/drone/push Build is failing
2025-12-08 13:36:41 +08:00
he 06f10e38f5 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
2025-12-08 13:13:51 +08:00
he 5092f5204f 去除多余的逻辑 2025-12-08 13:13:50 +08:00
hang 13f4420e42 修改项目文档过滤
continuous-integration/drone/push Build is passing
2025-12-08 10:38:23 +08:00
he e0e6e60ae0 邮件端口移动到配置文件
continuous-integration/drone/push Build is passing
2025-12-08 10:14:21 +08:00
he 9cac49d539 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
2025-12-05 14:54:24 +08:00
he 97d9aefbeb 项目邮件改为项目的发件信息 2025-12-05 14:54:22 +08:00
hang 340119f697 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
2025-12-05 14:13:30 +08:00
hang a075eb34cd 修改签署文档查看 2025-12-05 14:13:30 +08:00
he 53913d3777 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
2025-12-05 14:02:35 +08:00
he c1d79a7e9f 修改邮件中时间的问题 2025-12-05 14:02:33 +08:00
hang 3ee6f0ceaa 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
2025-12-05 14:01:57 +08:00
hang 1821d82ded 中心调研默认配置 2025-12-05 14:01:56 +08:00
hang 476ca95aa3 uat-项目影像下载-dir
continuous-integration/drone/push Build is passing
2025-12-05 13:50:11 +08:00
hang d8b799f057 uat-发布导表-1 2025-12-05 13:50:05 +08:00
he 4c4807c22e 添加字段
continuous-integration/drone/push Build is passing
2025-12-04 17:24:04 +08:00
he 8a9afc2d9a 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
2025-12-04 14:16:50 +08:00
he 6fcb670a89 OCT导入修改1 2025-12-04 14:16:47 +08:00
hang d224b519f7 修改中心调研返回配置对象bug
continuous-integration/drone/push Build is passing
2025-12-04 10:19:23 +08:00
hang b382595767 修改EA 下拉框返回数据
continuous-integration/drone/push Build is passing
2025-12-03 17:53:43 +08:00
hang 64ff6f73d4 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 failing
2025-12-03 16:21:29 +08:00
hang e1632b58ac lugano 长短经导表修改 2025-12-03 16:21:26 +08:00
he 7e39692cc4 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 failing
2025-12-03 15:13:41 +08:00
he 8de44fb7b3 添加重阅复制的逻辑 2025-12-03 15:13:38 +08:00
hang 647aee010a 赋值拷贝表单
continuous-integration/drone/push Build is running
2025-12-03 15:12:04 +08:00
hang 296459d38a 全量一致性核查默认排序
continuous-integration/drone/push Build is passing
2025-12-03 13:55:12 +08:00
hang 7941328a29 CDISC followUp
continuous-integration/drone/push Build is passing
2025-12-03 13:29:27 +08:00
hang 38d9a21a5d lugano 整体肿瘤pd
continuous-integration/drone/push Build is passing
2025-12-03 11:33:11 +08:00
he 7d88f49e68 修改被评估为NE的单个靶病灶
continuous-integration/drone/push Build is passing
2025-12-03 10:39:15 +08:00
he 3c46215a3c 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
2025-12-03 10:19:36 +08:00
he 137ed07b2b 修改 2025-12-03 10:19:33 +08:00
hang 6f05850fbc 全量一致性核查增加列
continuous-integration/drone/push Build is passing
2025-12-02 18:04:38 +08:00
hang 80447f5c7a 修改参数名
continuous-integration/drone/push Build is passing
2025-12-02 17:42:06 +08:00
hang 971d94495a 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
2025-12-02 17:30:05 +08:00
hang 77790a1cb2 中心调研增加字段 2025-12-02 17:30:02 +08:00
he ecfadcb5a3 查询基线病灶
continuous-integration/drone/push Build is passing
2025-12-02 17:24:32 +08:00
he d9e3b642a5 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
2025-12-02 16:32:00 +08:00
he e56e269f2e 修改 2025-12-02 16:31:59 +08:00
hang 98f7ab8c02 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
2025-12-02 15:35:16 +08:00
hang 5d33bd03a7 修改lugano rs导表肿瘤学 2025-12-02 15:35:13 +08:00
he 55882aa0ba 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
2025-12-02 11:27:15 +08:00
he 43d1a94f22 添加字段 2025-12-02 11:27:11 +08:00
hang 53b2b9156f 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
2025-12-02 10:54:31 +08:00
hang fafc075735 uat-lugano NA% 2025-12-02 10:54:28 +08:00
hang 09ee238e50 uat-ivus-增加标识-31
continuous-integration/drone/push Build is passing
2025-12-02 09:18:21 +08:00
hang 7be3813527 CDISC-lugano
continuous-integration/drone/push Build is passing
2025-12-01 14:38:11 +08:00
hang e6491a6203 修改导表错误-uat
continuous-integration/drone/push Build is passing
2025-12-01 13:21:40 +08:00
he 4955a24d6f 修改
continuous-integration/drone/push Build is passing
2025-12-01 10:52:14 +08:00
he 425f53b74c 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
2025-11-28 17:27:13 +08:00
he 2ed9595186 修改 2025-11-28 17:27:12 +08:00
hang 4d1e3beda6 uat-标注-30
continuous-integration/drone/push Build is passing
2025-11-28 16:47:08 +08:00
hang 8b0e1b8988 uat-标注-29 2025-11-28 16:47:04 +08:00
he c77682a9ad 修改
continuous-integration/drone/push Build is passing
2025-11-28 16:15:58 +08:00
he 8e0ff1f2b2 修改
continuous-integration/drone/push Build is passing
2025-11-28 15:31:23 +08:00
he b5e027a553 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
2025-11-28 15:28:16 +08:00
he ef712949ad 修改 2025-11-28 15:28:14 +08:00
hang b429b2cf8c uat-标注-28
continuous-integration/drone/push Build is passing
2025-11-28 14:19:24 +08:00
hang 9fb95c3030 uat-标注-27 2025-11-28 14:19:21 +08:00
hang 6883d47508 uat-标注-26 2025-11-28 14:19:19 +08:00
hang dae44745ca uat-标注-25 2025-11-28 14:19:16 +08:00
hang 24b7d2e1b9 uat-标注-24 2025-11-28 14:19:13 +08:00
hang 21ac6850d2 uat-标注-23 2025-11-28 14:19:10 +08:00
hang 8b69a07b93 uat-标注-22 2025-11-28 14:19:07 +08:00
hang f92bf347e3 uat-标注-21 2025-11-28 14:18:51 +08:00
hang 78669051de uat-标注-20 2025-11-28 14:18:05 +08:00
hang ea90b2b38b uat-标注-19 2025-11-28 14:18:02 +08:00
hang bd61a0f2e9 uat-标注-18 2025-11-28 14:17:59 +08:00
hang 888ad8897e uat-标注-17 2025-11-28 14:17:56 +08:00
hang eb18021988 uat-标注-16 2025-11-28 14:17:53 +08:00
he 7b243a9615 修改
continuous-integration/drone/push Build is passing
2025-11-28 14:07:54 +08:00
he 5716c4169a 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 failing
2025-11-28 11:36:49 +08:00
he abb9f47646 邮件临时修改 2025-11-28 11:36:47 +08:00
hang 9a5221ef7d 调整ivus导表位置
continuous-integration/drone/push Build is failing
2025-11-27 17:31:56 +08:00
hang d9eaa06de3 中心调研返回空对象,方便前端渲染
continuous-integration/drone/push Build is failing
2025-11-27 15:20:56 +08:00
hang 4f91874b82 uat-标注-15
continuous-integration/drone/push Build is passing
2025-11-27 10:57:51 +08:00
hang 139438133d uat-标注-14 2025-11-27 10:57:48 +08:00
hang 6705cf0fc0 uat-标注-13 2025-11-27 10:57:46 +08:00
hang 00255e902c uat-标注-12 2025-11-27 10:57:43 +08:00
hang 71e867156f uat-标注-11 2025-11-27 10:57:41 +08:00
hang 658f437f73 uat-标注-10 2025-11-27 10:57:39 +08:00
hang 95bf613263 非dicom检查预览 2025-11-27 10:57:36 +08:00
hang 03d61347ca uat-标注-8 2025-11-27 10:57:32 +08:00
hang 050a0875fe uat-标注-7 2025-11-27 10:57:30 +08:00
hang d9c46acb03 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
2025-11-27 10:56:07 +08:00
hang f19fc0e92f ivus-导表修改 2025-11-27 10:56:05 +08:00
he 3bc4b86749 ExtImaging 修改
continuous-integration/drone/push Build is passing
2025-11-26 16:55:05 +08:00
he 43455da7bc 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
2025-11-26 11:37:05 +08:00
he 0030eac22e 修改 2025-11-26 11:37:00 +08:00
hang 9158f28998 医学审核对话导表 2025-11-25 09:56:51 +08:00
181 changed files with 255979 additions and 3042 deletions
+4
View File
@@ -113,6 +113,9 @@ builder.Services.AddAutoMapper(automapper =>
//EF ORM QueryWithNoLock
builder.Services.AddEFSetup(_configuration);
// FusionCache
builder.Services.AddFusionCache();
builder.Services.AddMediator(cfg =>
{
@@ -191,6 +194,7 @@ app.MapControllers();
Log.Logger = new LoggerConfiguration()
//.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
+420 -168
View File
@@ -1,28 +1,29 @@
using FellowOakDicom.Network;
using FellowOakDicom;
using FellowOakDicom;
using FellowOakDicom.Imaging;
using FellowOakDicom.IO.Buffer;
using FellowOakDicom.Network;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.SCP.Service;
using Medallion.Threading;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Serilog;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using System;
using System.Collections.Generic;
using System.Data;
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;
using IRaCIS.Core.Infrastructure.Extention;
using FellowOakDicom.IO.Buffer;
using ZiggyCreatures.Caching.Fusion;
namespace IRaCIS.Core.SCP.Service
{
@@ -51,6 +52,8 @@ namespace IRaCIS.Core.SCP.Service
private Guid _trialSiteId { get; set; }
private bool _releasedNormally = false;
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
@@ -118,7 +121,7 @@ namespace IRaCIS.Core.SCP.Service
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId == _trialId).FirstOrDefault();
if (findTrialSiteAE != null)
{
@@ -127,7 +130,7 @@ namespace IRaCIS.Core.SCP.Service
isCanReceiveIamge = true;
}
}
if (association.CallingAE == "test-callingAE")
@@ -180,12 +183,15 @@ namespace IRaCIS.Core.SCP.Service
await AddUploadLogAsync();
_releasedNormally = true;
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
Log.Logger.Information($"进入释放连接请求 {_releasedNormally}");
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
}
@@ -199,15 +205,25 @@ namespace IRaCIS.Core.SCP.Service
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _ImageUploadList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
//有时候没有建立连接会走关闭通过日志发现
if (_upload != null)
{
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _ImageUploadList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
_upload.UploadJsonStr = (new SCPImageLog() { UploadList = _ImageUploadList }).ToJsonStr();
//可能是测试echo 导致记录了
if (_upload.FileCount > 0)
{
//可能是测试echo 导致记录了
await _SCPImageUploadRepository.AddAsync(_upload, true);
}
}
_upload.UploadJsonStr = (new SCPImageLog() { UploadList = _ImageUploadList }).ToJsonStr();
//可能是测试echo 导致记录了
await _SCPImageUploadRepository.AddAsync(_upload, _upload.FileCount > 0 ? true : false);
}
@@ -262,22 +278,25 @@ namespace IRaCIS.Core.SCP.Service
public async void OnConnectionClosed(Exception exception)
{
/* nothing to do here */
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//奇怪的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();
}
else
if (exception != null || _releasedNormally == false)
{
//客户端断网,恢复后,也是没有异常的,估计是超时走了关闭
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = true });
//记录日志
await AddUploadLogAsync();
}
else
{
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = false });
}
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
}
@@ -316,6 +335,11 @@ namespace IRaCIS.Core.SCP.Service
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var _fusionCache = _serviceProvider.GetService<IFusionCache>();
var _trialSiteRepository = _serviceProvider.GetService<IRepository<TrialSite>>();
var _systemAnonymizationRepository = _serviceProvider.GetService<IRepository<SystemAnonymization>>();
var storeRelativePath = string.Empty;
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
@@ -324,74 +348,383 @@ namespace IRaCIS.Core.SCP.Service
long fileSize = 0;
try
{
using (MemoryStream ms = new MemoryStream())
// 直接拿 Dataset(已经完整)
var dataset = request.Dataset;
#region
var anonymizeList = await _fusionCache.GetOrSetAsync(CacheKeys.SystemAnonymization, _ => CacheHelper.GetSystemAnonymizationListAsync(_systemAnonymizationRepository), TimeSpan.FromDays(7));
var trialSiteInfo = await _fusionCache.GetOrSetAsync(CacheKeys.TrialSiteInfo(_trialSiteId), _ => CacheHelper.GetTrialSiteInfo(_trialSiteId, _trialSiteRepository), TimeSpan.FromMinutes(2));
var fixedFiledList = anonymizeList.Where(t => t.IsFixed).ToList();
var ircFiledList = anonymizeList.Where(t => t.IsFixed == false).ToList();
foreach (var item in fixedFiledList)
{
await request.File.SaveAsync(ms);
#region 1便
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
// 回到开头,读取 dicom
ms.Position = 0;
var dicomFile = DicomFile.Open(ms);
dataset.AddOrUpdate(dicomTag, item.ReplaceValue);
}
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
var syntax = pixelData.Syntax;
foreach (var item in ircFiledList)
{
// 每个 fragment 固定大小 (64KB 示例,可以自己调整)
int fragmentSize = 20 * 1024;
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
if (syntax.IsEncapsulated)
if (dicomTag == DicomTag.ClinicalTrialProtocolID)
{
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
dataset.AddOrUpdate(DicomTag.ClinicalTrialProtocolID, trialSiteInfo.TrialCode);
for (int n = 0; n < pixelData.NumberOfFrames; n++)
}
if (dicomTag == DicomTag.ClinicalTrialSiteID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialSiteID, trialSiteInfo.TrialSiteCode);
}
if (dicomTag == DicomTag.ClinicalTrialSubjectID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialSubjectID, "");
}
if (dicomTag == DicomTag.ClinicalTrialTimePointID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialTimePointID, "");
}
if (dicomTag == DicomTag.PatientID)
{
var pid = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
dataset.AddOrUpdate(DicomTag.PatientID, trialSiteInfo.TrialCode + "-" + pid);
}
}
#endregion
// 构造 DicomFile(不用 Open
var dicomFile = new DicomFile(dataset);
#region 1便
var numberOfFrames = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
//多帧处理逻辑
if (numberOfFrames > 1)
{
//一定要有像素数据才处理
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
if (pixelData != null)
{
try
{
var frameData = pixelData.GetFrame(n); // 获取完整一帧
var data = frameData.Data;
int offset = 0;
while (offset < data.Length)
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 开始处理多帧instanceId:{instanceId}");
var syntax = pixelData.Syntax;
// 每个 fragment 固定大小 (64KB 示例,可以自己调整)
int fragmentSize = 20 * 1024;
var frag = dicomFile.Dataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData);
int fragmentCount = frag?.Fragments?.Count() ?? 0;
var originOffsetTable = frag?.OffsetTable; //有可能没有表,需要自己重建
var bot = new List<uint>();
uint botOffset = 0;
//需要拆成固定片段的
if (syntax.IsEncapsulated && fragmentCount == pixelData.NumberOfFrames && numberOfFrames > 1)
{
int size = Math.Min(fragmentSize, data.Length - offset);
var buffer = new byte[size];
Buffer.BlockCopy(data, offset, buffer, 0, size);
newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
offset += size;
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
#region test
//var newDicomFile = dicomFile.Clone();
//var newDataset = newDicomFile.Dataset;
//var dstPd = DicomPixelData.Create(newDataset, true);
//for (int i = 0; i < pixelData.NumberOfFrames; i++)
//{
// var frame = pixelData.GetFrame(i);
// dstPd.AddFrame(frame);
// var data = frame.Data;
// int offset = 0;
// while (offset < data.Length)
// {
// int size = Math.Min(fragmentSize, data.Length - offset);
// var buffer = new byte[size];
// Buffer.BlockCopy(data, offset, buffer, 0, size);
// newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
// offset += size;
// }
//}
//var newOffsetTable = newDataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData).OffsetTable;
//newFragments.OffsetTable.AddRange(newOffsetTable.ToArray());
#endregion
#region test fo-dicom auto bot
//var newDicomFile = dicomFile.Clone();
//var newDataset = newDicomFile.Dataset;
//var dstPd = DicomPixelData.Create(newDataset, true);
//for (int i = 0; i < pixelData.NumberOfFrames; i++)
//{
// var frame = pixelData.GetFrame(i);
// dstPd.AddFrame(frame);
//}
//var newOffsetTable = newDataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData).OffsetTable;
//Console.WriteLine(newOffsetTable.ToJsonStr());
#endregion
#region 使
for (int n = 0; n < pixelData.NumberOfFrames; n++)
{
var frameData = pixelData.GetFrame(n); // 获取完整一帧
var data = frameData.Data;
int offset = 0;
bot.Add(botOffset);
botOffset += (uint)data.Length;
while (offset < data.Length)
{
botOffset += 8;
int size = Math.Min(fragmentSize, data.Length - offset);
var buffer = new byte[size];
Buffer.BlockCopy(data, offset, buffer, 0, size);
newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
offset += size;
}
}
//保留原始偏移表
if (originOffsetTable.Count == pixelData.NumberOfFrames)
{
newFragments.OffsetTable.AddRange(originOffsetTable.ToArray());
}
else
{
newFragments.OffsetTable.AddRange(bot.ToArray());
//Console.WriteLine(bot.ToJsonStr());
}
#endregion
dicomFile.Dataset.AddOrUpdate(newFragments);
}
//传递过来的就是拆分的,但是是没有偏移表的,我需要自己创建偏移表,不然生成缩略图失败
else if (syntax.IsEncapsulated && fragmentCount > pixelData.NumberOfFrames && originOffsetTable.Count == 0)
{
uint offset = 0;
bot.Add(offset);
var fistSize = frag.Fragments.FirstOrDefault()?.Size ?? 0;
//和上一个大小不一样
var isDiffrentBefore = false;
uint count = 0;
// 假设你知道每帧对应的 fragment 数量
foreach (var frameFragments in frag.Fragments)
{
count++;
if (frameFragments.Size == fistSize)
{
isDiffrentBefore = false;
// 累加这一帧所有 fragment 的大小
offset += (uint)frameFragments.Size;
continue;
}
else
{
offset += (uint)frameFragments.Size;
isDiffrentBefore = true;
}
if (isDiffrentBefore)
{
//每个Fragment 也占用字节
offset += 8 * count;
bot.Add(offset);
count = 0;
}
}
bot.RemoveAt(bot.Count - 1);
// 设置到新的 PixelData
frag.OffsetTable.AddRange(bot.ToArray());
}
}
catch (Exception mutiEx)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 处理多帧失败,上传原始文件:{mutiEx.ToString()}");
}
// 替换原 PixelData
dicomFile.Dataset.AddOrUpdate(newFragments);
// 重新保存 dicom 到流
ms.SetLength(0);
dicomFile.Save(ms);
}
ms.Position = 0;
}
#endregion
#endregion
#region
//// --- 保存到本地文件测试 ---
//var localPath = @"D:\TestDicom.dcm";
//using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write))
//{
// ms.CopyTo(fs);
//}
//return new DicomCStoreResponse(request, DicomStatus.Success);
#endregion
// 直接写入内存
await using var ms = new MemoryStream();
await dicomFile.SaveAsync(ms);
ms.Position = 0;
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
fileSize = ms.Length;
#region
//// --- 保存到本地文件测试 ---
//var localPath = @"D:\TestDicom.dcm";
//using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write))
//{
// ms.CopyTo(fs);
//}
//return new DicomCStoreResponse(request, DicomStatus.Success);
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
#endregion
using (await @lock.AcquireAsync())
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(dicomFile, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE, fileSize);
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
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(dicomFile.Dataset);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(memoryStream, new JpegEncoder());
// 上传缩略图到 OSS
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, $"{seriesId.ToString()}_{instanceId.ToString()}.preview.jpg", false);
series.ImageResizePath = seriesPath;
}
}
await _seriesRepository.SaveChangesAsync();
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
find.SuccessImageCount++;
if (!find.PatientNameList.Any(t => t == patientIdStr) && patientIdStr.IsNotNullOrEmpty())
{
find.PatientNameList.Add(patientIdStr);
}
//首次 (默认是Guid 空,数据库归档出了Id)
if (find.SCPStudyId != scpStudyId)
{
find.SCPStudyId = scpStudyId;
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
find.FailedImageCount++;
}
}
fileSize = ms.Length;
}
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
@@ -402,87 +735,6 @@ namespace IRaCIS.Core.SCP.Service
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.File, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
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();
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
find.SuccessImageCount++;
if (!find.PatientNameList.Any(t => t == patientIdStr) && patientIdStr.IsNotNullOrEmpty())
{
find.PatientNameList.Add(patientIdStr);
}
//首次 (默认是Guid 空,数据库归档出了Id)
if (find.SCPStudyId != scpStudyId)
{
find.SCPStudyId = scpStudyId;
}
}
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
find.FailedImageCount++;
}
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
return new DicomCStoreResponse(request, DicomStatus.Success);
}
+101
View File
@@ -0,0 +1,101 @@
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.EntityFrameworkCore;
namespace IRaCIS.Core.SCP.Service;
public static class CacheKeys
{
//项目缓存
public static string Trial(string trialIdStr) => $"TrialId:{trialIdStr}";
//检查编号递增锁
public static string TrialStudyMaxCode(Guid trialId) => $"TrialStudyMaxCode:{trialId}";
public static string TrialStudyUidUploading(Guid trialId, string studyUid) => $"TrialStudyUid:{trialId}_{studyUid}";
//CRC上传影像提交锁key
public static string TrialStudyUidDBLock(Guid trialId, string studyUid) => $"TrialStudyUidDBLock:{trialId}_{studyUid}";
public static string TrialTaskStudyUidUploading(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialStudyUid:{trialId}_{visiTaskId}_{studyUid}";
//影像后处理上传提交锁key
public static string TrialTaskStudyUidDBLock(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialTaskStudyUidDBLock:{trialId}_{visiTaskId}_{studyUid}";
//系统匿名化
public static string SystemAnonymization => $"SystemAnonymization";
//前端国际化
public static string FrontInternational => $"FrontInternationalList";
//登录挤账号
public static string UserToken(Guid userId) => $"UserToken:{userId}";
//超时没请求接口自动退出
public static string UserAutoLoginOut(Guid userId) => $"UserAutoLoginOut:{userId}";
public static string UserDisable(Guid userId) => $"UserDisable:{userId}";
public static string UserRoleDisable(Guid userRoleId) => $"UserRoleDisable:{userRoleId}";
/// <summary>
/// 用户登录错误 限制登录
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public static string UserLoginError(string userName) => $"login-failures:{userName}";
/// <summary>
/// 跳过阅片
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string SkipReadingCacheKey(Guid userId) => $"{userId}SkipReadingCache";
/// <summary>
/// 开始阅片时间
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string StartReadingTimeKey(Guid userId) => $"{userId}StartReadingTime";
/// <summary>
/// 开始休息时间
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
//每个用户 每个浏览器独立时间
public static string UserMFAVerifyPass(Guid userId, string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
public static string TrialSiteInfo(Guid trialSiteId) => $"{trialSiteId}TrialSiteInfo";
}
public static class CacheHelper
{
public static async Task<string?> GetTrialStatusAsync(Guid trialId, IRepository<Trial> _trialRepository)
{
var statusStr = await _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => t.TrialStatusStr).FirstOrDefaultAsync();
return statusStr;
}
public class TrialSiteInfoDTO
{
public string TrialSiteCode { get; set; }
public string TrialCode { get; set; }
}
public static async Task<TrialSiteInfoDTO> GetTrialSiteInfo(Guid trialSiteId, IRepository<TrialSite> _trialSiteRepository)
{
var obj = await _trialSiteRepository.Where(t => t.Id == trialSiteId, ignoreQueryFilters: true).Select(t => new TrialSiteInfoDTO { TrialCode= t.Trial.TrialCode, TrialSiteCode= t.TrialSiteCode }).FirstOrDefaultAsync();
return obj??new TrialSiteInfoDTO();
}
public static async Task<List<SystemAnonymization>> GetSystemAnonymizationListAsync(IRepository<SystemAnonymization> _systemAnonymizationRepository)
{
var list = await _systemAnonymizationRepository.Where(t => t.IsEnable).ToListAsync();
return list;
}
}
+34 -49
View File
@@ -1,49 +1,32 @@
using IRaCIS.Core.Domain.Share;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Infrastructure;
using Medallion.Threading;
using FellowOakDicom;
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.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using System.Runtime.Intrinsics.X86;
using Serilog.Sinks.File;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.SCP.Service;
using MassTransit;
using Medallion.Threading;
using Microsoft.AspNetCore.Hosting;
using Serilog.Sinks.File;
using System.Data;
using System.Runtime.Intrinsics.X86;
using System.Text;
using ZiggyCreatures.Caching.Fusion;
using static IRaCIS.Core.SCP.Service.CacheHelper;
namespace IRaCIS.Core.SCP.Service
{
public class DicomArchiveService : BaseService, IDicomArchiveService
public class DicomArchiveService(IRepository<SCPPatient> _patientRepository,
IRepository<SCPStudy> _studyRepository,
IRepository<SCPSeries> _seriesRepository,
IRepository<SCPInstance> _instanceRepository
) : 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;
}
@@ -53,18 +36,19 @@ namespace IRaCIS.Core.SCP.Service
/// <param name="dataset"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
public async Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE, long fileSize)
{
var dataset = dicomFile.Dataset;
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);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString());
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
@@ -77,9 +61,9 @@ namespace IRaCIS.Core.SCP.Service
//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 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);
@@ -93,8 +77,8 @@ namespace IRaCIS.Core.SCP.Service
findPatient = new SCPPatient()
{
Id = NewId.NextSequentialGuid(),
TrialId=trialId,
TrialSiteId=trialSiteId,
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),
@@ -122,7 +106,7 @@ namespace IRaCIS.Core.SCP.Service
DateTime birthDate;
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate, out birthDate))
{
var patientAge = studyTime.Value.Year - birthDate.Year;
// 如果生日还未到,年龄减去一岁
@@ -245,6 +229,7 @@ namespace IRaCIS.Core.SCP.Service
findStudy.DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty);
findStudy.CalledAE = calledAE;
findStudy.CallingAE = callingAE;
findStudy.PatientIdStr = patientIdStr;
findStudy.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
findStudy.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
findStudy.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
@@ -289,7 +274,7 @@ namespace IRaCIS.Core.SCP.Service
RadiopharmaceuticalInformationSequence = dataset.GetSingleValueOrDefault(DicomTag.RadiopharmaceuticalInformationSequence, string.Empty),
AcquisitionDate = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionDate, string.Empty),
InstanceCount = 0
};
@@ -334,7 +319,7 @@ namespace IRaCIS.Core.SCP.Service
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1),
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
@@ -358,7 +343,7 @@ namespace IRaCIS.Core.SCP.Service
Path = fileRelativePath,
FileSize= fileSize,
FileSize = fileSize,
};
@@ -398,7 +383,7 @@ namespace IRaCIS.Core.SCP.Service
}
else
{
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath, FileSize = fileSize });
}
await _studyRepository.SaveChangesAsync();
@@ -279,7 +279,7 @@ namespace IRaCIS.Api.Controllers
var isExpire = _tokenService.IsTokenExpired(token);
if (!await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) || isExpire)
if ( Guid.TryParse(userId,out _) == false || isExpire || !await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) )
{
decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(I18n.T("UserRedirect_InitializationLinkExpire"))} ";
}
@@ -15,12 +15,13 @@ using Hangfire.Storage;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using MassTransit.Mediator;
using Microsoft.Extensions.Configuration;
namespace IRaCIS.Core.API.HostService;
public class HangfireHostService(IRecurringMessageScheduler _recurringMessageScheduler,
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IConfiguration _config,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IMediator _mediator,
ILogger<HangfireHostService> _logger) : IHostedService
@@ -56,6 +57,8 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
}
}
string defaultCulture = _config.GetValue<string>("SystemEmailSendConfig:CronEmailDefaultCulture");
// 项目手动选择,周期性邮件
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
@@ -68,11 +71,11 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
var trialId = task.TrialId;
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron);
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron, defaultCulture);
}
// 系统邮件定时任务
// 系统邮件定时任务 要设置默认语言
var systemTaskInfoList = await _emailNoticeConfigrepository.Where(t => t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, t.EmailCron, t.BusinessScenarioEnum, })
.ToListAsync();
@@ -82,7 +85,7 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
//利用主键作为任务Id
var jobId = $"{task.Id}_({task.BusinessScenarioEnum})";
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron, defaultCulture);
}
+11
View File
@@ -22,6 +22,7 @@ using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -29,6 +30,10 @@ using System.Runtime.InteropServices;
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
Console.WriteLine("Startup Culture: " + CultureInfo.CurrentCulture.Name);
Console.WriteLine("Startup UI Culture: " + CultureInfo.CurrentUICulture.Name);
#region
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
var config = new ConfigurationBuilder()
@@ -96,6 +101,12 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
builder.Services.AddControllers(options =>
{
// 关键配置:禁用不可空引用类型的自动 Required 验证
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
// 插到最前,抢在默认绑定器之前
//options.ModelBinderProviders.Insert(0, new SpecificNullableBinderProvider());
options.Filters.Add<ModelActionFilter>();
options.Filters.Add<ProjectExceptionFilter>();
options.Filters.Add<UnitOfWorkFilter>();
@@ -4,7 +4,7 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:3305",
"applicationUrl": "http://0.0.0.0:3305",
"sslPort": 0
}
},
@@ -23,7 +23,7 @@
"ASPNETCORE_ENVIRONMENT": "Test_IRC",
"ASPNETCORE_OpenSwagger": "true"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.Test_IRC_PGSQL": {
"commandName": "Project",
@@ -31,7 +31,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Test_IRC_PGSQL"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.Event_IRC": {
"commandName": "Project",
@@ -39,7 +39,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Event_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"Docker": {
"commandName": "Docker",
@@ -53,7 +53,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Uat_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.Prod_IRC": {
"commandName": "Project",
@@ -61,7 +61,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Prod_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.US_Uat_IRC": {
"commandName": "Project",
@@ -69,7 +69,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Uat_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.US_Prod_IRC": {
"commandName": "Project",
@@ -77,7 +77,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Prod_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
}
}
@@ -39,6 +39,8 @@ namespace IRaCIS.Core.API._PipelineExtensions.Serilog
var requestBodyPayload = await ReadRequestBody(context.Request);
context.Items["RequestBody"] = requestBodyPayload;
using (LogContext.PushProperty("RequestBody", requestBodyPayload))
{
//await _next.Invoke()
@@ -45,7 +45,7 @@ namespace IRaCIS.Core.API
}
else
{
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure()/*.CommandTimeout(60)*/);
}
@@ -71,29 +71,27 @@ namespace IRaCIS.Core.API
DateTime dateTime;
if (reader.ValueType == typeof(DateTime) || reader.ValueType == typeof(DateTime?))
// 2. 检查目标类型是否可空
bool isNullable = objectType == typeof(DateTime?);
var canConvert = DateTime.TryParse(reader.Value?.ToString(), out dateTime);
if (isNullable == false && canConvert == false)
{
DateTime? nullableDateTime = reader.Value as DateTime?;
if (nullableDateTime != null && nullableDateTime.HasValue)
{
dateTime = nullableDateTime.Value;
}
else
{
return null;
}
throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}");
}
else
{
if (DateTime.TryParse((string)reader.Value, out dateTime) == false)
if (canConvert == false)
{
return null;
}
}
////如果前端传递带时区的,那么转换会报错,需要DateTimeKind.Unspecified
//dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
// 将客户端时间转换为服务器时区的时间
var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local);
@@ -113,7 +111,10 @@ namespace IRaCIS.Core.API
//第一个参数默认使用系统本地时区 也就是应用服务器的时区
DateTime clientZoneTime = TimeZoneInfo.ConvertTime(nullableDateTime.Value, _clientTimeZone);
//writer.WriteValue(clientZoneTime);
//// 最简单的方式:创建 DateTimeOffset
//DateTimeOffset dateTimeOffset = new DateTimeOffset(clientZoneTime, _clientTimeZone.GetUtcOffset(clientZoneTime));
//writer.WriteValue(dateTimeOffset.ToString("yyyy-MM-dd HH:mm:sszzz"));
writer.WriteValue(clientZoneTime.ToString(_dateFormat));
}
@@ -3,8 +3,10 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
namespace IRaCIS.Core.API
{
@@ -51,34 +53,90 @@ namespace IRaCIS.Core.API
}
public class NullToEmptyStringValueProvider : IValueProvider
{
PropertyInfo _MemberInfo;
PropertyInfo _memberInfo;
private readonly bool _isNullable;
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
{
_MemberInfo = memberInfo;
_memberInfo = memberInfo;
}
// DTO → JSON 返回前端的时候 处理null 变为"" 方便前端判断
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;
var result = _memberInfo.GetValue(target);
// 检查类型是否为string或string?
if (_memberInfo.PropertyType == typeof(string) && result == null)
{
#region string string? null ""
//// 如果是string?类型,返回null 可以做到,但是得反射,因为 string string? 是编译层区分的
//var isNullable1 = _memberInfo.CustomAttributes
// .Any(a => a.AttributeType.Name == "NullableAttribute");
////var isNullable2 = _memberInfo.CustomAttributes
//// .Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
//if (isNullable1)
//{
// return result;
//}
#endregion
// 如果是string类型,返回空字符串
return string.Empty;
}
return result;
}
//影响 模型绑定时接收前端的值,同时影响 正常 JSON 序列化
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()*/);
_memberInfo.SetValue(target, value);
#region string null
//if (_memberInfo.PropertyType == typeof(string))
//{
// _memberInfo.SetValue(target, value == null ? string.Empty : value);
//}
//else
//{
// _memberInfo.SetValue(target, value);
//}
#endregion
#region string string?
//////接收模型的时候 定义的明明是string 但是上面也有该属性,判断不准的 比如阅片跟踪列表查询
//var isNullable1 = _memberInfo.CustomAttributes.Any(a => a.AttributeType.Name == "NullableAttribute");
////不影响 string? 传递null 变为""
//if (_memberInfo.PropertyType == typeof(string) && isNullable1 == false)
//{
// //如果不处理 前段传递null string不会接收,说前段没传递会验证报错
// _memberInfo.SetValue(target, value == null ? string.Empty : value);
//}
//else
//{
// _memberInfo.SetValue(target, value);
//}
#endregion
}
else
{
_MemberInfo.SetValue(target, value);
}
}
}
@@ -35,7 +35,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,retainedFileCountLimit:60);
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,retainedFileCountLimit:365);
#region
+5 -3
View File
@@ -35,7 +35,8 @@
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -56,13 +57,14 @@
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "uat@extimaging.com",
"FromName": "UAT_IRC",
"AuthorizationCode": "SHzyyl2021",
+16 -4
View File
@@ -12,6 +12,12 @@
//"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"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"AliyunOSS": {
@@ -36,7 +42,8 @@
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -55,20 +62,24 @@
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "irc@extimaging.com",
"FromName": "IRC Imaging System",
"AuthorizationCode": "ExtImg@2022",
"SiteUrl": "http://irc.extimaging.com/login",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
"SystemShortName": "IRC",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
@@ -76,6 +87,7 @@
"IsEnv_US": false,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
+18 -4
View File
@@ -18,6 +18,12 @@
// Hangfire 定时任务数据库链接字符串
"Hangfire": "Server=106.14.89.110,1435;Database=Test_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
},
// 对象存储服务配置
"ObjectStoreService": {
// 使用的对象存储服务类型
@@ -88,6 +94,8 @@
},
// 系统配置
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
// 打开用户复杂密码
"OpenUserComplexPassword": false,
// 是否在开始工作前强制签署电子知情同意书
@@ -110,7 +118,7 @@
"IsNeedChangePassWord": true,
// 密码有效期(天),到期后必须修改
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
// 模板类型 1 ElevateLiLi 2 Extensive(展影)
"TemplateType": 2,
// 是否打开项目关联删除
"OpenTrialRelationDelete": true,
@@ -127,6 +135,8 @@
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
// 发件人邮箱地址
"FromEmail": "test@extimaging.com",
// 发件人显示名称
@@ -135,12 +145,15 @@
"AuthorizationCode": "SHzyyl2021",
// 系统对外访问地址
"SiteUrl": "http://irc.test.extimaging.com/login",
// 系统简称
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
// 系统简称一致性核查使用
"SystemShortName": "IRC",
// 组织英文名称
"OrganizationName": "Extlmaging",
"OrganizationName": "ExtImaging",
// 组织中文名称
"OrganizationNameCN": "Extlmaging",
"OrganizationNameCN": "ExtImaging",
// 公司英文全称
"CompanyName": "Extensive Imaging",
// 公司中文全称
@@ -155,6 +168,7 @@
"IsOpenErrorNoticeEmail": false,
// 邮箱格式校验正则表达式
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN",
// 接收系统异常报警的邮箱列表
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
@@ -47,7 +47,8 @@
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false,
@@ -76,13 +77,14 @@
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "test@extimaging.com",
"FromName": "Test_IRC",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.test.extimaging.com/login",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
+15 -4
View File
@@ -12,7 +12,12 @@
//"RemoteNew": "Server=44.210.231.169,1435;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
//"Hangfire": "Server=44.210.231.169,1435;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AWS",
"MinIO": {
@@ -38,7 +43,8 @@
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -58,8 +64,8 @@
"TemplateType": 1,
"OpenTrialRelationDelete": false,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
//MFA免验证
"UserMFAVerifyMinutes": 1440
},
@@ -67,10 +73,14 @@
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
@@ -82,6 +92,7 @@
"IsEnv_US": true,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "en-US",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
+8 -2
View File
@@ -44,7 +44,8 @@
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -68,17 +69,22 @@
"TemplateType": 1,
"OpenLoginMFA": true,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
+13 -2
View File
@@ -13,6 +13,12 @@
"RemoteNew": "Server=3.226.182.187,1435;Database=US_Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=3.226.182.187,1435;Database=US_Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
},
"ObjectStoreService": {
@@ -48,7 +54,8 @@
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -67,17 +74,20 @@
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 1,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
@@ -89,6 +99,7 @@
"IsEnv_US": true,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "en-US",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
+15 -4
View File
@@ -10,6 +10,12 @@
"RemoteNew": "Server=101.132.253.119,1435;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.253.119,1435;Database=Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
@@ -54,7 +60,8 @@
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -74,21 +81,24 @@
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "uat@extimaging.com",
"FromName": "Uat IRC Imaging System",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.uat.extimaging.com/login",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
"SystemShortName": "IRC",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
@@ -96,6 +106,7 @@
"IsEnv_US": false,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
+1 -1
View File
@@ -3,7 +3,7 @@
"SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021",
"Issuer": "Extimaging",
"Audience": "EICS",
"TokenExpireMinute": "10080"//7天
"TokenExpireMinute": "10080" //7天
},
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
@@ -4,7 +4,6 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>%(DocumentTitle)</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
@@ -505,8 +505,8 @@ var abp = abp || {};
//Inputs
createInput(modalUxContent, 'tenancyName', 'Tenancy Name (Leave empty for Host)');
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'cyldev');
createInput(modalUxContent, 'password', 'Password', 'password', '123456');
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'user1wj');
createInput(modalUxContent, 'password', 'Password', 'password', '1');
createInput(modalUxContent, 'pwdMd5', 'PwdMd5', 'text', '');
createSelect(modalUxContent, 'roleSelect', 'role', [])
@@ -1,14 +1,22 @@
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Application.Helper.OtherTool;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
using System;
using System.Reflection;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace IRaCIS.Core.Application.Filter;
public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttribute, IActionFilter
public class ModelActionFilter(IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : ActionFilterAttribute, IActionFilter
{
@@ -23,8 +31,55 @@ public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttrib
.Select(e => e.ErrorMessage)
.ToArray();
var request = context.HttpContext.Request;
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:VueNoticeUserList").Get<string[]>();
var requestBody = context.HttpContext.Items["RequestBody"] as string;
// 🔔 异步告警(不要阻塞请求)
_ = WeComNotifier.SendAlertAsync(
webhook: webhook,
alert: new WeComAlert
{
Env = baseUrl,
UserName = _userInfo.UserName.IsNotNullOrEmpty()? $"{_userInfo.UserName}({_userInfo.UserTypeShortName})": "匿名",
Api = $"{request.Method} {request.Path}",
Message = $"前端传递参数,后端模型验证失败\n 具体信息:{JsonConvert.SerializeObject(validationErrors)}",
JsonData = requestBody,
AtUsers = userList ?? []
}
);
}
}
catch (Exception ex)
{
Log.Logger.Error($"模型验证过滤器里发送企业微信出现错误:{ex.Message}");
}
//---提供给接口的参数无效。
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject(validationErrors)));
}
}
}
@@ -1,13 +1,16 @@
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Application.Helper.OtherTool;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.Application.Filter;
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer) : Attribute, IExceptionFilter
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : Attribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
@@ -46,7 +49,44 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
else
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message)
: (exception.InnerException?.Message )), ApiResponseCodeEnum.ProgramException));
: (exception.Message + "Inner ExceptionMsg:" + exception.InnerException?.Message)), ApiResponseCodeEnum.ProgramException));
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get<string[]>();
var requestBody = context.HttpContext.Items["RequestBody"] as string;
var alert = new WeComAlert
{
Env = baseUrl,
UserName = $"{_userInfo.UserName}({_userInfo.UserTypeShortName})",
Api = context.HttpContext.Request.Path,
Message = exception.Message,
JsonData = requestBody,
Stack = exception.StackTrace,
AtUsers = userList ?? []
};
_ = WeComNotifier.SendAlertAsync(webhook, alert);
}
}
catch (Exception ex)
{
_logger.LogError($"异常过滤器里发送企业微信出现错误:{ex.Message}");
}
}
@@ -58,6 +98,7 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
_logger.LogError(errorInfo);
//_logger.LogError(exception, exception.Message);
}
else
@@ -7,6 +7,7 @@ namespace IRaCIS.Core.Domain.Share;
[Description("多环境 配置环境实体")]
public class ServiceVerifyConfigOption
{
public bool QCRiskControl { get; set; }
public bool OpenUserComplexPassword { get; set; }
public bool OpenSignDocumentBeforeWork { get; set; }
@@ -49,6 +50,8 @@ public class SystemEmailSendConfig
public string Host { get; set; } = string.Empty;
public string Imap { get; set; } = string.Empty;
public int ImapPort { get; set; }
public string FromEmail { get; set; } = string.Empty;
public string FromName { get; set; } = string.Empty;
@@ -57,6 +60,10 @@ public class SystemEmailSendConfig
public string SiteUrl { get; set; } = string.Empty;
public string PlatformName { get; set; } = string.Empty;
public string PlatformNameCN { get; set; } = string.Empty;
public string SystemShortName { get; set; } = string.Empty;
public string OrganizationName { get; set; } = string.Empty;
@@ -64,6 +64,8 @@ public static class CacheKeys
//每个用户 每个浏览器独立时间
public static string UserMFAVerifyPass(Guid userId,string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
public static string UserMFATag(Guid userId) => $"UserMFAVerifyPass:{userId}";
}
public static class CacheHelper
@@ -1,4 +1,5 @@
using FellowOakDicom;
using DocumentFormat.OpenXml.Office.CustomUI;
using FellowOakDicom;
using FellowOakDicom.Media;
using System;
using System.Collections.Generic;
@@ -13,6 +14,9 @@ namespace IRaCIS.Core.Application.Helper
public class StudyDIRInfo
{
public Guid SubjectId { get; set; }
public bool IsTaskStudy { get; set; }
public Guid SubjectVisitId { get; set; }
// Study
public Guid DicomStudyId { get; set; }
@@ -54,6 +58,7 @@ namespace IRaCIS.Core.Application.Helper
var mappings = new List<string>();
int index = 1;
var studyUid=list.FirstOrDefault()?.StudyInstanceUid;
var dicomDir = new DicomDirectory();
@@ -97,11 +102,11 @@ namespace IRaCIS.Core.Application.Helper
var dicomFile = new DicomFile(dataset);
// 文件名递增格式:IM_00001, IM_00002, ...
string filename = $@"IMAGE/IM_{index:D5}"; // :D5 表示补足5位
string filename = $@"IMAGE\IM_{index:D5}"; // :D5 表示补足5位
mappings.Add($"{filename} => {item.InstanceId}");
dic.Add(item.InstanceId.ToString(), Path.GetFileName(filename));
dic.Add(item.InstanceId.ToString(), filename.TrimEnd('/', '\\').Split('/', '\\').Last());
dicomDir.AddFile(dicomFile, filename);
@@ -125,7 +130,9 @@ namespace IRaCIS.Core.Application.Helper
// 重置流位置
memoryStream.Position = 0;
await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", false);
var relativePath= await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", true);
dic.Add($"{studyUid}_DICOMDIR" , relativePath.Split('/').Last());
}
//清理临时文件
@@ -2,6 +2,7 @@
using MailKit;
using MailKit.Security;
using MimeKit;
using Org.BouncyCastle.Tls;
namespace IRaCIS.Core.Application.Helper;
@@ -9,12 +10,15 @@ namespace IRaCIS.Core.Application.Helper;
public static class SendEmailHelper
{
public static async Task SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
string result = string.Empty;
result = messageToSend.MessageId;
//没有收件人 那么不发送
if (messageToSend.To.Count == 0)
{
return;
return string.Empty;
}
try
@@ -52,23 +56,78 @@ public static class SendEmailHelper
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
}
return result;
}
/// <summary>
/// 设置发送邮箱
/// </summary>
/// <param name="messageToSend"></param>
/// <param name="senderName"></param>
public static void ChangeEmailSenderName(MimeMessage messageToSend, string senderName)
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, Trial trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
// 项目的需要重设 发件地址与邮件地址
var fromAddress = messageToSend.From.Mailboxes.FirstOrDefault();
if (fromAddress != null)
{
messageToSend.From.Clear();
messageToSend.From.Add(new MailboxAddress(senderName, fromAddress.Address));
messageToSend.From.Add(new MailboxAddress(trial.EmailFromName, trial.EmailFromEmail));
}
string result = string.Empty;
result = messageToSend.MessageId;
//没有收件人 那么不发送
if (messageToSend.To.Count == 0)
{
return string.Empty;
}
try
{
using (var smtp = new MailKit.Net.Smtp.SmtpClient())
{
if (messageSentSuccess != null)
{
smtp.MessageSent += messageSentSuccess;
}
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
//await smtp.ConnectAsync("smtp.qq.com", 465, SecureSocketOptions.SslOnConnect);
//await smtp.AuthenticateAsync("zhou941003@qq.com", "sqfhlpfdvnexbcab");
//await smtp.ConnectAsync(_systemEmailConfig.Host, _systemEmailConfig.Port, SecureSocketOptions.Auto);
//await smtp.AuthenticateAsync(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
await smtp.ConnectAsync(trial.EmailSMTPServerAddress, trial.EmailSMTPServerPort, SecureSocketOptions.Auto);
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
await smtp.SendAsync(messageToSend);
await smtp.DisconnectAsync(true);
}
}
catch (Exception ex)
{
//---邮件发送失败,您进行的操作未能成功,请检查邮箱或联系维护人员
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
}
return result;
}
public static async Task<bool> TestEmailConfigAsync(SystemEmailSendConfig _systemEmailConfig)
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
@@ -88,7 +147,7 @@ public static class SendEmailHelper
return true;
}
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig,Trial? trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
var messageToSend = new MimeMessage();
@@ -158,7 +217,7 @@ public static class SendEmailHelper
await smtp.ConnectAsync(sMTPEmailConfig.Host, sMTPEmailConfig.Port, SecureSocketOptions.Auto);
await smtp.AuthenticateAsync(sMTPEmailConfig.UserName, sMTPEmailConfig.AuthorizationCode);
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
await smtp.SendAsync(messageToSend);
@@ -60,4 +60,16 @@ namespace IRaCIS.Core.Application.Helper
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class DateTimeTranaslateAttribute : Attribute
{
public string Formart { get; set; }
public DateTimeTranaslateAttribute(string formart)
{
Formart = formart;
}
}
}
@@ -1,19 +1,24 @@
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Wordprocessing;
using FellowOakDicom.Imaging.LUT;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore.Migrations;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using MiniExcelLibs;
using MiniExcelLibs.OpenXml;
using Newtonsoft.Json;
using NPOI.HSSF.UserModel;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using SharpCompress.Common;
using System.Collections;
using System.Globalization;
using Xceed.Document.NET;
@@ -681,13 +686,61 @@ public static class ExcelExportHelper
if (itemDicName.IsNotNullOrEmpty())
{
var translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
var optionTypeEnumStr = iteObjDic.ContainsKey("OptionTypeEnum") ? iteObjDic["OptionTypeEnum"]?.ToString() : "0";
var translatedItemData = "";
//多选
if (optionTypeEnumStr == "1")
{
int[] enumValues = new int[0];
// 1. 反序列化 JSON 数组 (字符串枚举)
if (!itemValue.StartsWith("[") || !itemValue.EndsWith("]"))
{
enumValues = new int[1] { int.Parse(itemValue) };
}
else
{
enumValues = JsonConvert.DeserializeObject<int[]>(itemValue) ?? new int[0];
}
// 2. 翻译每一项并输出逗号拼接字符串
translatedItemData = string.Join(",",
enumValues.Select(code =>
dynamicTranslateDataList[itemDicName]
.FirstOrDefault(t =>
string.Equals(code.ToString(), t.Code, StringComparison.OrdinalIgnoreCase)
) is var r && r != null
? (isEn_US ? r.Value : r.ValueCN)
: string.Empty
));
}
else
{
translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
}
row.GetCell(writeIndex).SetCellValue(translatedItemData);
}
else
{
row.GetCell(writeIndex).SetCellValue(itemValue);
var unit = iteObjDic.ContainsKey("Unit") ? iteObjDic["Unit"]?.ToString() : null;
if (unit.IsNotNullOrEmpty() && unit == "9")
{
row.GetCell(writeIndex).SetCellValue(itemValue + "%");
}
else
{
row.GetCell(writeIndex).SetCellValue(itemValue);
}
}
@@ -818,17 +871,16 @@ public static class ExcelExportHelper
//模板路径
var tplPath = physicalPath;
#region sheet
// 打开模板文件
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
// 获取文件流
var templateStream = new MemoryStream();
templateFile.CopyTo(templateStream);
templateStream.Seek(0, SeekOrigin.Begin);
var workbook = new XSSFWorkbook(templateStream);
#region sheet
// 打开模板文件
var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
var workbook = new XSSFWorkbook(templateFileStream);
int sheetCount = workbook.NumberOfSheets;
@@ -933,14 +985,8 @@ public static class ExcelExportHelper
}
}
using (var memoryStream2 = new MemoryStream())
{
workbook.Write(memoryStream2, true);
memoryStream2.Seek(0, SeekOrigin.Begin);
templateStream = memoryStream2;
}
workbook.Write(templateStream, leaveOpen: true);
templateStream.Position = 0;
}
@@ -1047,6 +1093,63 @@ public static class ExcelExportHelper
//模板路径
var tplPath = physicalPath;
var templateStream = new MemoryStream();
#region npoi
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
if (isEn_US)
{
// 打开模板文件
using var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
using var workbook = new XSSFWorkbook(templateFileStream);
int sheetCount = workbook.NumberOfSheets;
int removeRowIndex = 1; // 要删除的行(0-based
for (int i = 0; i < sheetCount; i++)
{
var sheet = workbook.GetSheetAt(i);
// 2 删除行
var row = sheet.GetRow(removeRowIndex);
if (row != null)
{
sheet.RemoveRow(row);
}
// 3 上移后续行
if (removeRowIndex < sheet.LastRowNum)
{
sheet.ShiftRows(
removeRowIndex + 1,
sheet.LastRowNum,
-1,
true, // copyRowHeight
false // resetOriginalRowHeight
);
}
}
workbook.Write(templateStream, leaveOpen: true);
templateStream.Position = 0;
}
else
{
using (var fs = new FileStream(tplPath, FileMode.Open, FileAccess.Read))
{
fs.CopyTo(templateStream);
}
templateStream.Position = 0;
}
#endregion
var memoryStream = new MemoryStream();
@@ -1055,7 +1158,7 @@ public static class ExcelExportHelper
IgnoreTemplateParameterMissing = true,
};
await MiniExcel.SaveAsByTemplateAsync(memoryStream, tplPath, data, config);
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), data, config);
memoryStream.Seek(0, SeekOrigin.Begin);
@@ -2,6 +2,7 @@
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Domain.Share;
using MassTransit.Mediator;
using System.Globalization;
namespace IRaCIS.Core.Application.Helper
{
@@ -57,25 +58,29 @@ namespace IRaCIS.Core.Application.Helper
}
public static void AddOrUpdateTrialCronJob(string jobId, Guid trialId, EmailBusinessScenario businessScenario, string emailCron)
public static void AddOrUpdateTrialCronJob(string jobId, Guid trialId, EmailBusinessScenario businessScenario, string emailCron,string defaultCulture="")
{
if (defaultCulture == "")
{
defaultCulture = CultureInfo.CurrentCulture.Name;
}
switch (businessScenario)
{
case EmailBusinessScenario.QCTask:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId,CultureInfoName= defaultCulture }, default), emailCron);
break;
case EmailBusinessScenario.CRCToQCQuestion:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
break;
case EmailBusinessScenario.QCToCRCImageQuestion:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
break;
//加急阅片 10分钟
case EmailBusinessScenario.ExpeditedReading:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
break;
default:
break;
@@ -83,20 +88,25 @@ namespace IRaCIS.Core.Application.Helper
}
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron, string defaultCulture="")
{
if (defaultCulture == "")
{
defaultCulture = CultureInfo.CurrentCulture.Name;
}
switch (businessScenario)
{
case EmailBusinessScenario.GeneralTraining_ExpirationNotification:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { CultureInfoName = defaultCulture }, default), emailCron);
break;
case EmailBusinessScenario.TrialTraining_ExpirationNotification:
Console.WriteLine("更新项目到期job");
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { CultureInfoName = defaultCulture }, default), emailCron);
break;
default:
+594 -37
View File
@@ -16,6 +16,7 @@ using Minio;
using Minio.DataModel;
using Minio.DataModel.Args;
using Minio.Exceptions;
using System.IO;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Web;
@@ -142,6 +143,10 @@ public enum ObjectStoreUse
public interface IOSSService
{
public Task SetImmediateArchiveRule(string prefix, string ruleId = "immediate-archive", bool isDelete = false);
public Task RestoreFilesByPrefixAsync(string prefix, int restoreDays = 3, int batchSize = 100);
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true, bool randomFileName = false);
@@ -155,7 +160,7 @@ public interface IOSSService
public Task DeleteFromPrefix(string prefix, bool isCache = false);
public Task DeleteObjects(List<string> objectKeys);
public Task DeleteObjects(List<string> objectKeys, bool isCache = false);
List<string> GetRootFolderNames();
@@ -182,7 +187,518 @@ public class OSSService : IOSSService
}
/// <summary>
/// 将指定前缀下的所有现有文件立即转为目标存储类型
/// </summary>
/// <param name="prefix">要转换的文件前缀,如 "project-a/logs/"</param>
/// <param name="ruleId">规则ID,默认为"immediate-archive"</param>
/// <param name="isDelete">默认是添加/更新 </param>
public async Task SetImmediateArchiveRule(string prefix, string ruleId = "immediate-archive", bool isDelete = false)
{
BackBatchGetToken();
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
{
// 1. 先获取现有的所有生命周期规则(避免覆盖)
var existingRules = new List<Aliyun.OSS.LifecycleRule>();
try
{
var existingRuleList = _ossClient.GetBucketLifecycle(aliConfig.BucketName);
if (existingRuleList != null)
{
existingRules.AddRange(existingRuleList);
Console.WriteLine($"找到 {existingRules.Count} 条现有规则");
}
}
catch (OssException ex) when (ex.ErrorCode == "NoSuchLifecycle")
{
// 如果没有生命周期规则,继续创建新规则
Console.WriteLine("当前Bucket无生命周期规则,将创建新规则");
}
// 2. 创建立即生效的转换规则
ruleId = $"{ruleId}_{prefix}";
var immediateRule = new Aliyun.OSS.LifecycleRule
{
ID = ruleId,
Prefix = prefix,
Status = RuleStatus.Enabled,
Transitions = new Aliyun.OSS.LifecycleRule.LifeCycleTransition[]
{
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
{
LifeCycleExpiration =
{
Days = 1
},
StorageClass = StorageClass.IA
},
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
{
LifeCycleExpiration =
{
Days = 30 //最后一次修改时间
},
StorageClass = StorageClass.Archive
}
}
};
// 3. 移除同名的旧规则(如果存在)
existingRules.RemoveAll(r => r.ID == ruleId);
// 4. 添加新规则到规则列表
if (isDelete == false)
{
existingRules.Add(immediateRule);
}
var request = new SetBucketLifecycleRequest(aliConfig.BucketName)
{
LifecycleRules = existingRules
};
_ossClient.SetBucketLifecycle(request);
}
catch (OssException ex)
{
Log.Logger.Error($"❌ 设置失败 [错误码: {ex.ErrorCode}] 详细: {ex.Message}");
}
catch (Exception ex)
{
Log.Logger.Error($"❌ 发生未知错误: {ex.Message}");
}
}
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.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
// 1. 获取现有的生命周期配置(避免覆盖)
LifecycleConfiguration existingConfig = null;
var getRequest = new GetLifecycleConfigurationRequest { BucketName = awsConfig.BucketName };
var response = await amazonS3Client.GetLifecycleConfigurationAsync(getRequest);
existingConfig = response.Configuration;
Console.WriteLine($"找到 {existingConfig?.Rules?.Count ?? 0} 条现有规则");
// 2. 生成唯一的规则ID
ruleId = $"{ruleId}_{prefix.Replace('/', '_').Trim('_')}";
// 3. 创建新的生命周期规则
var immediateRule = new Amazon.S3.Model.LifecycleRule
{
Id = ruleId,
Filter = new LifecycleFilter
{
// 使用前缀筛选对象
LifecycleFilterPredicate = new LifecyclePrefixPredicate { Prefix = prefix }
},
Status = LifecycleRuleStatus.Enabled,
// 定义多个转换阶段
Transitions = new List<LifecycleTransition>
{
// 1天后转为低频访问 (Standard-IA)
//new LifecycleTransition
//{
// Days = 1, //Days' in Transition action must be greater than or equal to 30 for storageClass 'STANDARD_IA'"
// StorageClass = S3StorageClass.StandardInfrequentAccess // 对应S3 Standard-IA
//},
// 30天后转为归档 (Glacier Instant Retrieval)
new LifecycleTransition
{
Days = 30, //创建时间
StorageClass = S3StorageClass.GlacierInstantRetrieval // 对应归档(即时检索)
}
// 如果需要更深的归档,可以继续添加:
// new LifecycleTransition { Days = 90, StorageClass = S3StorageClass.GlacierFlexibleRetrieval },
// new LifecycleTransition { Days = 180, StorageClass = S3StorageClass.DeepArchive }
}
// 注意:S3的生命周期规则不支持设置“立即生效(Days=0)”。
// 如果要对存量文件立即生效,需要配合其他方法(如批量修改存储类型)。
};
// 4. 更新规则列表(移除同名旧规则,添加新规则)
var existingRules = existingConfig.Rules ?? new List<Amazon.S3.Model.LifecycleRule>();
existingRules.RemoveAll(r => r.Id == ruleId);
if (isDelete == false)
{
existingRules.Add(immediateRule);
}
// 5. 提交新的生命周期配置
var putRequest = new PutLifecycleConfigurationRequest
{
BucketName = awsConfig.BucketName,
Configuration = new LifecycleConfiguration { Rules = existingRules }
};
await amazonS3Client.PutLifecycleConfigurationAsync(putRequest);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
/// 解冻指定前缀下的所有归档/冷归档文件
/// </summary>
/// <param name="prefix">要解冻的文件前缀</param>
/// <param name="restoreDays">解冻后文件保持可读的天数(默认3天)</param>
/// <param name="restoreTier">解冻优先级(仅AWS有效)</param>
/// <param name="batchSize">批量处理大小(默认100</param>
public async Task RestoreFilesByPrefixAsync(string prefix, int restoreDays = 3, int batchSize = 100)
{
BackBatchGetToken();
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var client = new OssClient(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
AliyunOSSTempToken.AccessKeyId,
AliyunOSSTempToken.AccessKeySecret,
AliyunOSSTempToken.SecurityToken
);
var bucketName = aliConfig.BucketName;
int totalRestored = 0;
int totalSkipped = 0;
int totalFailed = 0;
try
{
Console.WriteLine($"开始解冻阿里云OSS文件,前缀: {prefix}");
var allObjects = new List<OssObjectSummary>();
// 1. 分页列举文件
string nextMarker = null;
ObjectListing result = null;
do
{
var listRequest = new Aliyun.OSS.ListObjectsRequest(bucketName)
{
Prefix = prefix,
Marker = nextMarker,
MaxKeys = batchSize
};
result = client.ListObjects(listRequest);
allObjects.AddRange(result.ObjectSummaries);
nextMarker = result.NextMarker;
} while (result.IsTruncated);
// 2️⃣ 并行解冻(控制并发)
Parallel.ForEach(
allObjects,
new ParallelOptions
{
MaxDegreeOfParallelism = 5 // ⭐ 推荐 5~10
},
obj =>
{
// 只处理归档
if (obj.StorageClass != StorageClass.Archive.ToString())
{
Interlocked.Increment(ref totalSkipped);
return;
}
try
{
var restoreRequest = new Aliyun.OSS.RestoreObjectRequest(bucketName, obj.Key)
{
Days = restoreDays
};
client.RestoreObject(restoreRequest);
Interlocked.Increment(ref totalRestored);
Console.WriteLine($"✅ 提交解冻: {obj.Key}");
}
catch (OssException ex) when (ex.ErrorCode == "RestoreAlreadyInProgress")
{
// 已在解冻中,算成功
Interlocked.Increment(ref totalSkipped);
Console.WriteLine($"⚠️ 已在解冻中: {obj.Key}");
}
catch (Exception ex)
{
Interlocked.Increment(ref totalFailed);
Console.WriteLine($"❌ 解冻失败: {obj.Key} - {ex.Message}");
}
}
);
// 3. 输出统计结果
Console.WriteLine("\n================ 解冻完成 ================");
Console.WriteLine($"总计处理: {totalRestored + totalSkipped + totalFailed} 个文件");
Console.WriteLine($"成功解冻: {totalRestored} 个");
Console.WriteLine($"跳过文件: {totalSkipped} 个 (非归档类型)");
Console.WriteLine($"解冻失败: {totalFailed} 个");
if (totalRestored > 0)
{
Console.WriteLine($"\n📋 解冻说明:");
Console.WriteLine($" • 解冻任务已提交,文件将在后台处理");
Console.WriteLine($" • 解冻完成后,文件将保持可读状态 {restoreDays} 天");
Console.WriteLine($" • 归档文件约需1分钟,冷归档需数小时");
}
}
catch (Exception ex)
{
Log.Logger.Error($"❌ 阿里云解冻操作失败: {ex.Message}");
throw;
}
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
var credentials = new SessionAWSCredentials(
AWSTempToken.AccessKeyId,
AWSTempToken.SecretAccessKey,
AWSTempToken.SessionToken
);
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region),
UseHttp = true,
};
using var client = new AmazonS3Client(credentials, clientConfig);
var bucketName = awsConfig.BucketName;
int totalRestored = 0;
int totalSkipped = 0;
int totalFailed = 0;
try
{
Console.WriteLine($"开始解冻AWS S3文件,前缀: {prefix}");
var allObjects = new List<S3Object>();
// 1. 分页列举文件
string continuationToken = null;
ListObjectsV2Response response = null;
do
{
var listRequest = new ListObjectsV2Request
{
BucketName = bucketName,
Prefix = prefix,
ContinuationToken = continuationToken,
MaxKeys = batchSize
};
response = await client.ListObjectsV2Async(listRequest);
allObjects.AddRange(response.S3Objects);
continuationToken = response.NextContinuationToken;
} while (response.IsTruncated == true);
// 2️⃣ 并行解冻(控制并发)
await Parallel.ForEachAsync(
allObjects,
new ParallelOptions
{
MaxDegreeOfParallelism = 5 // ⭐ 推荐 5~10
},
async (obj, ct) =>
{
// 只处理归档
if (obj.StorageClass != S3StorageClass.Glacier)
{
Interlocked.Increment(ref totalSkipped);
return;
}
try
{
var restoreRequest = new Amazon.S3.Model.RestoreObjectRequest
{
BucketName = bucketName,
Key = obj.Key,
Days = restoreDays,
};
await client.RestoreObjectAsync(restoreRequest);
Interlocked.Increment(ref totalRestored);
Console.WriteLine($"✅ 提交解冻: {obj.Key}");
}
catch (OssException ex) when (ex.ErrorCode == "RestoreAlreadyInProgress")
{
// 已在解冻中,算成功
Interlocked.Increment(ref totalSkipped);
Console.WriteLine($"⚠️ 已在解冻中: {obj.Key}");
}
catch (Exception ex)
{
Interlocked.Increment(ref totalFailed);
Console.WriteLine($"❌ 解冻失败: {obj.Key} - {ex.Message}");
}
}
);
// 3. 输出统计结果
Console.WriteLine("\n================ 解冻完成 ================");
Console.WriteLine($"总计处理: {totalRestored + totalSkipped + totalFailed} 个文件");
Console.WriteLine($"成功解冻: {totalRestored} 个");
Console.WriteLine($"跳过文件: {totalSkipped} 个 (非归档类型)");
Console.WriteLine($"解冻失败: {totalFailed} 个");
if (totalRestored > 0)
{
Console.WriteLine($"\n📋 AWS解冻说明:");
Console.WriteLine($" • 解冻任务已提交到Glacier服务");
Console.WriteLine($" • 标准解冻: 3-5小时 (Glacier Flexible Retrieval)");
Console.WriteLine($" • 加急解冻: 1-5分钟 (额外收费)");
Console.WriteLine($" • 解冻后文件可读 {restoreDays} 天");
}
}
catch (Exception ex)
{
Log.Logger.Error($"❌ AWS解冻操作失败: {ex.Message}");
throw;
}
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
/// <summary>
/// 坑方法,会清空之前的规则
/// </summary>
/// <param name="prefix"></param>
/// <param name="ruleId"></param>
/// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception>
public async Task SetLifecycle(string prefix, string ruleId = "immediate-archive")
{
BackBatchGetToken();
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);
ruleId = $"{ruleId}_{prefix}";
var rule = new Aliyun.OSS.LifecycleRule
{
ID = ruleId,
Prefix = prefix,
Status = RuleStatus.Enabled,
Transitions = new Aliyun.OSS.LifecycleRule.LifeCycleTransition[]
{
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
{
LifeCycleExpiration =
{
Days = 1
},
StorageClass = StorageClass.IA
},
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
{
LifeCycleExpiration =
{
Days = 30
},
StorageClass = StorageClass.Archive
}
}
};
//会清空之前历史的规则,不能用。。。
var request = new SetBucketLifecycleRequest(aliConfig.BucketName);
request.AddLifecycleRule(rule);
_ossClient.SetBucketLifecycle(request);
}
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);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
/// <summary>
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
@@ -249,8 +765,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -375,8 +891,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -450,8 +966,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -504,11 +1020,14 @@ public class OSSService : IOSSService
var result = _ossClient.GetObject(aliConfig.BucketName, ossRelativePath);
// 将OSS返回的流复制到内存流中并返回
var memoryStream = new MemoryStream();
await result.Content.CopyToAsync(memoryStream);
memoryStream.Position = 0; // 重置位置以便读取
return memoryStream;
// 直接返回流
return result.Content;
//// 将OSS返回的流复制到内存流中并返回
//var memoryStream = new MemoryStream();
//await result.Content.CopyToAsync(memoryStream);
//memoryStream.Position = 0; // 重置位置以便读取
//return memoryStream;
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
@@ -520,16 +1039,30 @@ public class OSSService : IOSSService
.WithSSL(minIOConfig.UseSSL)
.Build();
var memoryStream = new MemoryStream();
var pipe = new System.IO.Pipelines.Pipe();
var getObjectArgs = new GetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithCallbackStream(stream => stream.CopyToAsync(memoryStream));
_ = Task.Run(async () =>
{
try
{
var args = new GetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithCallbackStream(stream =>
{
stream.CopyTo(pipe.Writer.AsStream());
});
await minioClient.GetObjectAsync(getObjectArgs);
memoryStream.Position = 0;
return memoryStream;
await minioClient.GetObjectAsync(args);
await pipe.Writer.CompleteAsync();
}
catch (Exception ex)
{
await pipe.Writer.CompleteAsync(ex);
}
});
return pipe.Reader.AsStream();
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
@@ -557,10 +1090,13 @@ public class OSSService : IOSSService
var response = await amazonS3Client.GetObjectAsync(getObjectRequest);
var memoryStream = new MemoryStream();
await response.ResponseStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
return memoryStream;
// ⭐ 直接返回流
return response.ResponseStream;
//var memoryStream = new MemoryStream();
//await response.ResponseStream.CopyToAsync(memoryStream);
//memoryStream.Position = 0;
//return memoryStream;
}
else
{
@@ -636,8 +1172,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -898,7 +1434,7 @@ public class OSSService : IOSSService
}
/// <summary>
/// 删除某个目录的文件
/// 删除某个目录的文件 (包含单个文件,oss单个文件需要去除前缀/)
/// </summary>
/// <param name="prefix"></param>
/// <returns></returns>
@@ -1010,8 +1546,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -1025,7 +1561,7 @@ public class OSSService : IOSSService
var listObjectsResponse = await amazonS3Client.ListObjectsV2Async(listObjectsRequest);
if (listObjectsResponse.S3Objects.Count > 0)
if (listObjectsResponse.S3Objects?.Count > 0)
{
// 准备删除请求
var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest
@@ -1055,7 +1591,7 @@ public class OSSService : IOSSService
}
}
public async Task DeleteObjects(List<string> objectKeys)
public async Task DeleteObjects(List<string> objectKeys, bool isCache = false)
{
GetObjectStoreTempToken();
@@ -1065,9 +1601,23 @@ public class OSSService : IOSSService
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
var bucketName = string.Empty;
if (isCache)
{
Uri uri = new Uri(aliConfig.ViewEndpoint);
string host = uri.Host; // 获取 "zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com"
string[] parts = host.Split('.');
bucketName = parts[0];
}
else
{
bucketName = aliConfig.BucketName;
}
if (objectKeys.Count > 0)
{
var result = _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, objectKeys, false));
var result = _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(bucketName, objectKeys, false));
}
}
@@ -1103,8 +1653,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -1178,8 +1728,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var request = new Amazon.S3.Model.GetObjectMetadataRequest
@@ -1263,9 +1813,16 @@ public class OSSService : IOSSService
{
var awsOptions = ObjectStoreServiceOptions.AWS;
// 创建 STS 客户端(考虑使用 RegionEndpoint
var stsConfig = new AmazonSecurityTokenServiceConfig
{
RegionEndpoint = RegionEndpoint.GetBySystemName(awsOptions.Region)
};
//aws 临时凭证
// 创建 STS 客户端
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey);
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey, stsConfig);
// 使用 AssumeRole 请求临时凭证
var assumeRoleRequest = new AssumeRoleRequest
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RestSharp;
using System.Net;
using System.Threading.Tasks;
using IdentityModel;
namespace IRaCIS.Core.Application.Helper.OtherTool;
public class WeComAlert
{
public string Env { get; set; } = "";
public string UserName { get; set; } = "";
public string Api { get; set; } = "";
public string Message { get; set; } = "";
public string? JsonData { get; set; }
public string? Stack { get; set; }
public bool HasStack => !string.IsNullOrWhiteSpace(Stack);
public string[] AtUsers { get; set; } = [];
}
public static class WeComNotifier
{
public static async Task SendAlertAsync(string webhook, WeComAlert alert)
{
try
{
var client = new RestClient();
var request = new RestRequest(webhook, Method.Post);
var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// 处理 @
var atText = alert.AtUsers != null && alert.AtUsers.Any()
? string.Join("", alert.AtUsers.Select(u => $" <@{u}>"))
: "";
// 处理堆栈
var stack = alert.Stack;
if (!string.IsNullOrWhiteSpace(stack))
{
stack = stack.Replace("\n", "\n> ");
if (stack.Length > 600)
stack = stack[..600] + "...(已截断)";
}
var markdown = $@"## 🚨 系统告警
> {atText}
> **部署环境:** [{alert.Env}]({alert.Env})
> **发生时间:** {time}
> **操作人:** {alert.UserName}
> **接口地址:** {alert.Api}
### ❗ 告警信息
<font color=""warning"">{alert.Message}</font>";
if (!string.IsNullOrWhiteSpace(alert.JsonData))
{
markdown += $@"
> **📦 请求数据(JSON 格式):**
```json
{alert.JsonData}
```";
}
if (!string.IsNullOrWhiteSpace(stack))
{
markdown += $@"
### 堆栈信息(部分)
<font color=""comment"">{stack}</font>";
}
var payload = new
{
msgtype = "markdown",
markdown = new { content = markdown }
};
request.AddHeader("Content-Type", "application/json");
request.AddStringBody(JsonConvert.SerializeObject(payload), DataFormat.Json);
await client.ExecuteAsync(request);
}
catch (Exception ex)
{
Log.Logger.Error("企业微信告警发送失败: " + ex.Message);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -43,6 +43,10 @@ namespace IRaCIS.Core.Application.MassTransit.Command
public DateTime CheckTime { get; set; }
public string CheckResult { get; set; }
[DictionaryTranslateAttribute("CheckState")]
public CheckStateEnum CheckState { get; set; }
}
@@ -82,7 +82,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
join noneDicomStudy in _noneDicomStudyRepository.AsQueryable() on sv.Id equals noneDicomStudy.SubjectVisitId
join noneDicomStudy in _noneDicomStudyRepository.Where(t => t.FileCount > 0) on sv.Id equals noneDicomStudy.SubjectVisitId
select new CheckDBModel()
{
SubjectVisitId = sv.Id,
@@ -320,7 +320,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
join study in _studyRepository.AsQueryable() on sv.Id equals study.SubjectVisitId
select new CheckDBModel()
{
SubjectStatus = sv.Subject.Status,
SubjectStatus = sv.Subject.Status,
SubjectVisitId = sv.Id,
SiteCode = sv.TrialSite.TrialSiteCode,
StudyDate = study.StudyTime == null ? string.Empty : ((DateTime)study.StudyTime).ToString("yyyy-MM-dd"),
@@ -332,7 +332,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
join noneDicomStudy in _noneDicomStudyRepository.AsQueryable() on sv.Id equals noneDicomStudy.SubjectVisitId
join noneDicomStudy in _noneDicomStudyRepository.Where(t => t.FileCount > 0) on sv.Id equals noneDicomStudy.SubjectVisitId
select new CheckDBModel()
{
SubjectStatus = sv.Subject.Status,
@@ -403,6 +403,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
{
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
CheckTime = DateTime.Now,
CheckState=CheckStateEnum.CVPassed,
SiteCode = dbCurrentVisitFirst.SiteCode,
SubjectCode = dbCurrentVisitFirst.SubjectCode,
VisitName = dbCurrentVisitFirst.VisitName,
@@ -420,6 +421,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
fullCheckResultList.Add(new FullCheckResult()
{
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
CheckState = CheckStateEnum.None,
CheckTime = DateTime.Now,
SiteCode = dbCurrentVisitFirst.SiteCode,
SubjectCode = dbCurrentVisitFirst.SubjectCode,
@@ -256,7 +256,7 @@ public class ImageConsumer(
}
/// <summary>
/// 发送影像重传相关邮件的通用方法
/// 发送影像重传相关邮件的通用方法 已经处理项目
/// </summary>
private async Task SendImageReuploadEmail(SendImageReuploadEmailInDto inDto)
{
@@ -339,12 +339,12 @@ public class ImageConsumer(
var messageToSend = new MimeMessage();
// 发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
// 添加抄送
foreach (var ccUser in ccUserList)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
messageToSend.Cc.Add(new MailboxAddress(ccUser.FullName, ccUser.EMail));
}
// 格式化邮件内容
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
@@ -369,8 +369,7 @@ public class ImageConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(inDto.EmailNoticeConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -437,7 +436,7 @@ public class ImageConsumer(
}
/// <summary>
/// 发送阅片人筛选相关邮件的通用方法
/// 发送阅片人筛选相关邮件的通用方法 已经处理项目
/// </summary>
private async Task SendReviewerSelectionEmail(EmailNoticeConfig emailNoticeConfig, Guid trialId, List<Guid> enrollIdList)
{
@@ -475,12 +474,12 @@ public class ImageConsumer(
var messageToSend = new MimeMessage();
// 发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
// 添加抄送
foreach (var ccUser in ccUserList)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
messageToSend.Cc.Add(new MailboxAddress(ccUser.FullName, ccUser.EMail));
}
// 格式化邮件内容
@@ -503,8 +502,7 @@ public class ImageConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailNoticeConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -83,7 +83,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -127,8 +127,8 @@ public class UrgentMedicalReviewAddedEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -192,7 +192,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -235,8 +235,8 @@ public class UrgentIRRepliedMedicalReviewConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -308,7 +308,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -353,8 +353,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -419,7 +418,7 @@ public class UrgentIRApplyedReReadingConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == taskInfo.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -462,8 +461,8 @@ public class UrgentIRApplyedReReadingConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -2,6 +2,7 @@
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Vml;
using DocumentFormat.OpenXml.Wordprocessing;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
@@ -55,6 +56,7 @@ public class UserSiteSurveySubmitedEventConsumer(
if (emailConfig != null)
{
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var trialUserList = await _trialUserRoleRepository.Where(t => t.TrialId == siteSurveyInfo.TrialId && t.TrialUser.IsDeleted == false)
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM || t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)
@@ -67,8 +69,8 @@ public class UserSiteSurveySubmitedEventConsumer(
var toUserName = string.Empty;
//有SPM
if (sPMOrCPMList.Count > 0)
//有SPM 并且参与
if (trialInfo.IsSPMJoinSiteSurvey && sPMOrCPMList.Count > 0)
{
foreach (var user in sPMOrCPMList)
{
@@ -96,7 +98,6 @@ public class UserSiteSurveySubmitedEventConsumer(
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
@@ -121,8 +122,8 @@ public class UserSiteSurveySubmitedEventConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -204,8 +205,8 @@ public class SiteSurveySPMSubmitedEventConsumer(
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -283,7 +284,7 @@ public class SiteSurverRejectedEventConsumer(
{
//没有SPM PM驳回到CRC
messageToSend.To.Add(new MailboxAddress(String.Empty, siteSurveyInfo.Email));
messageToSend.To.Add(new MailboxAddress(siteSurveyInfo.UserName, siteSurveyInfo.Email));
}
//发件地址
@@ -318,8 +319,8 @@ public class SiteSurverRejectedEventConsumer(
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -89,14 +89,14 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
foreach (var userinfo in userinfoList)
{
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
var userNames = userinfoList.Select(x => x.FullName).ToList();
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -121,8 +121,8 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -181,11 +181,11 @@ public class CRCRepliedQCChallengeEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -220,8 +220,8 @@ public class CRCRepliedQCChallengeEventConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -307,10 +307,10 @@ public class QCRepliedQCChallengeEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
if (craInfo != null)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, craInfo.EMail));
messageToSend.Cc.Add(new MailboxAddress(craInfo.FullName, craInfo.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -338,8 +338,8 @@ public class QCRepliedQCChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -438,8 +438,8 @@ public class CRCRepliedCheckChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -517,10 +517,10 @@ public class PMRepliedCheckChallengeEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
if (craInfo != null)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, craInfo.EMail));
messageToSend.Cc.Add(new MailboxAddress(craInfo.FullName, craInfo.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -548,8 +548,8 @@ public class PMRepliedCheckChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -620,7 +620,7 @@ public class CheckStateChangedToAuditEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -647,8 +647,8 @@ public class CheckStateChangedToAuditEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -707,11 +707,11 @@ public class QCClaimTaskEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -756,8 +756,8 @@ public class QCClaimTaskEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -1,14 +1,16 @@
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using IRaCIS.Core.Application.Helper.OtherTool;
using IRaCIS.Core.Domain.BaseModel;
using IRaCIS.Core.Domain.Share;
using MassTransit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NPOI.SS.Formula.Functions;
using Newtonsoft.Json;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logger) : IFilter<ConsumeContext<T>> where T : DomainEvent
public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logger, IConfiguration _config) : IFilter<ConsumeContext<T>> where T : DomainEvent
{
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
@@ -23,6 +25,44 @@ public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logge
{
var errorInfo = $"Exception: {exception.Message}[{exception.StackTrace}]" + (exception.InnerException != null ? $" InnerException: {exception.InnerException.Message}[{exception.InnerException.StackTrace}]" : "");
_logger.LogError(errorInfo);
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get<string[]>();
// 🔔 异步告警(不要阻塞请求)
_ = WeComNotifier.SendAlertAsync(
webhook: webhook,
alert: new WeComAlert
{
Env = baseUrl,
UserName = "MassTransit 自动触发邮件",
Api = "",
Message = $"异常信息:{exception.Message} ",
Stack = exception.StackTrace,
AtUsers = userList ?? []
}
);
}
}
catch (Exception ex)
{
Log.Logger.Error($"MassTransit里发送企业微信出现错误:{ex.Message}");
}
}
}
@@ -37,7 +37,12 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public async Task Consume(ConsumeContext<UrgentIRUnReadTaskRecurringEvent> context)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialId = context.Message.TrialId;
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
@@ -163,8 +168,8 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
//处理标记已通知的任务
@@ -18,7 +18,7 @@ public static class OldRecurringEmailHelper
public static string EmailNamePlaceholder => StaticData.EmailSend.EmailNamePlaceholder;
//发送项目邮件的通用方法 已经处理
public static async Task SendTrialEmailAsync(
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IRepository<Trial> _trialRepository,
@@ -34,6 +34,7 @@ public static class OldRecurringEmailHelper
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
.Include(t => t.TrialEmailNoticeUserList).Include(t => t.TrialEmailBlackUserList).FirstOrDefaultAsync();
var trialInfo=await _trialRepository.Where(t=>t.Id== trialId).FirstOrDefaultAsync();
if (trialEmailConfig == null || trialEmailConfig.IsAutoSend == false || trialEmailConfig.IsEnable == false)
{
@@ -162,7 +163,7 @@ public static class OldRecurringEmailHelper
if (sendEmailConfig != null)
{
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
}
@@ -7,6 +7,7 @@ using MassTransit.Scheduling;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -25,14 +26,20 @@ public class QCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRepo
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<VisitTask> _visitTaskRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<QCImageQuestionRecurringEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<QCImageQuestionRecurringEvent> context)
{
var trialId = context.Message.TrialId;
var isEn_us = false;
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr }).FirstNotNullAsync();
@@ -64,8 +71,9 @@ public class QCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRepo
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
{
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
user.FullName, DateTime.Now, sendStat.ToBeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
return (topicStr, htmlBodyStr, isEn_us, userId);
};
@@ -91,13 +99,20 @@ public class CRCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRep
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<CRCImageQuestionRecurringEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<CRCImageQuestionRecurringEvent> context)
{
var trialId = context.Message.TrialId;
var isEn_us = false;
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr, t.DeclarationTypeEnumList }).FirstNotNullAsync();
//找到 该项目的IQC 用户Id
@@ -131,9 +146,10 @@ public class CRCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRep
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
{
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
user.FullName, DateTime.Now, sendStat.ToBeDealedCount - sendStat.ReUploadTobeDealedCount, sendStat.ReUploadTobeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeDealedCount - sendStat.ReUploadTobeDealedCount, sendStat.ReUploadTobeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
return (topicStr, htmlBodyStr, false, userId);
@@ -158,13 +174,20 @@ public class ImageQCRecurringEventConsumer(IRepository<Trial> _trialRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<ImageQCRecurringEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<ImageQCRecurringEvent> context)
{
var trialId=context.Message.TrialId;
var isEn_us = false;
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr }).FirstNotNullAsync();
@@ -199,9 +222,12 @@ public class ImageQCRecurringEventConsumer(IRepository<Trial> _trialRepository,
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
{
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
user.FullName, DateTime.Now, sendStat.ToBeClaimedCount, sendStat.ToBeReviewedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeClaimedCount, sendStat.ToBeReviewedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
return (topicStr, htmlBodyStr, false, userId);
};
@@ -42,7 +42,12 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
DateTime now = DateTime.Now;
Console.WriteLine("发送定时过期提醒");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var systemDocQuery =
from sysDoc in _systemDocumentRepository.AsQueryable(false)
from identityUser in _identityUserRepository.AsQueryable(false).Where(t => t.UserRoleList.Where(t => t.IsUserRoleDisabled == false).Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
@@ -89,7 +94,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
@@ -150,7 +155,11 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public async Task Consume(ConsumeContext<SystemDocumentPublishEvent> context)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
// 记录是否只发送给新增角色的日志
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
@@ -230,7 +239,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
@@ -14,6 +14,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Reactive.Joins;
using System.Text;
using System.Text.RegularExpressions;
@@ -52,7 +53,11 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
DateTime now = DateTime.Now;
Console.WriteLine("发送定时项目过期提醒");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(true)
@@ -75,6 +80,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
CurrentStaffTrainDays = trialDoc.CurrentStaffTrainDays,
NewStaffTrainDays = trialDoc.NewStaffTrainDays,
Id = trialDoc.Id,
TrialId= trialDoc.TrialId,
IsSystemDoc = false,
CreateTime = trialDoc.CreateTime,
FullFilePath = trialDoc.Path,
@@ -109,10 +115,11 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
{
Console.WriteLine($"{index}发送定时过期提醒,邮箱:{userinfo.EMail},姓名{userinfo.UserName}");
index++;
var trialInfo = _trialRepository.Where(x => x.Id == userinfo.TrialId).FirstOrDefault();
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
@@ -138,7 +145,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -162,6 +169,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialDocument> _trialDocumentRepository,
IRepository<IdentityUser> _identityUserRepository,
IRepository<Trial> _trialRepository,
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
@@ -173,7 +181,11 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public async Task Consume(ConsumeContext<TrialDocumentPublishEvent> context)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
// 记录是否只发送给新增角色的日志
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
@@ -198,6 +210,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
{
IsSystemDoc = true,
Id = trialDoc.Id,
TrialId= trialDoc.TrialId,
CreateTime = trialDoc.CreateTime,
IsDeleted = trialDoc.IsDeleted,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
@@ -219,7 +232,9 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
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
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
join trial in _trialRepository.AsQueryable(false) on trialDoc.TrialId equals trial.Id
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
on trialIdentityUser.IdentityUserId equals identityUser.Id
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted &&trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
@@ -228,6 +243,8 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
{
IsSystemDoc = false,
Id = trialDoc.Id,
TrialId = trialDoc.TrialId,
EmailFromName =trial.EmailFromName,
CreateTime = trialDoc.CreateTime,
IsDeleted = trialDoc.IsDeleted,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
@@ -242,6 +259,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
FullFilePath = trialDoc.Path
};
}
var datalist = await systemDocQuery.IgnoreQueryFilters().ToListAsync();
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
@@ -257,7 +275,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
@@ -283,7 +301,12 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
var trial = datalist.Where(x => x.ConfirmUserId == userinfo.Id).FirstOrDefault();
var trialInfo = await _trialRepository.Where(x=>x.Id==trial.TrialId).FirstNotNullAsync();
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
msg += "发送成功";
}
@@ -17,7 +17,9 @@ using MassTransit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Subject = IRaCIS.Core.Domain.Models.Subject;
namespace IRaCIS.Core.Application.Service.Allocation;
@@ -45,6 +47,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
IRepository<DicomSeries> _dicomSeriesRepository,
IRepository<SubjectCanceDoctor> _subjectCanceDoctorRepository,
IRepository<ReadingTaskQuestionMark> _readingTaskQuestionMarkRepository,
IRepository<ReadingNoneDicomMark> _readingNoneDicomMarkRepository,
IRepository<ReadingNoneDicomMarkBinding> _readingNoneDicomMarkBindingRepository,
IRepository<ReadingTableAnswerRowInfo> _readingTableAnswerRowInfoRepository,
//IRepository<ReadingCustomTag> _readingCustomTagRepository,
IRepository<TaskInfluence> _taskInfluenceRepository,
@@ -1027,8 +1031,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//满足前序访视不存在 需要签署但是未签署 sql 相当复杂 同时想查询所有未读的统计数字 就无法统计 byzhouhang
//但是加字段 IsFrontTaskNeedSignButNotSign 那么签名临床数据的时候,要对该subject 该标准的有效的任务 这个字段需要在签名的时候维护 采取这种方式 统计数字灵活
//.Where(t => t.Subject.SubjectVisitTaskList.AsQueryable().Where(visitTaskLambda).Any(c => c.IsNeedClinicalDataSign == true && c.IsClinicalDataSign == false && c.VisitTaskNum < t.VisitTaskNum))
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate));
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate))
;
var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode });
@@ -1171,7 +1175,9 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == trialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect)
.WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId)
.WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate));
.WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate))
.WhereIf(critrion.CriterionType == CriterionType.OCT, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.Modality == "OCT").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true)
.WhereIf(critrion.CriterionType == CriterionType.IVUS, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.Modality == "IVUS").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true);
var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode });
@@ -2081,7 +2087,9 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
{
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
{
CopyForms(newTask, origenalTask);
newTask.IsCopyLesionAnswer = true;
//CopyForms(newTask, origenalTask);
}
@@ -2179,7 +2187,6 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
{
CopyForms(newTask, origenalTask);
}
}
@@ -2189,7 +2196,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
{
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
{
CopyForms(newTask, origenalTask);
newTask.IsCopyLesionAnswer = true;
//CopyForms(newTask, origenalTask);
}
}
@@ -2214,7 +2222,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
private void CopyForms(VisitTask newTask, VisitTask origenalTask)
{
newTask.IsCopyLesionAnswer = true;
//自定义
//var readingCustomTagList = _readingCustomTagRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
@@ -2256,7 +2264,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//ReadingTableAnswerRowInfo ReadingTableQuestionAnswer 一起加
var readingTableAnswerRowInfoList = _readingTableAnswerRowInfoRepository.Where(t => t.VisitTaskId == origenalTask.Id).Include(t => t.LesionAnswerList).ToList();
Dictionary<Guid, Guid> lesionRelationship = new Dictionary<Guid, Guid>() { };
foreach (var item in readingTableAnswerRowInfoList)
{
@@ -2264,7 +2272,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
var originalFristAddTaskId = item.FristAddTaskId;
var newRowId = NewId.NextSequentialGuid();
lesionRelationship.Add(item.Id, newRowId);
foreach (var mark in readingTaskQuestionMarkList)
{
mark.RowId = mark.RowId == item.Id ? newRowId : mark.RowId;
@@ -2286,13 +2294,54 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
}
}
foreach (var item in readingTableAnswerRowInfoList)
{
if (item.SplitRowId != null && lesionRelationship.ContainsKey(item.SplitRowId.Value))
{
item.SplitRowId = lesionRelationship[item.SplitRowId.Value];
}
if (item.MergeRowId != null && lesionRelationship.ContainsKey(item.MergeRowId.Value))
{
item.MergeRowId = lesionRelationship[item.MergeRowId.Value];
}
}
_ = _readingTaskQuestionMarkRepository.AddRangeAsync(readingTaskQuestionMarkList).Result;
_ = _readingTableAnswerRowInfoRepository.AddRangeAsync(readingTableAnswerRowInfoList).Result;
var noneMarkList = _readingNoneDicomMarkRepository.Where(x => x.VisitTaskId == origenalTask.Id).ToList();
var noneMarkBindingList = _readingNoneDicomMarkBindingRepository.Where(x => x.VisitTaskId == origenalTask.Id).ToList();
foreach (var item in noneMarkList)
{
var newid = NewId.NextSequentialGuid();
item.MeasureData = item.MeasureData.Replace(origenalTask.Id.ToString(), newTask.Id.ToString());
item.VisitTaskId = newTask.Id;
foreach (var item1 in noneMarkBindingList)
{
if (item1.NoneDicomMarkId == item.Id)
{
item1.NoneDicomMarkId = newid;
}
}
item.Id = newid;
}
foreach (var item in noneMarkBindingList)
{
item.Id = NewId.NextSequentialGuid();
item.VisitTaskId = newTask.Id;
}
_ = _readingNoneDicomMarkRepository.AddRangeAsync(noneMarkList).Result;
_ = _readingNoneDicomMarkBindingRepository.AddRangeAsync(noneMarkBindingList).Result;
}
@@ -2385,6 +2434,10 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//另一个阅片人的任务根据任务进度自动进入PM退回或PM申请重阅
filterExpression = filterExpression.And(t => t.VisitTaskNum >= task.VisitTaskNum);
//退回只影响有序的后续所有的,无序的当前访视
filterExpression = filterExpression.And(t => (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) ||
(t.TrialReadingCriterion.IsReadingTaskViewInOrder != ReadingOrder.InOrder && t.SourceSubjectVisitId == task.SourceSubjectVisitId));
var influenceTaskList = await _visitTaskRepository.Where(filterExpression, true).ToListAsync();
@@ -2542,9 +2595,12 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//删除序列数据
await _subjectCriteriaEvaluationVisitStudyFilterRepository.BatchDeleteNoTrackingAsync(t => t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB && t.SubjectVisit.SubjectId == task.SubjectId && t.SubjectVisitId == task.SourceSubjectVisitId);
otherVisitIdList = otherVisitIdList.Where(t => t != task.SourceSubjectVisitId.Value).ToList();
}
//BM后续访视 ,筛选状态不变,任务生成状态重置(实际该访视任务状态 可能是重阅重置了或者失效了,需要后续生成,或者取消分配了,需要后续重新分配)
await _subjectCriteriaEvaluationVisitFilterRepository.UpdatePartialFromQueryAsync(t => t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB && t.SubjectVisit.SubjectId == task.SubjectId && otherVisitIdList.Contains(t.SubjectVisitId),
t => new SubjectCriteriaEvaluationVisitFilter()
@@ -2706,6 +2762,13 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//默认影响的都是该标准的任务
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId);
}
//退回只影响有序的后续所有的,无序的当前访视
if (isReReading == false)
{
filterExpression = filterExpression.And(t => (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) ||
(t.TrialReadingCriterion.IsReadingTaskViewInOrder != ReadingOrder.InOrder && t.SourceSubjectVisitId == filterObj.SourceSubjectVisitId));
}
}
@@ -10,6 +10,22 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.ViewModel;
public class GetReSendEmailInDto : PageInput
{
public Guid Id { get; set; }
public DateTime? EmailStartDate { get; set; }
public DateTime? EmailEndDate { get; set; }
public EmailState? EmailStateEnum { get; set; }
public string ToRecipientName { get; set; } = string.Empty;
public string CcRecipientName { get; set; } = string.Empty;
}
public class EmailLogView : EmailLogAddOrEdit
{
@@ -125,12 +141,27 @@ public class GetEmailInfoOutDto:EmailLogView
public class GetEmailInfoInDto
{
public Guid Id { get; set; }
public Guid? TrialId { get; set; }
}
public class SynchronizationEmailInDto
{
public Guid? TrialId { get; set; }
}
public class ResendEmailInDto
{
public Guid Id { get; set; }
public Guid? TrialId { get; set; }
}
public class EmailAuthorization
{
public string FromEmail { get; set; } = string.Empty;
public string AuthorizationCode { get; set; } = string.Empty;
}
public class EmailLogQuery:PageInput
@@ -4,13 +4,14 @@
// 生成时间 2025-10-28 06:22:42Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using DocumentFormat.OpenXml.Wordprocessing;
using IdentityModel.Client;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure.Extention;
using IdentityModel.Client;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
@@ -19,8 +20,10 @@ using MassTransit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using MimeKit;
using Panda.DynamicWebApi.Attributes;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service;
@@ -38,6 +41,8 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
IRepository<Trial> _trialRepository,
IRepository<EmailAttachmentLog> _emailAttachmentLogRepository,
IOSSService oSSService,
IRepository<EmailReSendLog> _emailReSendLog,
IRepository<EmailRecipientLog> _emailRecipientLogRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer): BaseService, IEmailLogService
@@ -75,6 +80,33 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
}
/// <summary>
/// 获取重发信息
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<EmailLogView>> GetReSendEmail(GetReSendEmailInDto inDto)
{
var messageId = await _emailLogRepository.Where(x => x.Id == inDto.Id).Select(x => x.MessageId).FirstOrDefaultAsync();
var reSendMessagelist = await _emailReSendLog.Where(x => x.MainMailMessageId == messageId).Select(x => x.ReMailMessageId).ToListAsync();
var emailLogQueryable = _emailLogRepository
.Where(x => reSendMessagelist.Contains(x.MessageId))
.WhereIf(inDto.EmailStartDate.HasValue, x => x.EmailDate >= inDto.EmailStartDate.Value)
.WhereIf(inDto.EmailEndDate.HasValue, x => x.EmailDate <= inDto.EmailEndDate.Value)
.WhereIf(inDto.EmailStateEnum.HasValue, x => x.EmailStateEnum == inDto.EmailStateEnum.Value)
.WhereIf(inDto.CcRecipientName.IsNotNullOrEmpty(), x => x.EmailRecipientLogList.Any(x => x.RecipientTypeEnum == RecipientType.Cc && x.RecipientName.Contains(inDto.CcRecipientName)))
.WhereIf(inDto.ToRecipientName.IsNotNullOrEmpty(), x => x.EmailRecipientLogList.Any(x => x.RecipientTypeEnum == RecipientType.To && x.RecipientName.Contains(inDto.ToRecipientName)))
.ProjectTo<EmailLogView>(_mapper.ConfigurationProvider);
var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" };
var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray);
return pageList;
}
/// <summary>
/// 获取单条邮件日志详情
/// </summary>
@@ -95,8 +127,7 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
{
try
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
await AuthenticateImap(client,inDto.TrialId);
var sentFolder = OpenSentFolder(client);
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
var message = sentFolder.GetMessage(uid);
@@ -181,8 +212,7 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
{
try
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
await AuthenticateImap(client,inDto.TrialId);
var sentFolder = OpenSentFolder(client);
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
var message = sentFolder.GetMessage(uid);
@@ -222,7 +252,41 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
messageToSend.Body = builder.ToMessageBody();
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
SystemEmailSendConfig sendConfig = new SystemEmailSendConfig()
{
};
if (inDto.TrialId != null)
{
sendConfig = await _trialRepository.Where(x => x.Id == inDto.TrialId.Value).Select(x => new SystemEmailSendConfig()
{
Host = _systemEmailConfig.Host,
Port = _systemEmailConfig.Port,
FromEmail = x.EmailFromEmail,
AuthorizationCode = x.EmailAuthorizationCode
}).FirstNotNullAsync();
}
else
{
sendConfig = _systemEmailConfig;
}
var msgid= await SendEmailHelper.SendEmailAsync(messageToSend, sendConfig);
await _emailReSendLog.AddAsync(new EmailReSendLog()
{
MainMailMessageId= emailInfo.MessageId,
ReMailMessageId= msgid
});
await _emailReSendLog.SaveChangesAsync();
await SynchronizationEmail(new SynchronizationEmailInDto() {
TrialId= inDto.TrialId
});
return ResponseOutput.Ok();
}
@@ -232,34 +296,39 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> SynchronizationEmail()
public async Task<IResponseOutput> SynchronizationEmail(SynchronizationEmailInDto inDto)
{
var maxTime=await _emailLogRepository.MaxAsync(t => t.EmailDate);
var fromEmail = _systemEmailConfig.FromEmail;
if (inDto.TrialId != null)
{
fromEmail= await _trialRepository.Where(x => x.Id == inDto.TrialId.Value).Select(x => x.EmailFromEmail).FirstNotNullAsync();
}
var maxTime = await _emailLogRepository.Where(x=>x.SenderAddress== fromEmail).MaxAsync(t => t.EmailDate);
var startDate = maxTime ?? DateTime.MinValue;
List<EmailLog> emailList = new List<EmailLog>();
List<EmailRecipientLog> EmailRecipientLogList = new List<EmailRecipientLog>();
// 第一步:同步发件箱邮件
using (var client = new ImapClient())
{
try
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
await AuthenticateImap(client, inDto.TrialId);
var sentFolder = OpenSentFolder(client);
var startDate = maxTime ?? DateTime.MinValue;
var searchQuery = SearchQuery.All.And(SearchQuery.DeliveredAfter(startDate));
var uids = sentFolder.Search(searchQuery).ToList();
foreach (var uid in uids)
{
try
{
//var message = sentFolder.GetMessage(uid);
// var emailView = ConvertToEmailLogView(uid, message);
var message = sentFolder.GetMessage(uid);
if (message.Date.DateTime <= startDate)
{
@@ -267,7 +336,7 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
}
var emaillog = new EmailLog
{
Id=NewId.NextGuid(),
Id = NewId.NextGuid(),
UniqueId = uid.ToString(),
MessageId = message.MessageId ?? string.Empty,
EmailSubject = message.Subject ?? string.Empty,
@@ -282,7 +351,7 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
emaillog.SenderName = fromMailbox.Name ?? string.Empty;
}
List<EmailRecipientLog> recipientLogs= new List<EmailRecipientLog>();
List<EmailRecipientLog> recipientLogs = new List<EmailRecipientLog>();
int sort = 0;
message.To.Mailboxes.ForEach(x =>
@@ -318,22 +387,37 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
}
}
sentFolder.Close();
}
catch (Exception ex)
{
Console.WriteLine($"同步发件箱邮件时出错: {ex.Message}");
}
finally
{
client.Disconnect(true);
}
await _emailLogRepository.AddRangeAsync(emailList);
await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList);
await _emailLogRepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
// 保存同步的发件箱邮件
await _emailLogRepository.AddRangeAsync(emailList);
await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList);
await _emailLogRepository.SaveChangesAsync();
// 第二步:处理收件箱中的邮件发送失败通知
try
{
await ProcessInboxFailureNotifications(startDate,inDto.TrialId);
}
catch (Exception ex)
{
Console.WriteLine($"处理收件箱失败通知邮件时出错: {ex.Message}");
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
// 即使处理失败通知邮件出错,也不影响整体同步操作的成功
// 记录警告但不中断主流程
}
return ResponseOutput.Ok();
}
private IMailFolder OpenSentFolder(ImapClient client)
@@ -391,11 +475,81 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
return folder;
}
private void AuthenticateImap(ImapClient client)
private IMailFolder OpenInboxFolder(ImapClient client)
{
IMailFolder folder = null;
try
{
folder = client.GetFolder(SpecialFolder.All);
}
catch { }
if (folder == null)
{
var candidates = new[] { "收件箱", "Inbox" };
foreach (var name in candidates)
{
try
{
var f = client.GetFolder(name);
if (f != null)
{
folder = f;
break;
}
}
catch { }
}
}
if (folder == null)
{
try
{
var personal = client.GetFolder(client.PersonalNamespaces.FirstOrDefault());
if (personal != null)
{
foreach (var sub in personal.GetSubfolders(false))
{
if (string.Equals(sub.FullName, "Inbox", StringComparison.OrdinalIgnoreCase))
{
folder = sub;
break;
}
}
}
}
catch { }
}
if (folder == null)
throw new InvalidOperationException("未找到收件箱文件夹");
folder.Open(FolderAccess.ReadOnly);
return folder;
}
private async Task AuthenticateImap(ImapClient client, Guid? trialId)
{
try
{
client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
EmailAuthorization authorization = new EmailAuthorization()
{
FromEmail = _systemEmailConfig.FromEmail,
AuthorizationCode = _systemEmailConfig.AuthorizationCode,
};
if (trialId != null)
{
authorization = await _trialRepository.Where(x => x.Id == trialId.Value).Select(x => new EmailAuthorization()
{
AuthorizationCode = x.EmailAuthorizationCode,
FromEmail = x.EmailFromEmail
}).FirstNotNullAsync();
}
client.Connect(_systemEmailConfig.Imap, _systemEmailConfig.ImapPort, SecureSocketOptions.SslOnConnect);
client.Authenticate(authorization.FromEmail, authorization.AuthorizationCode);
}
catch (AuthenticationException)
{
@@ -420,254 +574,173 @@ public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
}
}
//private string AcquireOAuth2TokenByPassword()
//{
// if (_systemEmailConfig.OAuthTenantId.IsNullOrEmpty() || _systemEmailConfig.OAuthClientId.IsNullOrEmpty())
// return string.Empty;
// using var http = new HttpClient();
// var req = new PasswordTokenRequest
// {
// Address = $"https://login.microsoftonline.com/{_systemEmailConfig.OAuthTenantId}/oauth2/v2.0/token",
// ClientId = _systemEmailConfig.OAuthClientId,
// ClientSecret = _systemEmailConfig.OAuthClientSecret,
// Scope = "offline_access https://outlook.office365.com/IMAP.AccessAsUser.All",
// UserName = _systemEmailConfig.FromEmail,
// Password = _systemEmailConfig.AuthorizationCode
// };
// var resp = http.RequestPasswordTokenAsync(req).GetAwaiter().GetResult();
// if (resp.IsError || resp.AccessToken.IsNullOrEmpty())
// return string.Empty;
// return resp.AccessToken;
//}
// 取skip的值
public int CalcReverseSkip(int pageIndex, int pageSize, int totalCount)
{
int lastPageFullSkip = totalCount / pageSize * pageSize; // 最后一页“整页”起点
int skip = lastPageFullSkip - pageIndex * pageSize; // 倒着减
return Math.Max(skip, 0); // 防止负值
}
/// <summary>
/// 邮件筛选条件构建
/// 处理收件箱中的邮件发送失败通知
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
private SearchQuery BuildSearchQuery(EmailLogQuery query)
public async Task ProcessInboxFailureNotifications(DateTime startTime, Guid? trialId)
{
var searchQuery = SearchQuery.All;
// 日期范围筛选
if (query.EmailStartDate.HasValue || query.EmailEndDate.HasValue)
{
var startDate = query.EmailStartDate ?? DateTime.MinValue;
var endDate = query.EmailEndDate ?? DateTime.MaxValue;
searchQuery = searchQuery.And(SearchQuery.DeliveredAfter(startDate)
.And(SearchQuery.DeliveredBefore(endDate.AddDays(1)))); // 包含结束日期当天
}
// 主题关键词筛选(如果IMAP服务器支持)
if (!string.IsNullOrEmpty(query.ToRecipientName))
{
searchQuery = searchQuery.And(SearchQuery.HeaderContains("To", query.ToRecipientName));
}
if (!string.IsNullOrEmpty(query.CcRecipientName))
{
searchQuery = searchQuery.And(SearchQuery.HeaderContains("Cc", query.CcRecipientName));
}
return searchQuery;
}
/// <summary>
/// 邮件排序处理
/// </summary>
/// <param name="sentFolder"></param>
/// <param name="uids"></param>
/// <returns></returns>
private List<EmailLogView> GetEmailsWithSorting(IMailFolder sentFolder, IList<UniqueId> uids)
{
var emailList = new List<EmailLogView>();
// 获取完整的邮件信息
foreach (var uid in uids)
using (var client = new ImapClient())
{
try
{
var message = sentFolder.GetMessage(uid);
var emailView = ConvertToEmailLogView(uid, message);
emailList.Add(emailView);
await AuthenticateImap(client, trialId);
var inboxFolder = OpenInboxFolder(client);
// 搜索可能包含失败通知的邮件
// 通常失败通知邮件会包含"失败"、"错误"、"undeliverable"、"delivery failed"等关键词
var searchQuery = SearchQuery.All.Or(SearchQuery.SubjectContains("失败"))
.Or(SearchQuery.SubjectContains("错误"))
.Or(SearchQuery.SubjectContains("undeliverable"))
.Or(SearchQuery.SubjectContains("delivery failed"))
.Or(SearchQuery.SubjectContains("Delivery Status Notification"))
.Or(SearchQuery.BodyContains("失败"))
.Or(SearchQuery.BodyContains("错误"))
.Or(SearchQuery.BodyContains("undeliverable"))
.Or(SearchQuery.BodyContains("delivery failed")).And(SearchQuery.DeliveredAfter(startTime));
var uids = inboxFolder.Search(searchQuery).ToList();
var processedCount = 0;
var updatedCount = 0;
foreach (var uid in uids)
{
try
{
var message = inboxFolder.GetMessage(uid);
// 尝试从邮件内容中提取原始邮件的Message-Id或相关信息
var originalMessageId = ExtractOriginalMessageId(message);
if (!string.IsNullOrEmpty(originalMessageId))
{
// 查找对应的EmailLog记录
var emailLog = await _emailLogRepository
.Where(x => x.MessageId == originalMessageId)
.FirstOrDefaultAsync();
await _emailLogRepository.BatchUpdateNoTrackingAsync(x => x.MessageId == originalMessageId, x => new EmailLog()
{
EmailStateEnum = EmailState.Error,
ErrorInfo = message.TextBody
});
updatedCount++;
}
processedCount++;
}
catch (Exception ex)
{
}
}
inboxFolder.Close();
}
catch (Exception ex)
{
Console.WriteLine($"处理邮件 {uid} 失败: {ex.Message}");
Console.WriteLine($"连接收件箱处理失败通知邮件时出错: {ex.Message}");
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
}
finally
{
try
{
client.Disconnect(true);
}
catch (Exception ex)
{
Console.WriteLine($"断开IMAP客户端连接时出错: {ex.Message}");
}
}
}
return emailList;
}
/// <summary>
/// 获取完整的邮件信息
/// 从失败通知邮件中提取原始邮件的Message-Id
/// </summary>
/// <param name="uid"></param>
/// <param name="message"></param>
/// <returns></returns>
private EmailLogView ConvertToEmailLogView(UniqueId uid, MimeMessage message)
private string ExtractOriginalMessageId(MimeMessage message)
{
string saveFolder = @"D:\MailAttachments";
Directory.CreateDirectory(saveFolder);
var emailView = new EmailLogView
{
UniqueId = uid.ToString(),
MessageId = message.MessageId ?? string.Empty,
EmailSubject = message.Subject ?? string.Empty,
EmailDate = message.Date.UtcDateTime,
Content = message.HtmlBody ?? string.Empty,
};
// 首先检查邮件头中是否有原始Message-Id
var originalMessageId = message.Headers["Original-Message-ID"] ??
message.Headers["X-Original-Message-ID"] ??
message.Headers["In-Reply-To"];
var toHeader = message.Headers[HeaderId.To]; // 原始头,未解码
Console.WriteLine($"原始To头 = |{toHeader}|");
foreach (var att in message.Attachments)
if (!string.IsNullOrEmpty(originalMessageId))
{
// att 可能是 MimePart 或 MessagePart
if (att is MimePart part && !string.IsNullOrEmpty(part.FileName))
// 清理Message-Id格式(移除尖括号)
originalMessageId = originalMessageId.Trim('<', '>');
return originalMessageId;
}
// 检查邮件附件中是否包含.eml文件(这是最常见的失败通知格式)
foreach (var attachment in message.Attachments)
{
if (attachment is MessagePart messagePart)
{
var file = Path.Combine(saveFolder, part.FileName);
using (var stream = File.Create(file))
part.Content.DecodeTo(stream);
Console.WriteLine($" 附件已保存:{file}");
// 直接获取嵌入的邮件消息
var originalMessageIdFromAttachment = messagePart.Message?.MessageId;
if (!string.IsNullOrEmpty(originalMessageIdFromAttachment))
{
return originalMessageIdFromAttachment.Trim('<', '>');
}
}
else if (attachment is MimePart mimePart)
{
// 检查文件扩展名是否为.eml
var fileName = mimePart.FileName ?? mimePart.ContentType.Name;
if (!string.IsNullOrEmpty(fileName) &&
(fileName.EndsWith(".eml", StringComparison.OrdinalIgnoreCase) ||
mimePart.ContentType.MimeType.Equals("message/rfc822", StringComparison.OrdinalIgnoreCase)))
{
try
{
// 解析.eml附件内容
using var memoryStream = new MemoryStream();
mimePart.Content.DecodeTo(memoryStream);
memoryStream.Position = 0;
var originalMessage = MimeMessage.Load(memoryStream);
if (!string.IsNullOrEmpty(originalMessage?.MessageId))
{
return originalMessage.MessageId.Trim('<', '>');
}
}
catch (Exception ex)
{
Console.WriteLine($"解析.eml附件时出错: {ex.Message}");
}
}
}
}
// 处理发件人信息
var fromMailbox = message.From.Mailboxes.FirstOrDefault();
if (fromMailbox != null)
// 如果邮件头和附件中都没有,尝试从邮件正文中提取
var content = message.HtmlBody ?? message.TextBody ?? string.Empty;
// 尝试匹配常见的Message-Id格式
var messageIdPattern = @"(?:Message-ID|Message-Id|message-id):\s*<?([^>\s]+)>?";
var match = System.Text.RegularExpressions.Regex.Match(content, messageIdPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (match.Success)
{
emailView.SenderAddress = fromMailbox.Address;
emailView.SenderName = fromMailbox.Name ?? string.Empty;
return match.Groups[1].Value;
}
//// 处理收件人
//emailView.ToRecipients = message.To.Mailboxes.Select(address => new EmaliSendInfo
//{
// Name = address.Name ?? string.Empty,
// Address = address.Address
//}).ToList();
// 尝试从邮件引用部分提取
var referencePattern = @"^>.*Message-ID:\s*<?([^>\s]+)>?";
var matches = System.Text.RegularExpressions.Regex.Matches(content, referencePattern, System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (matches.Count > 0)
{
return matches[0].Groups[1].Value;
}
//// 处理抄送人
//emailView.CcRecipients = message.Cc.Mailboxes.Select(address => new EmaliSendInfo
//{
// Name = address.Name ?? string.Empty,
// Address = address.Address
//}).ToList();
//// 处理密送人
//emailView.BccRecipients = message.Bcc.Mailboxes.Select(address => new EmaliSendInfo
//{
// Name = address.Name ?? string.Empty,
// Address = address.Address
//}).ToList();
// 处理附件
//emailView.Attachments = ProcessAttachments(message);
//emailView.HasAttachments = emailView.Attachments.Any();
//emailView.AttachmentCount = emailView.Attachments.Count;
return emailView;
return null;
}
///// <summary>
///// 处理附件信息
///// </summary>
///// <param name="message"></param>
///// <returns></returns>
//private List<EmailAttachmentInfo> ProcessAttachments(MimeMessage message)
//{
// var attachments = new List<EmailAttachmentInfo>();
// foreach (var attachment in message.Attachments)
// {
// var attachmentInfo = new EmailAttachmentInfo();
// if (attachment is MimePart mimePart)
// {
// attachmentInfo.FileName = mimePart.FileName ??
// mimePart.ContentType?.Name ??
// "未知文件";
// attachmentInfo.ContentType = mimePart.ContentType.MimeType;
// attachmentInfo.ContentId = mimePart.ContentId ?? string.Empty;
// attachmentInfo.IsInline = mimePart.ContentDisposition != null &&
// mimePart.ContentDisposition.Disposition.Equals("inline", StringComparison.OrdinalIgnoreCase);
// // 获取附件大小
// try
// {
// if (mimePart.ContentObject.Stream != null)
// {
// attachmentInfo.Size = mimePart.ContentObject.Stream.Length;
// }
// }
// catch
// {
// attachmentInfo.Size = 0;
// }
// }
// else if (attachment is MessagePart messagePart)
// {
// // 处理内嵌消息作为附件的情况
// attachmentInfo.FileName = "embedded-message.eml";
// attachmentInfo.ContentType = "message/rfc822";
// attachmentInfo.IsInline = false;
// }
// attachments.Add(attachmentInfo);
// }
// return attachments;
//}
/// <summary>
/// 应用内存中的过滤条件(对于无法通过IMAP搜索的条件)
/// </summary>
/// <param name="emails"></param>
/// <param name="query"></param>
/// <returns></returns>
private List<EmailLogView> ApplyMemoryFilters(List<EmailLogView> emails, EmailLogQuery query)
{
var filtered = emails.AsEnumerable();
// 收件人姓名筛选
//if (!string.IsNullOrEmpty(query.Keyword))
//{
// filtered = filtered.Where(e =>
// e.ToRecipients.Any(r =>
// r.Name.Contains(query.Keyword, StringComparison.OrdinalIgnoreCase)) ||
// e.CcRecipients.Any(r =>
// r.Name.Contains(query.Keyword, StringComparison.OrdinalIgnoreCase))
// );
//}
// 发件人姓名筛选
//if (!string.IsNullOrEmpty(query.SenderName))
//{
// filtered = filtered.Where(e =>
// e.SenderName.Contains(query.SenderName, StringComparison.OrdinalIgnoreCase));
//}
return filtered.ToList();
}
public async Task<IResponseOutput> AddOrUpdateEmailLog(EmailLogAddOrEdit addOrEditEmailLog)
@@ -10,12 +10,13 @@ using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service.Common;
public class TumorCommonQustionInfo
{
//问题标识,肿瘤评估用于区分是什么问题
public QuestionType? QuestionType { get; set; }
public OptionType OptionTypeEnum { get; set; }
public Guid QuestionId { get; set; }
public string QuestionName { get; set; }
@@ -52,6 +53,8 @@ public class TumorLessionAnswerInfo
{
public QuestionMark? QuestionMark { get; set; }
public OptionType OptionTypeEnum { get; set; }
//病灶Id
public Guid RowId { get; set; }
@@ -122,7 +125,23 @@ public class TumorExportBaseModel : TU_TR_RSBaseModel
public string VisitNote { get; set; }
#endregion
#region
public List<OncologyExportInfo> OncologyResultList { get; set; }
//public string OncologyUserName { get; set; }
#endregion
}
public class OncologyExportInfo
{
public decimal? VisitTaskNum { get; set; }
public string VisitName { get; set; }
public string OncologyResult { get; set; } = string.Empty;
public string OncologyReason { get; set; } = string.Empty;
}
@@ -22,6 +22,8 @@ using System.ComponentModel.Design;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using static IRaCIS.Core.Application.Service.ExcelExportHelper;
using IDictionaryService = IRaCIS.Application.Interfaces.IDictionaryService;
using TrialIdentityUser = IRaCIS.Core.Domain.Models.TrialIdentityUser;
@@ -32,6 +34,8 @@ namespace IRaCIS.Core.Application.Service.Common
[ApiExplorerSettings(GroupName = "Common")]
public class ExcelExportService(IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<Internationalization> _internationalizationRepository,
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
IRepository<SystemDocNeedConfirmedUserType> _systemDocNeedConfirmedUserTypeRepository,
IRepository<DicomStudy> _dicomStudyRepository,
@@ -467,7 +471,10 @@ namespace IRaCIS.Core.Application.Service.Common
.WhereIf(!string.IsNullOrEmpty(inQuery.Code), o => o.TrialCode.Contains(inQuery.Code))
.WhereIf(!string.IsNullOrEmpty(inQuery.ResearchProgramNo), o => o.ResearchProgramNo.Contains(inQuery.ResearchProgramNo))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.ExperimentName), o => o.ExperimentName.Contains(inQuery.ExperimentName))
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.Admin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.OP, t => t.TrialIdentityUserList.Any(t => t.IdentityUserId == _userInfo.IdentityUserId && t.IsDeleted == false) && t.IsDeleted == false)
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.Admin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.OP,
t => t.TrialIdentityUserList.Any(t => t.IdentityUserId == _userInfo.IdentityUserId && t.IsDeleted == false
&& t.TrialUserRoleList.Any(t => t.UserId == _userInfo.UserRoleId && t.IsDeleted == false))
&& t.IsDeleted == false)
.WhereIf(inQuery.CriterionType != null, o => o.TrialReadingCriterionList.Any(t => t.CriterionType == inQuery.CriterionType && t.IsSigned && t.IsConfirm))
.WhereIf(!string.IsNullOrEmpty(inQuery.PM_EMail), o => o.TrialUserRoleList.Any(t => t.UserRole.IdentityUser.EMail.Contains(inQuery.PM_EMail) && (t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)))
.Select(t => new TrialToBeDoneDto()
@@ -1758,6 +1765,80 @@ namespace IRaCIS.Core.Application.Service.Common
var list = taskMedicalReviewQueryable.OrderBy(t => t.TrialSiteCode).ThenBy(t => t.SubjectCode).ThenBy(t => t.VisitTaskNum).ToList();
#region by he wen tao
Dictionary<string, string> i18NKeys = new Dictionary<string, string>()
{
{ "Msg1","trials:medicalFeedback:message:msg1"},
{ "Msg2","trials:medicalFeedback:message:msg2"},
{ "Msg3","trials:medicalFeedback:message:msg3"},
{ "Msg4", "trials:medicalFeedback:message:msg4"},
{ "Msg5", "trials:medicalFeedback:message:msg5"},
{ "CloseReasonEnum", "trials:medicalFeedback:title:closeReasonEnum"},
{ "IsEndorse", "trials:medicalFeedback:title:isEndorse"},
{ "Reason", "trials:medicalFeedback:title:reason"},
{ "IsRequestReread", "trials:medicalFeedback:title:isRequestReread"},
};
var i18Values = i18NKeys.Select(x => x.Value).ToList();
var i18nList = await _internationalizationRepository.Where(x => i18Values.Contains(x.Code))
.Select(x => new
{
Code = x.Code,
Value = _userInfo.IsEn_Us ? x.Value : x.ValueCN
}).ToListAsync();
List<string> dictionaryCodeList = new List<string>()
{
"AuditAdvice",
"MedicalDialogCloseEnum",
"MedicalReviewDoctorUserIdea",
"YesOrNo",
};
var dictionadParents = await _dictionaryRepository.Where(x => dictionaryCodeList.Contains(x.Parent.Code))
.Select(x => new
{
ParentCode = x.Parent.Code,
Code = x.Code,
Value = _userInfo.IsEn_Us ? x.Value : x.ValueCN
}).ToListAsync();
JointMedicalReviewI18n i18N = new JointMedicalReviewI18n() { };
foreach (var kv in i18NKeys)
{
PropertyInfo? pi = i18N.GetType().GetProperty(kv.Key);
if (pi != null && pi.CanWrite)
{
var value = i18nList.Where(x => x.Code == kv.Value).Select(x => x.Value).FirstOrDefault() ?? string.Empty;
object safeValue = Convert.ChangeType(value, pi.PropertyType);
pi.SetValue(i18N, safeValue);
}
}
foreach (var item in dictionaryCodeList)
{
PropertyInfo? pi = i18N.GetType().GetProperty(item);
if (pi != null && pi.CanWrite)
{
var value = dictionadParents.Where(x => x.ParentCode == item).ToDictionary(x => x.Code, x => x.Value);
object safeValue = Convert.ChangeType(value, pi.PropertyType);
pi.SetValue(i18N, safeValue);
}
}
foreach (var item in list)
{
foreach (var dialog in item.DialogList)
{
dialog.ResultContent = JointMedicalReviewDialog(dialog, i18N);
}
}
#endregion
var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo<ExcelExportInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
@@ -1767,6 +1848,88 @@ namespace IRaCIS.Core.Application.Service.Common
return await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialMedicalReviewList_Export, exportInfo, $"{exportInfo.ResearchProgramNo}", _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(TaskMedicalReviewExportDto));
}
/// <summary>
/// 获取医学审核对话拼接内容
/// </summary>
/// <param name="record"></param>
/// <param name="i18NAndDic"></param>
/// <returns></returns>
private string JointMedicalReviewDialog(GetMedicalReviewDialogOutDto record, JointMedicalReviewI18n i18NAndDic)
{
StringBuilder str = new StringBuilder() { };
var userTypes = new List<int>() { 14, 30 };
// 这里userTypes.Contains(record.UserTypeEnumInt) 可以提出来 但是还是按照前端写吧 免得不好对照
// v-if="[14, 30].includes(record.UserTypeEnumInt) && record.Questioning
if (userTypes.Contains(record.UserTypeEnumInt) && record.Questioning.IsNotNullOrEmpty())
{
// <!-- 您好,根据医学审核反馈,该阅片任务的评估有如下问题需要您确认或澄清: -->
str.AppendLine(i18NAndDic.Msg1);
str.AppendLine(record.Questioning);
if (record.FileList.Count() > 0)
{
str.AppendLine(i18NAndDic.Msg2);
record.FileList.ForEach(x =>
{
str.AppendLine(x.FileName);
});
}
str.AppendLine(i18NAndDic.Msg3);
str.AppendLine(i18NAndDic.AuditAdvice[record.AuditAdviceEnum.GetEnumInt()]);
str.AppendLine(i18NAndDic.Msg4);
}
//v-if="[14, 30].includes(record.UserTypeEnumInt) && record.MedicalDialogCloseEnum!== null"
if (userTypes.Contains(record.UserTypeEnumInt) && record.MedicalDialogCloseEnum != null)
{
str.AppendLine(i18NAndDic.CloseReasonEnum + i18NAndDic.MedicalDialogCloseEnum[record.MedicalDialogCloseEnum.Value.GetEnumInt()]);
}
// v-if="[14, 30].includes(record.UserTypeEnumInt) && record.Content
if (userTypes.Contains(record.UserTypeEnumInt) && record.Content.IsNotNullOrEmpty())
{
str.AppendLine(record.Content);
}
// v-if="[13].includes(record.UserTypeEnumInt)"
if (13 == record.UserTypeEnumInt)
{
str.AppendLine(i18NAndDic.IsEndorse + i18NAndDic.MedicalReviewDoctorUserIdea[record.DoctorUserIdeaEnum.GetEnumInt()]);
// v-if="record.DoctorUserIdeaEnum===2"
if ((int)record.DoctorUserIdeaEnum == 2)
{
str.AppendLine(i18NAndDic.Reason + record.DisagreeReason);
}
// record.MedicalDialogCloseEnum!== null
if (record.MedicalDialogCloseEnum != null)
{
str.AppendLine(i18NAndDic.IsRequestReread + i18NAndDic.YesOrNo[record.IsApplyHeavyReading.ToString()]);
}
// v-if="record.FileList && record.FileList.length > 0"
if (record.FileList.Count() > 0)
{
record.FileList.ForEach(x =>
{
str.AppendLine(x.FileName);
});
}
}
return str.ToString();
}
/// <summary>
/// 影像下载记录表
@@ -2605,9 +2768,13 @@ namespace IRaCIS.Core.Application.Service.Common
if (criterion.CriterionType == CriterionType.OCT)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.OCT_ReadingLession_Export, ExportCatogory = ExportResult.OCT_ReadingLession_Export });
list.Add(new ExportDocumentDes() { Code = StaticData.Export.OCT_CDISC_Export, ExportCatogory = ExportResult.OCT_CDISC_Export });
}
if (criterion.CriterionGroup != CriterionGroup.Tumor)
if (criterion.CriterionType == CriterionType.IVUS)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.IVUS_CDISC_Export, ExportCatogory = ExportResult.IVUS_CDISC_Export });
}
if (criterion.CriterionGroup != CriterionGroup.Tumor && criterion.CriterionType != CriterionType.OCT && criterion.CriterionType != CriterionType.IVUS)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.CDISC_Reading_Export, ExportCatogory = ExportResult.NoneTumorCDISC });
}
@@ -2736,7 +2903,7 @@ namespace IRaCIS.Core.Application.Service.Common
//阅片结果表
export_Template = StaticData.Export.CommonReading_Export;
}
//斑块表
else if (inQuery.ReadingExportType == ExportResult.OCT_ReadingLession_Export)
{
//OCT
@@ -3003,7 +3170,7 @@ namespace IRaCIS.Core.Application.Service.Common
addLessionInfoList.Add(new CommonQuesionInfo() { QuestionName = _userInfo.IsEn_Us ? "Lesion Type" : "病灶类型", QuestionValue = lession.LessionType, TranslateDicName = "LesionType" });
}
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionName = t.QuestionName, OptionTypeEnum = t.OptionTypeEnum, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName ,Unit=t.Unit});
//有三部分组成 外层问题+ 没有配置病灶编号和类型+ 动态的表格问题
var dynamicLessionInfoList = item.QuestionAnswerList.Union(addLessionInfoList).Union(dynamicPartialLessionInfoList).ToList();
@@ -3151,14 +3318,14 @@ namespace IRaCIS.Core.Application.Service.Common
addLessionInfoList.Add(new CommonQuesionInfo() { QuestionId = Guid.Empty, QuestionName = _userInfo.IsEn_Us ? "Table Name" : "表格名称", QuestionValue = firstLessionAnser.TableName });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.TableName + "_" + t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, OptionTypeEnum = t.OptionTypeEnum, QuestionName = t.TableName + "_" + t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode, Unit = t.Unit });
//有三部分组成 外层问题+ 固定列表格名称 + 动态的表格问题
dynamicLessionInfoList = item.QuestionAnswerList.Union(addLessionInfoList).Union(dynamicPartialLessionInfoList).ToList();
}
else
{
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, OptionTypeEnum = t.OptionTypeEnum, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode, Unit = t.Unit });
//两部分组成 外层问题+ 动态的表格问题
dynamicLessionInfoList = item.QuestionAnswerList.Union(dynamicPartialLessionInfoList).ToList();
@@ -3205,7 +3372,7 @@ namespace IRaCIS.Core.Application.Service.Common
TranslateDicNameList = translateDicNameList
};
if (export_Template == StaticData.Export.ReadingLession_Export || export_Template == StaticData.Export.CommonJudgeReadingDetail_Export)
if (export_Template == StaticData.Export.ReadingLession_Export || export_Template == StaticData.Export.CommonJudgeReadingDetail_Export || export_Template== StaticData.Export.OCT_ReadingLession_Export)
{
dynamicColumnConfig.TempalteLastColumnIndex = 8;
}
@@ -3224,7 +3391,7 @@ namespace IRaCIS.Core.Application.Service.Common
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,
QuestionId = t.Id,
TableName = _userInfo.IsEn_Us ? t.ReadingQuestionTrial.QuestionEnName : t.ReadingQuestionTrial.QuestionName,
QuestionName = _userInfo.IsEn_Us ? t.QuestionEnName : t.QuestionName,
CDISCCode = t.CDISCCode,
@@ -3268,7 +3435,7 @@ namespace IRaCIS.Core.Application.Service.Common
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 });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId,OptionTypeEnum=t.OptionTypeEnum, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode, Unit = t.Unit });
//两部分组成 外层问题+ 动态的表格问题
dynamicLessionInfoList = item.QuestionAnswerList.Union(dynamicPartialLessionInfoList).ToList();
File diff suppressed because it is too large Load Diff
@@ -240,7 +240,7 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
var query = _visitTaskRepository.Where(t => t.TrialId == inQuery.TrialId && t.IsAnalysisCreate == false && (t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze))
//访视和全局查询已签名完成的,裁判可以是未签名,未完成的
.Where(t => (t.ReadingTaskState == ReadingTaskState.HaveSigned && (t.ReadingCategory == ReadingCategory.Visit || t.ReadingCategory == ReadingCategory.Global)) || t.ReadingCategory == ReadingCategory.Judge)
.Where(t => (t.ReadingTaskState == ReadingTaskState.HaveSigned && (t.ReadingCategory == ReadingCategory.Visit || t.ReadingCategory == ReadingCategory.Global || t.ReadingCategory == ReadingCategory.Oncology)) || t.ReadingCategory == ReadingCategory.Judge)
.WhereIf(inQuery.TrialReadingCriterionId != null, t => t.TrialReadingCriterionId == inQuery.TrialReadingCriterionId)
.WhereIf(inQuery.TrialSiteId != null, t => t.Subject.TrialSiteId == inQuery.TrialSiteId)
@@ -261,7 +261,7 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
SourceSubjectVisitId = t.SourceSubjectVisitId,
VisitTaskNum = t.VisitTaskNum,
VisitNum = t.VisitTaskNum,
VisitName = t.SourceSubjectVisit.VisitName,
VisitName = t.TaskBlindName, //t.SourceSubjectVisit.VisitName,
EarliestScanDate = t.SourceSubjectVisit.EarliestScanDate,
LatestScanDate = t.SourceSubjectVisit.LatestScanDate,
VisitTaskId = t.Id,
@@ -314,7 +314,7 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
LesionList = t.LesionList.OrderBy(t => t.RowMark).Select(c => new TumorLessionInfo()
{
Id = c.Id,
OrganInfoId=c.OrganInfoId,
OrganInfoId = c.OrganInfoId,
LessionType = c.ReadingQuestionTrial.LesionType,
LessionCode = c.RowMark,
SplitRowId = c.SplitRowId,
@@ -335,7 +335,18 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
}).ToList(),
}).ToList()
}).ToList(),
OncologyResultList = t.ReadingCategory == ReadingCategory.Oncology ? t.OncologyResultList.Select(t => new OncologyExportInfo()
{
VisitName = t.VisitTask.TaskName,
VisitTaskNum = t.VisitTask.VisitTaskNum,
OncologyReason = t.EvaluationReason,
OncologyResult = t.EvaluationResult
}).ToList() : new List<OncologyExportInfo>()
});
@@ -386,10 +397,10 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
var dicNameList = taskList.SelectMany(t => t.QuestionAnswerList).Select(t => t.TranslateDicName)
.Union(taskList.SelectMany(t => t.LesionList).SelectMany(t => t.LessionAnswerList).Where(t => t.TranslateDicName.IsNotNullOrEmpty()).Select(c => c.TranslateDicName))
.Union(new List<string>() { "ArmEnum", "ValueUnit", "GlobalAssessType", "ImageQualityIssues" })
.Union(new List<string>() { "ArmEnum", "ValueUnit", "GlobalAssessType", "ImageQualityIssues", "OncologyAssessType" })
.Distinct().ToArray();
var trialOrganDic = _trialOrganRepository.Where(t => t.TrialCriterionId == criterion.Id).Select(t=>new {t.OrganInfoId,t.Part,t.PartEN}).ToDictionary(
var trialOrganDic = _trialOrganRepository.Where(t => t.TrialCriterionId == criterion.Id).Select(t => new { t.OrganInfoId, t.Part, t.PartEN }).ToDictionary(
t => t.OrganInfoId,
t => new { t.Part, t.PartEN }
);
@@ -484,6 +495,78 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
}
continue;
}
//lungano 单独处理
if (task.ReadingCategory == ReadingCategory.Oncology)
{
//取最后一次肿瘤学结果
if (task.VisitTaskId !=
taskList.Where(t => t.SubjectCode == task.SubjectCode && t.ReadingCategory == ReadingCategory.Oncology)
.OrderByDescending(c => c.VisitTaskNum).First().VisitTaskId)
{
continue;
}
foreach (var OncologyResult in task.OncologyResultList)
{
if (OncologyResult.OncologyReason.IsNotNullOrEmpty())
{
var co = CreatNewCOExport(task, coList.Count + 1);
co.RemarksQuote = $"{OncologyResult?.VisitName}_ONCOLOGY_COMMENTS";
co.VisitNum = OncologyResult?.VisitTaskNum;
co.VisitName = OncologyResult?.VisitName;
co.Remarks = OncologyResult.OncologyReason;
co.CODTC = task.SignTime?.ToString("yyyy-MM-dd");
coList.Add(co);
}
if (OncologyResult.OncologyResult.IsNotNullOrEmpty())
{
var visitTask = taskList.Where(t => t.VisitNum == OncologyResult?.VisitTaskNum && t.SubjectCode == task.SubjectCode).FirstOrDefault();
var rs = CreatNewRSExport(task, rs_subjectIndexNoDic);
rs.ARM_VisitName = $"{task.ArmEnumStr}_{OncologyResult?.VisitName}";
rs.VisitNum = OncologyResult?.VisitTaskNum;
rs.VisitName = OncologyResult?.VisitName;
rs.LatestScanDate = visitTask?.LatestScanDate;
rs.EarliestScanDate = visitTask?.EarliestScanDate;
Fill_RS_IndexValue(rs, 9, Lugano_RSFixed.RSValueList);
rs.RespondEfficacyAssessment = translateDataList["OncologyAssessType"].Where(t => t.Code.ToLower() == OncologyResult.OncologyResult?.ToLower()).Select(t => isEn_Us ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty; ;
var findTask = reReadingList.FirstOrDefault(t => t.UserName == rs.UserName && t.ArmEnum == rs.ArmEnum && t.VisitTaskNum == rs.VisitNum);
if (findTask != null)
{
rs.ReAssessmentReason = findTask.RequestReReadingReason;
}
rsList.Add(rs);
}
}
continue;
}
@@ -520,16 +603,21 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
var tu = CreatNewTUExport(task, lesion, visitIndexNoDic, translateDataList, isEn_Us);
if (lesion.OrganInfoId.HasValue)
if (!tuList.Any(t => t.SubjectCode == task.SubjectCode && t.ARM_TumorNo == $"{task.ArmEnumStr}_{lesion.LessionCode}"))
{
tu.BodyPart = _userInfo.IsEn_Us ? trialOrganDic[lesion.OrganInfoId.Value].PartEN : trialOrganDic[lesion.OrganInfoId.Value].Part;
if (lesion.OrganInfoId.HasValue)
{
tu.BodyPart = _userInfo.IsEn_Us ? trialOrganDic[lesion.OrganInfoId.Value].PartEN : trialOrganDic[lesion.OrganInfoId.Value].Part;
}
Fill_Resisit_Lugano_TUExport(tu, lesion);
tuList.Add(tu);
}
Fill_Resisit_Lugano_TUExport(tu, lesion);
tuList.Add(tu);
#endregion
@@ -660,7 +748,7 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
}
else
{
if (questionAnswer.ValueType == ValueOfType.Percentage)
if (questionAnswer.ValueType == ValueOfType.Percentage && double.TryParse(questionAnswer.QuestionValue, out _))
{
tr.OriginalMeasurements = questionAnswer.QuestionValue + "%";
@@ -680,7 +768,7 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
#endregion
#region rs 线
#region rs 线PD
//非基线
if (task.VisitNum > 0)
@@ -690,24 +778,30 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
List<RSValueEnum> rsValueList = new List<RSValueEnum>();
var overallPD = "";
if (criterion.CriterionType == CriterionType.RECIST1Point1)
{
rsValueTypeIndex = Get_Resist_RS_VisitFixed_Index(task, questionAnswer);
rsValueList = RESIST_RSFixed.RSValueList;
overallPD = "PD";
}
else if (criterion.CriterionType == CriterionType.Lugano2014)
{
trValueTypeIndex = Get_Lugano_RS_VisitFixed_Index(task, questionAnswer);
rsValueTypeIndex = Get_Lugano_RS_VisitFixed_Index(task, questionAnswer);
rsValueList = Lugano_RSFixed.RSValueList;
overallPD = "PMD/PD";
}
if (rsValueTypeIndex != -1)
{
var rs = CreatNewRSExport(task, questionAnswer, rs_subjectIndexNoDic, translateDataList, isEn_Us);
var rs = CreatNewRSExport(task, rs_subjectIndexNoDic);
Fill_RS_IndexValue(rs, rsValueTypeIndex, rsValueList);
@@ -721,13 +815,15 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
rs.RespondEfficacyAssessment = questionAnswer.QuestionValue;
}
if (questionAnswer.QuestionType == QuestionType.Tumor)
if ((questionAnswer.QuestionType == QuestionType.Tumor && criterion.CriterionType == CriterionType.RECIST1Point1) ||
(questionAnswer.QuestionType == QuestionType.ImgOncology && criterion.CriterionType == CriterionType.Lugano2014))
{
rs.AssessmentReason = task.VisitNote;
rs.IsOveralResponse = true;
rs.IsOverallResponsePD = rs.RespondEfficacyAssessment == "PD";
rs.IsOverallResponsePD = rs.RespondEfficacyAssessment == overallPD;
}
@@ -751,6 +847,8 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
rsList.Add(rs);
}
}
#endregion
@@ -833,22 +931,17 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
}
//if (task.IsTrigerJudge == true && task.IsJudgeSelect == true && task.JudgeNote.IsNotNullOrEmpty())
//{
// var co = CreatNewCOExport(task, coList.Count + 1);
// co.RemarksQuote = $"{co.VisitName}_ADJUDICATION_COMMENTS";
// co.Remarks = task.JudgeNote;
// co.CODTC = task.JudgeSignTime?.ToString("yyyy-MM-dd");
// coList.Add(co);
//}
#endregion
}
#region --rs
//var globalList = taskList.Where(t => t.ReadingCategory == ReadingCategory.Global).GroupBy(t => new { t.SubjectCode, t.ArmEnum }).Select(g => g.OrderByDescending(t => t.VisitNum).FirstOrDefault()).ToList();
//RS表 整体肿瘤评估了,其他病灶评估输出的也是最早的日期
@@ -938,7 +1031,7 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
return co;
}
private RS_Export CreatNewRSExport(TumorExportBaseModel task, TumorCommonQustionInfo questionAnswer, Dictionary<string, int> subjectIndexNoDic, Dictionary<string, List<BasicDicSelectCopy>> translateDataList, bool isEn_Us)
private RS_Export CreatNewRSExport(TumorExportBaseModel task, Dictionary<string, int> subjectIndexNoDic)
{
subjectIndexNoDic[task.SubjectCode]++;
@@ -1117,6 +1210,38 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
}
}
if (trValueTypeIndex == 0 || trValueTypeIndex == 1)
{
if (lesion.LessionType == LesionType.TargetLesion)
{
//处理消失 空为0mm
if (lessionAnswer.QuestionValue.IsNullOrEmpty() && lesion.LessionAnswerList.FirstOrDefault(t => t.QuestionMark == QuestionMark.State)?.QuestionValue == "3")
{
lessionAnswer.QuestionValue = "0";
}
}
//有值 就展示,没值就不展示
else if (lesion.LessionType == LesionType.NonTargetLesions || lesion.LessionType == LesionType.NewLesions)
{
if (lesion.LessionAnswerList.FirstOrDefault(t => t.QuestionMark == QuestionMark.ShortAxis)?.QuestionValue.IsNotNullOrEmpty() == true
|| lesion.LessionAnswerList.FirstOrDefault(t => t.QuestionMark == QuestionMark.MajorAxis)?.QuestionValue.IsNotNullOrEmpty() == true)
{
}
else
{
trValueTypeIndex = -1;
}
}
else
{
trValueTypeIndex = -1;
}
}
return trValueTypeIndex;
@@ -1277,7 +1402,7 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
case QuestionType.NoTargetLesion:
rsValueTypeIndex = 1;
break;
case QuestionType.NewLesions:
case QuestionType.NewLesionEvaluation:
rsValueTypeIndex = 2;
break;
@@ -1307,9 +1432,6 @@ public class Tumor_CDISC_ExportService(IRepository<ReadingQuestionCriterionTrial
rsValueTypeIndex = 8;
break;
case QuestionType.Tumor:
rsValueTypeIndex = 3;
break;
}
@@ -53,7 +53,7 @@ namespace IRaCIS.Core.Application.Service
public interface IMailVerificationService
{
Task AnolymousSendEmail(string researchProgramNo, string emailAddress, int verificationCode);
Task AnolymousSendEmail(Guid trialId, string emailAddress, int verificationCode);
Task SendEmailVerification(string emailAddress, int verificationCode);
@@ -66,7 +66,7 @@ namespace IRaCIS.Core.Application.Service
Task AddUserSendEmailAsync(Guid userId, string baseUrl, string routeUrl);
Task AdminResetPwdSendEmailAsync(Guid userId, string pwdNotMd5 = "123456");
Task AdminResetPwdSendEmailAsync(Guid userId, string pwdNotMd5 = "123456", string baseUrl="");
Task SiteSurveyUserJoinEmail(Guid trialId, Guid userId, string userTypes, string baseUrl, string rootUrl);
@@ -189,6 +189,14 @@ namespace IRaCIS.Core.Application.Service
return str;
}
private string ReplacePlatformName(string needDealtxt)
{
var platformName = _userInfo.IsEn_Us ? _systemEmailConfig.PlatformName : _systemEmailConfig.PlatformNameCN;
var str = needDealtxt.Replace("{platformName}", platformName);
return str;
}
//MFA
public async Task SenMFAVerifyEmail(Guid userId, string userName, string emailAddress, int verificationCode, UserMFAType mfaType = UserMFAType.Login)
{
@@ -276,10 +284,13 @@ namespace IRaCIS.Core.Application.Service
//中心调研 登陆 发送验证码
public async Task AnolymousSendEmail(string researchProgramNo, string emailAddress, int verificationCode)
public async Task AnolymousSendEmail(Guid trialId, string emailAddress, int verificationCode)
{
//throw new BusinessValidationFailedException("模拟邮件取数据或者发送异常!!!");
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
@@ -292,7 +303,7 @@ namespace IRaCIS.Core.Application.Service
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName, researchProgramNo);
var topicStr = string.Format(input.topicStr, companyName, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(ReplaceCompanyName(input.htmlBodyStr),
@@ -312,7 +323,7 @@ namespace IRaCIS.Core.Application.Service
var sucessHandle = GetEmailSuccessHandle(Guid.Empty, verificationCode, emailAddress);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, sucessHandle);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo, sucessHandle);
}
@@ -370,7 +381,7 @@ namespace IRaCIS.Core.Application.Service
}
//管理员重置密码发送邮件
public async Task AdminResetPwdSendEmailAsync(Guid userId, string pwdNotMd5 = "123456")
public async Task AdminResetPwdSendEmailAsync(Guid userId, string pwdNotMd5 = "123456", string baseUrl = "")
{
var sysUserInfo = (await _identityUserRepository.Where(t => t.Id == userId).Include(t => t.UserRoleList).ThenInclude(c => c.UserTypeRole).FirstOrDefaultAsync()).IfNullThrowException();
@@ -393,7 +404,9 @@ namespace IRaCIS.Core.Application.Service
sysUserInfo.FullName,
sysUserInfo.UserName,
//string.Join(',', sysUserInfo.UserRoleList.Select(t => t.UserTypeRole.UserTypeShortName)),
pwdNotMd5
pwdNotMd5,
baseUrl
);
return (topicStr, htmlBodyStr);
@@ -533,8 +546,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.SiteUseOrExternalUserFirstrJoinTrial : EmailBusinessScenario.SiteUserOrExternalUserExistJoinTrial, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, null);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo, null);
}
@@ -593,8 +605,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.SiteUseOrExternalUserFirstrJoinTrial : EmailBusinessScenario.SiteUserOrExternalUserExistJoinTrial, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -723,8 +734,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.DoctorUserFirstJoinTrial : EmailBusinessScenario.DoctorUserExistJoinTrial, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, null);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo, null);
//创建账号 和创建角色 一条,更新的时候才记录更新角色
if (isNeedCreateNewUser == false)
@@ -750,6 +760,8 @@ namespace IRaCIS.Core.Application.Service
var isHaveTrialId = feedBack.TrialId != null;
var trialinfo = await _trialRepository.Where(x => x.Id == feedBack.TrialId).FirstOrDefaultAsync();
var companyName = _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var emialScenario = feedBack.VisitTaskId != null ? EmailBusinessScenario.IRImageError : (feedBack.SubjectVisitId != null ? EmailBusinessScenario.TrialSubjectVisitFeedBack : (feedBack.TrialId != null ? EmailBusinessScenario.TrialFeedBack : EmailBusinessScenario.SysFeedBack));
@@ -884,7 +896,15 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.SysFeedBack, messageToSend, emailConfigFunc);
}
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
if (trialinfo == null)
{
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
else
{
await SendEmailHelper.SendEmailAsync(messageToSend, trialinfo);
}
}
@@ -909,7 +929,7 @@ namespace IRaCIS.Core.Application.Service
{
var topicStr = string.Format(input.topicStr, companyName);
var htmlBodyStr = string.Format(ReplaceCompanyName(input.htmlBodyStr),
var htmlBodyStr = string.Format(ReplacePlatformName(ReplaceCompanyName(input.htmlBodyStr)) ,
sysUserInfo.FullName
);
@@ -926,7 +946,7 @@ namespace IRaCIS.Core.Application.Service
//中心调研核对人员提醒
public async Task SiteSuervyCheckUser(Guid trialId, string email, string name)
{
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.TrialCode, t.EmailFromName, t.ResearchProgramNo }).FirstOrDefaultAsync();
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).FirstOrDefaultAsync();
var messageToSend = new MimeMessage();
//发件地址
@@ -954,14 +974,14 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.SiteSurvey_CheckUser, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
public async Task SiteSuervyUpdateUser(Guid trialSiteId, string email, string name, string url)
{
var trialInfo = await _trialSiteRepository.Where(t => t.Id == trialSiteId).Select(t => new { t.Trial.TrialCode, t.Trial.EmailFromName, t.Trial.ResearchProgramNo, t.TrialSiteCode, t.TrialSiteName, t.TrialSiteAliasName }).FirstOrDefaultAsync();
var siteInfo = await _trialSiteRepository.Where(t => t.Id == trialSiteId).Include(x=>x.Trial).FirstOrDefaultAsync();
var trialInfo = siteInfo.Trial;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
@@ -980,8 +1000,8 @@ namespace IRaCIS.Core.Application.Service
var htmlBodyStr = string.Format(ReplaceCompanyName(input.htmlBodyStr),
name,
trialInfo.TrialCode,
trialInfo.TrialSiteCode,
trialInfo.TrialSiteName,
siteInfo.TrialSiteCode,
siteInfo.TrialSiteName,
url
);
@@ -990,8 +1010,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.SiteSurvey_UpdateUser, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -120,6 +120,10 @@ namespace IRaCIS.Core.Application.Service
CreateMap<TumorExportBaseModel, TR_Export>();
CreateMap<TumorExportBaseModel, RS_Export>();
CreateMap<TumorExportBaseModel, CO_Export>();
CreateMap<IVUS_OCTBaseDto, IvusExportDto>();
CreateMap<IVUS_OCTBaseDto, OctExportDto>();
}
}
@@ -183,6 +183,7 @@ namespace IRaCIS.Application.Contracts
public class SelectionReviewerDTO : DoctorDTO
{
public bool IsVacation { get; set; }
public int DoctorTrialState { get; set; }
public bool IsEnroll { get; set; } = false;
@@ -133,6 +133,29 @@ namespace IRaCIS.Core.Application.Service
}
/// <summary>
/// 维护 医生基本信息 Code
/// </summary>
/// <returns></returns>
public async Task ServiceDoctorBasicInfo()
{
var doctorList = await _doctorRepository.Where(t => t.Code == 0 && t.TrialId == null).ToListAsync();
var code = await _doctorRepository.Select(t => t.Code).DefaultIfEmpty().MaxAsync();
foreach (var doctorItem in doctorList)
{
code++;
doctorItem.Code = code;
doctorItem.ReviewerCode = AppSettings.GetCodeStr(doctorItem.Code, nameof(Doctor));
await _doctorRepository.BatchUpdateNoTrackingAsync(x => x.Id == doctorItem.Id, x => new Doctor()
{
Code = doctorItem.Code,
ReviewerCode = doctorItem.ReviewerCode
});
}
}
/// <summary>
/// 新增修改 医生基本信息和工作
/// </summary>
@@ -131,6 +131,7 @@ namespace IRaCIS.Core.Application.Service
#region
CreateMap<Doctor, SelectionReviewerDTO>()
.ForMember(d => d.IsVacation, u => u.MapFrom(t => t.VacationList.Any(x=>x.StartDate<DateTime.Now&&x.EndDate>DateTime.Now)))
.ForMember(d => d.DoctorUserName, u => u.MapFrom(t => t.UserRole.IdentityUser.UserName));
@@ -92,11 +92,16 @@ namespace IRaCIS.Core.Application.Contracts
public class UnionDocumentWithConfirmInfoView : UnionDocumentView
{
public DocUserSignType SysDocUserSignType { get; set; }
public bool IsConfirmIdentityUserInner { get; set; }
public Guid TrialId { get; set; }
public bool IsNeedSendEmial { get; set; }
public DateTime UserCreateTime { get; set; }
public string EmailFromName { get; set; }
public DateTime? ConfirmTime { get; set; }
@@ -30,6 +30,7 @@ namespace IRaCIS.Core.Application.ViewModel
public new List<UserTypeEnum> CopyUserTypeList => TrialEmailNoticeUserList.Where(t => t.EmailUserType == EmailUserType.Copy).Select(t => t.UserType).ToList();
public List<CriterionType>? SysCriterionTypeList { get; set; }
}
@@ -97,6 +98,8 @@ namespace IRaCIS.Core.Application.ViewModel
[NotDefault]
public Guid TrialId { get; set; }
public string EmailTopic { get; set; } = string.Empty;
public EmailBusinessScenario? BusinessScenarioEnum { get; set; }
public CriterionType? CriterionTypeEnum { get; set; }
@@ -125,7 +128,7 @@ namespace IRaCIS.Core.Application.ViewModel
{
public Guid SubjectId { get; set; }
public Guid TrialReadingCriterionId { get; set; }
public CriterionType CriterionType { get; set; }
public EmailBusinessScenario BusinessScenarioEnum { get; set; }
}
@@ -110,8 +110,8 @@ namespace IRaCIS.Core.Application.Service
if (sendEmailConfig != null)
{
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).FirstOrDefaultAsync();
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
}
}
@@ -19,6 +19,8 @@ namespace IRaCIS.Core.Application.Contracts
Task<IResponseOutput> UserConfirm(UserConfirmCommand userConfirmCommand);
Task<List<TrialUserDto>> GetTrialUserSelect(Guid trialId);
Task<IResponseOutput<PageOutput<UnionDocumentWithConfirmInfoView>>> GetSysDocumentConfirmList(SystemDocQuery inQuery);
//Task<PageOutput<DocumentUnionWithUserStatView>> GetTrialSystemDocumentList(DocumentTrialUnionQuery querySystemDocument);
//List<TrialUserUnionDocumentView> GetTrialUserDocumentList(Guid trialId);
@@ -208,6 +208,7 @@ namespace IRaCIS.Core.Application.Services
await _systemDocumentRepository.BatchUpdateNoTrackingAsync(x => inDto.Ids.Contains(x.Id), x => new SystemDocument()
{
IsPublish = true,
PublishDate= DateTime.Now,
IsDeleted = false,
});
@@ -286,7 +287,7 @@ namespace IRaCIS.Core.Application.Services
var isInternal = _userInfo.IsZhiZhun;
var query = from sysDoc in _systemDocumentRepository.AsQueryable(true)
var query = from sysDoc in _systemDocumentRepository.Where(t=>t.IsPublish)
.Where(t => t.NeedConfirmedUserTypeList.Any(t => t.NeedConfirmUserTypeId == _userInfo.UserTypeId))
.WhereIf(!string.IsNullOrEmpty(inQuery.Name), t => t.Name.Contains(inQuery.Name))
//外部人员 只签署 外部需要签署的
@@ -301,6 +302,7 @@ namespace IRaCIS.Core.Application.Services
{
AttachmentCount=sysDoc.SystemDocumentAttachmentList.Where(z=>!z.OffLine).Count(),
IsSystemDoc = true,
IsPublish=sysDoc.IsPublish,
CurrentStaffTrainDays=sysDoc.CurrentStaffTrainDays,
NewStaffTrainDays = sysDoc.NewStaffTrainDays,
Id = sysDoc.Id,
@@ -326,6 +328,7 @@ namespace IRaCIS.Core.Application.Services
//UserTypeShortName = user.UserTypeRole.UserTypeShortName
};
var list = await query
//过滤掉删除的,并且没有签名的
.Where(t => !(t.IsDeleted == true && t.ConfirmTime == null))
@@ -54,9 +54,9 @@ namespace IRaCIS.Core.Application.Services
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);
.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);
@@ -101,8 +101,9 @@ namespace IRaCIS.Core.Application.Services
await _trialDocumentRepository.UpdatePartialFromQueryAsync(x => inDto.Ids.Contains(x.Id), x => new TrialDocument()
{
IsPublish = true,
PublishDate = DateTime.Now,
IsDeleted = false,
});
}, false, true);
await _trialDocumentRepository.SaveChangesAsync();
Console.WriteLine("开始 发布项目文档");
@@ -122,7 +123,7 @@ namespace IRaCIS.Core.Application.Services
}
/// <summary>
/// 测试定时发送
/// 测试邮件定时发送
/// </summary>
/// <returns></returns>
public async Task<IResponseOutput> TestPush()
@@ -140,6 +141,22 @@ namespace IRaCIS.Core.Application.Services
return ResponseOutput.Result(true);
}
public async Task<IResponseOutput> TestSendEmail()
{
Task.Run(async () =>
{
// 创建独立作用域
using (var scope = serviceScopeFactory.CreateScope())
{
// 从新作用域解析服务
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new ImageQCRecurringEvent { TrialId = Guid.Parse("08de2254-5d7d-581a-0242-0a0001000000") });
}
});
return ResponseOutput.Result(true);
}
/// <summary>
/// Setting 界面的 项目所有文档列表
/// </summary>
@@ -155,7 +172,7 @@ namespace IRaCIS.Core.Application.Services
.WhereIf(inQuery.UserTypeId != null, t => t.NeedConfirmedUserTypeList.Any(t => t.NeedConfirmUserTypeId == inQuery.UserTypeId))
.WhereIf(inQuery.IsDeleted != null, t => t.IsDeleted == inQuery.IsDeleted)
.WhereIf(inQuery.IsPublish != null, t => t.IsPublish == inQuery.IsPublish)
.WhereIf(!string.IsNullOrEmpty(inQuery.FileTypeCode), t => t.FileType.Code== inQuery.FileTypeCode)
.WhereIf(!string.IsNullOrEmpty(inQuery.FileTypeCode), t => t.FileType.Code == inQuery.FileTypeCode)
.ProjectTo<TrialDocumentView>(_mapper.ConfigurationProvider, new { token = _userInfo.UserToken, isEn_Us = _userInfo.IsEn_Us });
return await trialDocumentQueryable.ToPagedListAsync(inQuery);
@@ -164,7 +181,7 @@ namespace IRaCIS.Core.Application.Services
[HttpPost]
public async Task<PageOutput<TrialSignDocView>> GetTrialSignDocumentList(TrialDocQuery inQuery)
{
var trialDocQueryable = from trialDoc in _trialDocumentRepository.AsQueryable(true)
var trialDocQueryable = from trialDoc in _trialDocumentRepository.Where(t => t.IsPublish)
.WhereIf(inQuery.TrialId != null, t => t.TrialId == inQuery.TrialId)
.Where(t => t.NeedConfirmedUserTypeList.Any(t => t.NeedConfirmUserTypeId == _userInfo.UserTypeId))
@@ -337,7 +354,7 @@ namespace IRaCIS.Core.Application.Services
#region
var systemDocQuery =
from sysDoc in _systemDocumentRepository.AsQueryable(true).Where(t => t.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == _userInfo.UserTypeId))
from sysDoc in _systemDocumentRepository.Where(t => t.IsPublish).Where(t => t.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == _userInfo.UserTypeId))
//外部人员 只签署 外部需要签署的
.WhereIf(isInternal == false, t => t.DocUserSignType == DocUserSignType.InnerAndOuter)
from trialUser in _trialIdentityUserRepository.AsQueryable(false)
@@ -376,7 +393,7 @@ namespace IRaCIS.Core.Application.Services
//项目文档查询
var trialDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(true).Where(t => t.TrialId == inQuery.TrialId).Where(t => t.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == _userInfo.UserTypeId))
from trialDoc in _trialDocumentRepository.Where(t => t.IsPublish).Where(t => t.TrialId == inQuery.TrialId).Where(t => t.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == _userInfo.UserTypeId))
from trialUser in _trialIdentityUserRepository.AsQueryable(false).Where(t => t.TrialId == inQuery.TrialId && t.IdentityUserId == _userInfo.IdentityUserId
&& t.TrialUserRoleList.Any(t => trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == t.UserRole.UserTypeId)))
@@ -566,13 +583,14 @@ namespace IRaCIS.Core.Application.Services
#endregion
var needSignTrialDocCount = await _trialDocumentRepository.AsQueryable(true).Where(t => t.TrialId == inQuery.TrialId && t.Trial.TrialStatusStr != StaticData.TrialState.TrialStopped)
var needSignTrialDocCount = await _trialDocumentRepository.AsQueryable(true).Where(t => t.IsPublish)
.Where(t => t.TrialId == inQuery.TrialId && t.Trial.TrialStatusStr != StaticData.TrialState.TrialStopped)
.Where(t => t.Trial.TrialIdentityUserList.Any(t => t.IdentityUserId == _userInfo.IdentityUserId && t.TrialUserRoleList.Any(t => t.UserRole.UserTypeId == _userInfo.UserTypeId)))
.Where(t => t.IsDeleted == false && !t.TrialDocConfirmedUserList.Any(t => t.ConfirmUserId == _userInfo.IdentityUserId && t.ConfirmTime != null) && t.NeedConfirmedUserTypeList.Any(u => u.NeedConfirmUserTypeId == _userInfo.UserTypeId))
.CountAsync();
var needSignSystemDocCount = await _systemDocumentRepository.AsQueryable(true)
var needSignSystemDocCount = await _systemDocumentRepository.AsQueryable(true).Where(t => t.IsPublish)
.WhereIf(isInternal == false, t => t.DocUserSignType == DocUserSignType.InnerAndOuter)
.Where(t => t.IsDeleted == false && !t.SystemDocConfirmedUserList.Any(t => t.ConfirmUserId == _userInfo.IdentityUserId && t.ConfirmTime != null) && t.NeedConfirmedUserTypeList.Any(u => u.NeedConfirmUserTypeId == _userInfo.UserTypeId))
.CountAsync();
@@ -605,7 +623,7 @@ namespace IRaCIS.Core.Application.Services
var trialInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId, ignoreQueryFilters: true).Select(t => new { t.TrialFinishedTime, t.TrialStatusStr }).FirstNotNullAsync());
var trialDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(t => t.TrialId == inQuery.TrialId)
from trialDoc in _trialDocumentRepository.Where(t => t.TrialId == inQuery.TrialId && t.IsPublish)
.Where(t => inQuery.UserTypeId != null ? t.NeedConfirmedUserTypeList.Any(t => t.NeedConfirmUserTypeId == inQuery.UserTypeId) : true)
from trialUser in _trialIdentityUserRepository.AsQueryable(false).Where(t => t.TrialId == inQuery.TrialId
&& t.TrialUserRoleList.AsQueryable().Any(t => trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == t.UserRole.UserTypeId))
@@ -696,7 +714,8 @@ namespace IRaCIS.Core.Application.Services
.WhereIf(inQuery.BeginCreateTime != null, t => t.CreateTime >= inQuery.BeginCreateTime)
.WhereIf(inQuery.EndCreateTime != null, t => t.CreateTime <= inQuery.EndCreateTime)
.WhereIf(!string.IsNullOrEmpty(inQuery.UserName), t => t.UserName.Contains(inQuery.UserName))
.WhereIf(inQuery.IsDeleted != null, t => t.IsDeleted == inQuery.IsDeleted);
.WhereIf(inQuery.IsDeleted != null, t => t.IsDeleted == inQuery.IsDeleted)
.WhereIf(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.EA, t => t.ConfirmTime != null);
var result = await unionQuery.ToPagedListAsync(inQuery);
@@ -906,6 +925,12 @@ namespace IRaCIS.Core.Application.Services
//EA 只能查看内部人员文档
var isEA = _userInfo.UserTypeEnumInt == (int)UserTypeEnum.EA;
//EA 但是没有在进行的培训记录查看权限,那么返回空数据
if (isEA && !_auditRecordRepository.Any(t => t.IsViewTrainingRecord && t.AuditState == AuditState.Ongoing && t.AuditRecordIdentityUserList.Any(c => c.IdentityUserId == _userInfo.IdentityUserId)))
{
return ResponseOutput.Ok();
}
var list = _systemDocConfirmedUserRepository.Where(t => t.ConfirmTime != null)
.WhereIf(isEA, t => t.ConfirmUser.IsZhiZhun == true)
.Select(t => new { t.ConfirmUserId, t.ConfirmUser.UserName, t.ConfirmUser.FullName }).Distinct().ToList();
@@ -922,15 +947,18 @@ namespace IRaCIS.Core.Application.Services
var isEA = _userInfo.UserTypeEnumInt == (int)UserTypeEnum.EA;
//EA 但是没有在进行的培训记录查看权限,那么返回空数据
if (isEA && !_auditRecordRepository.Any(t => t.IsViewTrainingRecord && t.AuditState == AuditState.Ongoing))
if (isEA && !_auditRecordRepository.Any(t => t.IsViewTrainingRecord && t.AuditState == AuditState.Ongoing && t.AuditRecordIdentityUserList.Any(c => c.IdentityUserId == _userInfo.IdentityUserId)))
{
return ResponseOutput.Ok(new PageOutput<UnionDocumentWithConfirmInfoView>());
}
var systemDocQuery =
from sysDoc in _systemDocumentRepository.AsQueryable(false)
from sysDoc in _systemDocumentRepository.Where(t => t.IsPublish)
.Where(t => inQuery.UserTypeId != null ? t.NeedConfirmedUserTypeList.Any(t => t.NeedConfirmUserTypeId == inQuery.UserTypeId) : true)
from identityUser in _identityUserRepository.AsQueryable(false).Where(t => t.Status == UserStateEnum.Enable && t.UserRoleList.Where(t => t.IsUserRoleDisabled == false).Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
from identityUser in _identityUserRepository.AsQueryable(false)
.Where(t => t.Status == UserStateEnum.Enable && t.UserRoleList.Where(t => t.IsUserRoleDisabled == false).Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
.Where(t => inQuery.UserId != null ? t.Id == inQuery.UserId : true)
.Where(t => inQuery.UserTypeId != null ? t.UserRoleList.Any(t => t.UserTypeId == inQuery.UserTypeId && t.IsUserRoleDisabled == false) : true)
.Where(t => isEA ? t.IsZhiZhun == true : true) //EA 只能查看内部人员文档
@@ -939,6 +967,9 @@ namespace IRaCIS.Core.Application.Services
select new UnionDocumentWithConfirmInfoView()
{
IsSystemDoc = true,
SysDocUserSignType = sysDoc.DocUserSignType,
IsConfirmIdentityUserInner = identityUser.IsZhiZhun,
IsPublish=sysDoc.IsPublish,
Id = sysDoc.Id,
CreateTime = sysDoc.CreateTime,
IsDeleted = sysDoc.IsDeleted,
@@ -970,14 +1001,16 @@ namespace IRaCIS.Core.Application.Services
};
var unionQuery = systemDocQuery.IgnoreQueryFilters().Where(t => !(t.IsDeleted == true && t.ConfirmTime == null))
//外部人员 只签署 外部需要签署的
.Where(t => t.IsConfirmIdentityUserInner == false ? t.SysDocUserSignType == DocUserSignType.InnerAndOuter : true)
.WhereIf(!string.IsNullOrEmpty(inQuery.Name), t => t.Name.Contains(inQuery.Name))
.WhereIf(inQuery.FileTypeId != null, t => t.FileTypeId == inQuery.FileTypeId)
.WhereIf(inQuery.IsConfirmed == true, t => t.ConfirmTime != null)
.WhereIf(inQuery.IsConfirmed == false, t => t.ConfirmTime == null)
.WhereIf(inQuery.StartConfirmTime != null, t => t.ConfirmTime >= inQuery.StartConfirmTime.Value)
.WhereIf(inQuery.EndConfirmTime != null, t => t.ConfirmTime <= inQuery.EndConfirmTime.Value)
.WhereIf(inQuery.BeginCreateTime != null, t => t.CreateTime >= inQuery.BeginCreateTime)
.WhereIf(inQuery.EndCreateTime != null, t => t.CreateTime <= inQuery.EndCreateTime)
.WhereIf(inQuery.BeginCreateTime != null, t => t.CreateTime >= inQuery.BeginCreateTime)
.WhereIf(inQuery.EndCreateTime != null, t => t.CreateTime <= inQuery.EndCreateTime)
.WhereIf(!string.IsNullOrEmpty(inQuery.UserName), t => t.UserName.Contains(inQuery.UserName))
.WhereIf(inQuery.IsDeleted != null, t => t.IsDeleted == inQuery.IsDeleted)
.WhereIf(isInternal == false, t => t.ConfirmTime != null); //不是内部的人,看有签名时间的
@@ -1069,6 +1102,7 @@ namespace IRaCIS.Core.Application.Services
return ResponseOutput.NotOk(_localizer["TrialD_DuplicateFileInProject"]);
}
entity.IsDeleted = true;
//entity.Id = NewId.NextGuid();
await _trialDocumentRepository.AddAsync(entity, true);
return ResponseOutput.Ok(entity.Id.ToString());
@@ -1081,7 +1115,7 @@ namespace IRaCIS.Core.Application.Services
return ResponseOutput.NotOk(_localizer["TrialD_DuplicateFileInProject"]);
}
var document = (await _trialDocumentRepository.Where(t => t.Id == addOrEditTrialDocument.Id, true).Include(t => t.NeedConfirmedUserTypeList).FirstOrDefaultAsync()).IfNullThrowException();
var document = (await _trialDocumentRepository.Where(t => t.Id == addOrEditTrialDocument.Id, true,true).Include(t => t.NeedConfirmedUserTypeList).FirstOrDefaultAsync()).IfNullThrowException();
bool beforeIsPublish = document.IsPublish;
bool beforeIsDeleted = document.IsDeleted;
@@ -1089,6 +1123,7 @@ namespace IRaCIS.Core.Application.Services
_mapper.Map(addOrEditTrialDocument, document);
document.UpdateTime = DateTime.Now;
#region
//if (document.FileTypeId != addOrEditTrialDocument.FileTypeId)
@@ -13,6 +13,7 @@ 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.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Domain.Share.Common;
using IRaCIS.Core.Infra.EFCore.Common;
@@ -617,8 +618,8 @@ namespace IRaCIS.Core.Application.Service
FileStream = File.OpenRead(phyPath),
});
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
var trialInfo = await _trialRepository.Where(t => t.Id == taskInfo.TrialId).FirstOrDefaultAsync();
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
return string.Empty;
}
@@ -733,8 +734,8 @@ namespace IRaCIS.Core.Application.Service
FileStream = pdfMemoryStream
});
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
var trialInfo = await _trialRepository.Where(t => t.Id == taskInfo.TrialId).FirstOrDefaultAsync();
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
}
@@ -772,10 +773,12 @@ namespace IRaCIS.Core.Application.Service
{
var subjectId = generateEmailCommand.SubjectId;
var businessScenarioEnum = generateEmailCommand.BusinessScenarioEnum;
var trialReadingCriterionId = generateEmailCommand.TrialReadingCriterionId;
var criterionType = generateEmailCommand.CriterionType;
var trialConfig = await _subjectRepository.Where(t => t.Id == subjectId).Select(t => new { t.Trial.IsEnrollementQualificationConfirm, t.Trial.IsPDProgressView }).FirstNotNullAsync();
var trialConfig = await _subjectRepository.Where(t => t.Id == subjectId).Select(t => new { t.Trial.IsEnrollementQualificationConfirm, t.Trial.IsPDProgressView, t.TrialId }).FirstNotNullAsync();
var trialReadingCriterionId = _readingQuestionCriterionTrialRepository.Where(t => t.CriterionType == criterionType && t.TrialId == trialConfig.TrialId).Select(t => t.Id).FirstOrDefault();
//找到入组确认 或者Pd 进展 已生成任务的 访视
var subjectVisitList = await _subjectVisitRepository.Where(t => t.SubjectId == subjectId & t.CheckState == CheckStateEnum.CVPassed && (t.IsEnrollmentConfirm == true || t.PDState == PDStateEnum.PDProgress)).ToListAsync();
@@ -1670,9 +1673,10 @@ x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.LesionNumber && x.Readi
{
//await SyncSystemEmainCofigDocListAsync(inQuery.TrialId);
var trialConfig = _trialRepository.Where(t => t.Id == inQuery.TrialId).Select(t => new { t.IsEnrollementQualificationConfirm, t.IsPDProgressView }).First();
var trialConfig = _trialRepository.Where(t => t.Id == inQuery.TrialId).Select(t => new { t.IsEnrollementQualificationConfirm, t.IsPDProgressView, TrialCriterionTypeList = t.TrialReadingCriterionList.Where(t => t.IsSigned).Select(t => t.CriterionType).ToList() }).First();
var trialEmailNoticeConfigQueryable = _trialEmailNoticeConfigRepository.Where(t => t.TrialId == inQuery.TrialId)
.WhereIf(inQuery.EmailTopic.IsNotNullOrEmpty(), t => t.EmailTopic.Contains(inQuery.EmailTopic) || t.EmailTopicCN.Contains(inQuery.EmailTopic))
.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))
@@ -1690,6 +1694,7 @@ x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.LesionNumber && x.Readi
var orderQuery = inQuery.Asc ? trialEmailNoticeConfigQueryable.OrderBy(sortField) : trialEmailNoticeConfigQueryable.OrderBy(sortField + " desc");
var list = await orderQuery.ToListAsync();
return ResponseOutput.Ok(list, trialConfig);
}
@@ -1763,22 +1768,22 @@ x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.LesionNumber && x.Readi
await _trialEmailNoticeConfigRepository.SaveChangesAsync();
var cronInfo = await _trialEmailNoticeConfigRepository.Where(t => t.Id == addOrEditTrialEmailNoticeConfig.Id)
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
.FirstAsync();
// var cronInfo = await _trialEmailNoticeConfigRepository.Where(t => t.Id == addOrEditTrialEmailNoticeConfig.Id)
//.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
//.FirstAsync();
var jobId = $"{cronInfo.TrialId}({cronInfo.TrialCode})_({cronInfo.BusinessScenarioEnum})";
// var jobId = $"{cronInfo.TrialId}({cronInfo.TrialCode})_({cronInfo.BusinessScenarioEnum})";
if (addOrEditTrialEmailNoticeConfig.IsAutoSend)
{
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, addOrEditTrialEmailNoticeConfig.TrialId, addOrEditTrialEmailNoticeConfig.BusinessScenarioEnum, addOrEditTrialEmailNoticeConfig.EmailCron);
// if (addOrEditTrialEmailNoticeConfig.IsAutoSend)
// {
// HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, addOrEditTrialEmailNoticeConfig.TrialId, addOrEditTrialEmailNoticeConfig.BusinessScenarioEnum, addOrEditTrialEmailNoticeConfig.EmailCron);
}
else
{
HangfireJobHelper.RemoveCronJob(jobId);
}
// }
// else
// {
// HangfireJobHelper.RemoveCronJob(jobId);
// }
return ResponseOutput.Ok();
}
@@ -117,4 +117,10 @@ namespace IRaCIS.Core.Application.Contracts.Dicom.DTO
public bool HasLabel { get; set; } = false;
public bool KeySeries { get; set; } = false;
}
public class UpdateImageResizeDTO
{
public Guid SeriesId { get; set; }
public string ImageResizePath { get; set; }
}
}
@@ -134,6 +134,8 @@ namespace IRaCIS.Core.Application.Contracts
public string Bodypart { get; set; } = string.Empty;
public string BodyPartForEditOther { get; set; } = string.Empty;
public DateTime? StudyTime { get; set; }
@@ -269,8 +271,7 @@ namespace IRaCIS.Core.Application.Contracts
public AddOrUpdateStudyDto Study { get; set; }
[NotDefault]
public Guid VisitTaskId { get; set; }
public Guid? VisitTaskId { get; set; }
public bool? IsImageSegmentLabel { get; set; }
}
@@ -506,8 +507,9 @@ namespace IRaCIS.Core.Application.Contracts
public class IRTaskUploadedDicomStudyQuery
{
[NotDefault]
public Guid VisitTaskId { get; set; }
public Guid? VisitTaskId { get; set; }
public Guid? SubjectVisitId { get; set; }
}
public class IRUploadTaskDicomStudyDto : DicomStudyBasicInfo
@@ -981,7 +983,9 @@ namespace IRaCIS.Core.Application.Contracts
public int? VisitCount { get; set; }
public int? MarkVisitCount { get; set; }
public int? MarkIVUSVisitCount { get; set; }
public int? MarkOCTVisitCount { get; set; }
public int? DicomStudyCount { get; set; }
@@ -1027,7 +1031,8 @@ namespace IRaCIS.Core.Application.Contracts
public Guid? SourceSubjectVisitId => SubjectVisitId;
//防止前端null 故意返回
public Guid VisitTaskId { get; set; }
public List<StudyBasicInfo> OrginalStudyList { get; set; }
@@ -53,6 +53,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
IDistributedLockProvider _distributedLockProvider,
IRepository<TrialImageDownload> _trialImageDownloadRepository,
IRepository<Subject> _subjectRepository,
IRepository<DicomInstance> _instanceRepository,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IFusionCache _fusionCache) : BaseService, IDownloadAndUploadService
{
@@ -125,7 +126,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
//要根据标准阅片顺序,确定是否查询单个任务的,还是查询所有的
var criterionInfo = await _readingQuestionCriterionTrialRepository.Where(t => t.Id == inQuery.TrialReadingCriterionId)
.Select(t => new { t.IsReadingTaskViewInOrder, t.Trial.Modalitys }).FirstNotNullAsync();
.Select(t => new { t.IsReadingTaskViewInOrder, t.Trial.Modalitys, t.IsImageFilter, t.CriterionModalitys }).FirstNotNullAsync();
var subjectCode = string.Empty;
var subjectId = inQuery.SubjectId;
@@ -159,7 +160,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
if (inQuery.IsImageSegmentLabel == true)
{
var querySV = _subjectVisitRepository.WhereIf(inQuery.SubjectVisitId != null, t => t.Id == inQuery.SubjectVisitId)
var querySV = _subjectVisitRepository.Where(t => t.CheckState == CheckStateEnum.CVPassed)
.WhereIf(inQuery.SubjectVisitId != null, t => t.Id == inQuery.SubjectVisitId)
.WhereIf(inQuery.SubjectId != null, t => t.SubjectId == inQuery.SubjectId)
.Select(u => new SubjectVisitMarkUploadDto()
{
@@ -174,6 +176,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
.Select(t => new StudyBasicInfo()
{
Id = t.Id,
SubjectVisitId = t.SubjectVisitId,
StudyInstanceUid = t.StudyInstanceUid,
ModalityForEdit = t.ModalityForEdit,
BodyPartExamined = t.BodyPartExamined,
@@ -194,6 +198,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
UploadStudyList = u.TaskStudyList.Select(t => new StudyBasicInfo()
{
Id = t.Id,
SubjectVisitId = t.SubjectVisitId,
StudyInstanceUid = t.StudyInstanceUid,
ModalityForEdit = t.ModalityForEdit,
BodyPartExamined = t.BodyPartExamined,
@@ -227,7 +232,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var query = _visitTaskRepository.Where(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == inQuery.TrialReadingCriterionId && t.SourceSubjectVisitId != null
var query = _visitTaskRepository.Where(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == inQuery.TrialReadingCriterionId && t.SourceSubjectVisit.CheckState == CheckStateEnum.CVPassed
&& t.DoctorUserId == _userInfo.UserRoleId && t.TaskState == TaskState.Effect)
//满足 有序,或者随机只看到当前任务的dicom 非dicom检查
.WhereIf(criterionInfo.IsReadingTaskViewInOrder != ReadingOrder.SubjectRandom && inQuery.VisitTaskId != null, t => t.Id == inQuery.VisitTaskId)
@@ -252,7 +257,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
OrginalStudyList = u.SourceSubjectVisit.StudyList
.Where(t => u.TrialReadingCriterion.IsImageFilter ? ("|" + u.TrialReadingCriterion.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
.Where(t => inQuery.IsImageSegmentLabel == false ? t.ModalityForEdit == "XA" : true)
.Where(t => inQuery.IsImageSegmentLabel == false ? t.ModalityForEdit == "XA" || t.ModalityForEdit == "DSA" || t.ModalityForEdit == "OCT" : true)
.Select(t => new StudyBasicInfo()
{
Id = t.Id,
@@ -326,7 +331,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
{
foreach (var item in list)
{
item.OrginalStudyList.AddRange(item.OriginalTaskStudyList);
item.OrginalStudyList.AddRange(item.OriginalTaskStudyList.WhereIf(criterionInfo.IsImageFilter, t => ("|" + criterionInfo.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|")));
}
}
@@ -548,9 +553,11 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var @uploadLock = _distributedLockProvider.CreateLock($"UploadTaskDicom");
var visiTaskId = incommand.VisitTaskId ?? Guid.Empty;
using (await @uploadLock.AcquireAsync())
{
if (_fusionCache.GetOrDefault<Guid>(CacheKeys.TrialTaskStudyUidDBLock(incommand.TrialId, incommand.VisitTaskId, incommand.Study.StudyInstanceUid)) != Guid.Empty)
if (_fusionCache.GetOrDefault<Guid>(CacheKeys.TrialTaskStudyUidDBLock(incommand.TrialId, visiTaskId, incommand.Study.StudyInstanceUid)) != Guid.Empty)
{
//---当前已有人正在上传和归档该检查!
return ResponseOutput.NotOk(I18n.T("UploadDownLoad_ArchiveInProgress"));
@@ -558,7 +565,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
else
{
//在事务未完成前 防止前端重复提交
await _fusionCache.SetAsync(CacheKeys.TrialTaskStudyUidDBLock(incommand.TrialId, incommand.VisitTaskId, incommand.Study.StudyInstanceUid), _userInfo.UserRoleId, TimeSpan.FromMinutes(1));
await _fusionCache.SetAsync(CacheKeys.TrialTaskStudyUidDBLock(incommand.TrialId, visiTaskId, incommand.Study.StudyInstanceUid), _userInfo.UserRoleId, TimeSpan.FromMinutes(1));
}
}
@@ -570,7 +577,6 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
svId = _dicomStudyRepository.Where(t => t.TrialId == incommand.TrialId && t.StudyInstanceUid == incommand.Study.StudyInstanceUid).Select(t => t.SubjectVisitId).FirstOrDefault();
}
var visiTaskId = incommand.VisitTaskId;
var modalitys = string.Empty;
@@ -805,8 +811,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
}
finally
{
await _fusionCache.RemoveAsync(CacheKeys.TrialTaskStudyUidDBLock(incommand.TrialId, incommand.VisitTaskId, incommand.Study.StudyInstanceUid));
await _fusionCache.RemoveAsync(CacheKeys.TrialTaskStudyUidUploading(incommand.TrialId, incommand.VisitTaskId, incommand.Study.StudyInstanceUid));
await _fusionCache.RemoveAsync(CacheKeys.TrialTaskStudyUidDBLock(incommand.TrialId, visiTaskId, incommand.Study.StudyInstanceUid));
await _fusionCache.RemoveAsync(CacheKeys.TrialTaskStudyUidUploading(incommand.TrialId, visiTaskId, incommand.Study.StudyInstanceUid));
}
@@ -820,39 +826,68 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
[HttpDelete]
public async Task<IResponseOutput> DeleteTaskStudy(Guid visitTaskId, bool isDicom, Guid? dicomStudyId, Guid? noneDicomStudyId)
public async Task<IResponseOutput> DeleteTaskStudy(Guid visitTaskId, Guid? subjectVisitId, bool isDicom, Guid? dicomStudyId, Guid? noneDicomStudyId)
{
if (isDicom)
{
if (dicomStudyId == null)
//靶段标注删除访视影像
if (visitTaskId == Guid.Empty && subjectVisitId != null && subjectVisitId != Guid.Empty)
{
var deleteStudyPathList = await _taskInstanceRepository.Where(t => t.VisitTaskId == visitTaskId).GroupBy(t => t.StudyId).Select(g => g.First().Path).ToListAsync();
foreach (var fisrtPath in deleteStudyPathList)
{
var prefix = fisrtPath.Substring(1, fisrtPath.LastIndexOf('/') - 1);
await _oSSService.DeleteFromPrefix(prefix, true);
}
await _taskStudyRepository.DeleteFromQueryAsync(t => t.VisitTaskId == visitTaskId);
await _taskSeriesRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId);
await _taskInstanceRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId);
}
else
{
var fisrtPath = await _taskInstanceRepository.Where(t => t.VisitTaskId == visitTaskId && t.StudyId == dicomStudyId).Select(t => t.Path).FirstOrDefaultAsync();
var fisrtPath = await _taskInstanceRepository.Where(t => t.SubjectVisitId == subjectVisitId).Select(t => t.Path).FirstOrDefaultAsync();
var prefix = fisrtPath.Substring(1, fisrtPath.LastIndexOf('/') - 1);
await _oSSService.DeleteFromPrefix(prefix, true);
await _taskStudyRepository.DeleteFromQueryAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId);
await _taskSeriesRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId);
await _taskInstanceRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId);
await _taskStudyRepository.DeleteFromQueryAsync(t => t.SubjectVisitId == subjectVisitId);
await _taskSeriesRepository.BatchDeleteNoTrackingAsync(t => t.SubjectVisitId == subjectVisitId);
await _taskInstanceRepository.BatchDeleteNoTrackingAsync(t => t.SubjectVisitId == subjectVisitId);
}
if (visitTaskId != Guid.Empty)
{
if (dicomStudyId == null)
{
var deleteStudyPathList = await _taskInstanceRepository.Where(t => t.VisitTaskId == visitTaskId).GroupBy(t => t.StudyId).Select(g => g.First().Path).ToListAsync();
foreach (var fisrtPath in deleteStudyPathList)
{
var prefix = fisrtPath.Substring(1, fisrtPath.LastIndexOf('/') - 1);
await _oSSService.DeleteFromPrefix(prefix, true);
}
await _taskStudyRepository.DeleteFromQueryAsync(t => t.VisitTaskId == visitTaskId);
await _taskSeriesRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId);
await _taskInstanceRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId);
}
else
{
var fisrtPath = await _taskInstanceRepository.Where(t => t.VisitTaskId == visitTaskId && t.StudyId == dicomStudyId).Select(t => t.Path).FirstOrDefaultAsync();
var prefix = fisrtPath.Substring(1, fisrtPath.LastIndexOf('/') - 1);
await _oSSService.DeleteFromPrefix(prefix, true);
await _taskStudyRepository.DeleteFromQueryAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId);
await _taskSeriesRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId);
await _taskInstanceRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId);
}
}
}
else
{
await _noneDicomStudyFileReposiotry.DeleteFromQueryAsync(t => t.VisitTaskId == visitTaskId && t.OriginNoneDicomStudyId == noneDicomStudyId);
if (visitTaskId == Guid.Empty)
{
//删除靶段标注非dicom
await _noneDicomStudyFileReposiotry.DeleteFromQueryAsync(t => t.ImageLabelNoneDicomStudyId == noneDicomStudyId);
}
else
{
//删除后处理非dicom
await _noneDicomStudyFileReposiotry.DeleteFromQueryAsync(t => t.VisitTaskId == visitTaskId && t.OriginNoneDicomStudyId == noneDicomStudyId);
}
}
await _noneDicomStudyFileReposiotry.SaveChangesAsync();
@@ -868,7 +903,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<List<TaskNoneDicomStudyDTO>> GetIRUploadTaskNoneDicomStudyList(IRUploadStudyQuery inQuery)
public async Task<IResponseOutput<List<TaskNoneDicomStudyDTO>>> GetIRUploadTaskNoneDicomStudyList([FromServices] INoneDicomStudyService _noneDicomStudyService, IRUploadStudyQuery inQuery)
{
var subjectCode = inQuery.SubjectCode;
var subjectId = inQuery.SubjectId;
@@ -893,12 +928,71 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
}
var info = await _readingQuestionCriterionTrialRepository.Where(t => t.Id == inQuery.TrialReadingCriterionId)
.Select(t => new { t.IsImageFilter, t.CriterionModalitys, t.IsReadingTaskViewInOrder }).FirstNotNullAsync();
.Select(t => new { t.IsImageFilter, t.CriterionType, t.TrialId, t.CriterionModalitys, t.IsReadingTaskViewInOrder }).FirstNotNullAsync();
var config = await _subjectRepository.Where(t => t.Id == subjectId).Select(t => new { t.Trial.ImageFormatList, t.Trial.StudyNameList, t.Trial.IsShowStudyName }).FirstOrDefaultAsync();
//靶段标注上传,查看访视级别,上传绑定访视级别
if (inQuery.IsImageSegmentLabel == true)
{
var list = _noneDicomStudyReposiotry.Where(t => t.SubjectId == subjectId && t.SubjectVisitId == inQuery.SubjectVisitId)
//存在ivus 和oct 这两种的项目
if (_readingQuestionCriterionTrialRepository.Where(t => t.TrialId == info.TrialId &&
(t.CriterionType == CriterionType.IVUS || t.CriterionType == CriterionType.OCT)).Distinct().Count() == 2
&& !_noneDicomStudyReposiotry.Any(t => t.SubjectId == inQuery.SubjectId && t.Modality == "IVUS"))
{
#region ivus dicom检查
var addList = await _dicomStudyRepository.Where(t => t.SubjectId == inQuery.SubjectId)
.Select(t => new NoneDicomStudyAddOrEdit()
{
TrialId = t.TrialId,
SubjectId = t.SubjectId,
SubjectVisitId = t.SubjectVisitId,
ImageDate = (DateTime)t.StudyTime,
BodyPart = t.BodyPartForEdit,
BodyPartForEditOther = t.BodyPartForEditOther,
Modality = "IVUS"
}).ToListAsync();
var @lock = _distributedLockProvider.CreateLock($"NoneDicomCode");
using (await @lock.AcquireAsync())
{
var trialId = addList.FirstOrDefault().TrialId;
//默认会是0
var code = await _noneDicomStudyReposiotry.Where(t => t.TrialId == trialId).Select(x => x.Code).DefaultIfEmpty().MaxAsync();
foreach (var g in addList.GroupBy(t => t.SubjectVisitId))
{
var addOrEditNoneDicomStudy = g.First();
var optEntity = await _noneDicomStudyReposiotry.InsertFromDTOAsync(addOrEditNoneDicomStudy);
optEntity.Code = code + 1;
optEntity.StudyCode = AppSettings.GetCodeStr(optEntity.Code, nameof(NoneDicomStudy));
code++;
}
await _noneDicomStudyReposiotry.SaveChangesAsync();
}
#endregion
}
var list = _noneDicomStudyReposiotry.Where(t => t.SubjectId == subjectId && t.SubjectVisit.CheckState == CheckStateEnum.CVPassed)
.WhereIf(inQuery.SubjectVisitId != null, t => t.SubjectVisitId == inQuery.SubjectVisitId)
//.WhereIf(info.IsImageFilter, t => ("|" + info.CriterionModalitys + "|").Contains("|" + t.Modality + "|"))
.Select(u => new TaskNoneDicomStudyDTO()
{
@@ -924,7 +1018,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
Path = t.Path
}).ToList(),
ReadingFileCount = u.FileCount,
ReadingFileCount = u.NoneDicomFileList.Where(t => t.IsReading).Count(),
}).ToList();
@@ -950,12 +1044,12 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
item.UploadedFileList = noneDicomStudyList.Where(t => t.OriginNoneDicomStudyId == item.Id).ToList();
}
return list;
return ResponseOutput.Ok(list, config);
}
else
{
var query = from u in _visitTaskRepository.Where(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == inQuery.TrialReadingCriterionId
&& t.SourceSubjectVisitId != null && t.DoctorUserId == doctorUserId && t.TaskState == TaskState.Effect)
&& t.SourceSubjectVisit.CheckState == CheckStateEnum.CVPassed && t.DoctorUserId == doctorUserId && t.TaskState == TaskState.Effect)
//满足 有序,或者随机只看到当前任务的dicom 非dicom检查
.WhereIf(info.IsReadingTaskViewInOrder != ReadingOrder.SubjectRandom && inQuery.VisitTaskId != null, t => t.Id == inQuery.VisitTaskId)
join ns in _noneDicomStudyReposiotry.Where(t => t.SubjectId == subjectId).WhereIf(info.IsImageFilter, t => ("|" + info.CriterionModalitys + "|").Contains("|" + t.Modality + "|"))
@@ -976,12 +1070,12 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
ImageDate = ns.ImageDate,
BodyPart = ns.BodyPart,
BodyPartForEditOther = ns.BodyPartForEditOther,
FileCount = ns.FileCount,
Modality = ns.Modality,
StudyCode = ns.StudyCode,
//靶段标注 阅片IR 上传时IsImageSegmentLabel == false ,正常后处理上传IsImageSegmentLabel 该字段为空
FileList = (inQuery.IsImageSegmentLabel == false ? ns.NoneDicomFileList : ns.ImageLabelNoneDicomFileList)
FileList = (inQuery.IsImageSegmentLabel == false ? ns.ImageLabelNoneDicomFileList : ns.NoneDicomFileList)
.Select(t => new NoneDicomFileInfo()
{
FileType = t.FileType,
@@ -990,7 +1084,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
Path = t.Path
}).ToList(),
ReadingFileCount = ns.FileCount,
FileCount = (inQuery.IsImageSegmentLabel == false ? ns.ImageLabelNoneDicomFileList : ns.NoneDicomFileList).Count(),
ReadingFileCount = (inQuery.IsImageSegmentLabel == false ? ns.ImageLabelNoneDicomFileList : ns.NoneDicomFileList).Where(t => t.IsReading).Count(),
};
@@ -1018,7 +1113,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
item.UploadedFileList = taskNoneDicomStudyList.Where(t => t.OriginNoneDicomStudyId == item.Id).ToList();
}
return list;
return ResponseOutput.Ok(list, config);
}
@@ -1044,8 +1139,11 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var dirDic = new Dictionary<string, string>();
#region DIR处理导出文件名OSS里面存储
var info = await _subjectVisitRepository.Where(t => t.Id == inQuery.SubjectVisitId).Select(t => new { t.Trial.TrialCode }).FirstOrDefaultAsync();
//有传输语法值的导出 才生成DIR
if (_subjectVisitRepository.Where(t => t.Id == inQuery.SubjectVisitId).SelectMany(t => t.StudyList.SelectMany(t => t.InstanceList)).All(c => c.TransferSytaxUID != string.Empty))
//if (_subjectVisitRepository.Where(t => t.Id == inQuery.SubjectVisitId).SelectMany(t => t.StudyList.SelectMany(t => t.InstanceList)).All(c => c.TransferSytaxUID != string.Empty))
if (!_instanceRepository.Where(t => inQuery.SubjectVisitId == t.SubjectVisitId).Any(c => c.TransferSytaxUID == string.Empty))
{
var list = _subjectVisitRepository.Where(t => t.Id == inQuery.SubjectVisitId).SelectMany(t => t.StudyList)
.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false)
@@ -1055,7 +1153,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
DicomStudyId = t.DicomStudy.Id,
PatientId = t.DicomStudy.PatientId,
PatientId = info.TrialCode + "-" + t.DicomStudy.Subject.Code,
PatientName = t.DicomStudy.PatientName,
PatientBirthDate = t.DicomStudy.PatientBirthDate,
PatientSex = t.DicomStudy.PatientSex,
@@ -1089,6 +1187,9 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
foreach (var item in list.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId }))
{
var studyUid = item.Key.StudyInstanceUid;
var ossFolder = $"{pathInfo.TrialId}/Image/{pathInfo.SubjectId}/{pathInfo.VisitId}/{item.Key.StudyInstanceUid}";
var isSucess = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), dirDic, ossFolder, _oSSService));
@@ -1096,7 +1197,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
if (isSucess)
{
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/DICOMDIR" });
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic[$"{studyUid}_DICOMDIR"]}" });
}
}
@@ -1236,7 +1337,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
if (inQuery.IsImageSegmentLabel == true)
{
var list = await _subjectVisitRepository
var list = await _subjectVisitRepository.Where(t => t.CheckState == CheckStateEnum.CVPassed)
.WhereIf(inQuery.SubjectId != null, t => t.SubjectId == inQuery.SubjectId)
.ProjectTo<SubjectVisitMarkStudyDto>(_mapper.ConfigurationProvider).ToListAsync();
@@ -1248,7 +1349,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var dicomStudyList = list.Where(item => item.DicomStudyList.Count > 0)
.Select(item => new SubjectVisitMarkStudyDto
{
SourceSubjectVisitId=item.SourceSubjectVisitId,
SourceSubjectVisitId = item.SourceSubjectVisitId,
VisitTaskId = item.VisitTaskId,
SubjectId = item.SubjectId,
SubjectCode = item.SubjectCode,
@@ -1311,7 +1412,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
}
var query = _visitTaskRepository.Where(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == inQuery.TrialReadingCriterionId
&& t.SourceSubjectVisitId != null && t.DoctorUserId == doctorUserId)
&& t.SourceSubjectVisit.CheckState == CheckStateEnum.CVPassed && t.DoctorUserId == doctorUserId)
//满足 有序,或者随机只看到当前任务的dicom 非dicom检查
.WhereIf(inQuery.VisitTaskId == null, t => t.TaskState == TaskState.Effect)//从待阅列表进入,要筛选出有效的,任务可能重阅了,也要看到该任务的
.WhereIf(criterionInfo.IsReadingTaskViewInOrder != ReadingOrder.SubjectRandom && inQuery.VisitTaskId != null, t => t.Id == inQuery.VisitTaskId)
@@ -1329,8 +1430,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
{
var subjectVisitIdList = list.Select(t => t.SourceSubjectVisitId).Distinct().ToList();
var preDicomStudyList = _subjectVisitRepository.Where(t => subjectVisitIdList.Contains(t.Id)).SelectMany(t => t.TaskStudyList)
.ProjectTo<DicomStudyBasicInfo>(_mapper.ConfigurationProvider).ToList();
//var preDicomStudyList = _subjectVisitRepository.Where(t => subjectVisitIdList.Contains(t.Id)).SelectMany(t => t.TaskStudyList)
// .ProjectTo<DicomStudyBasicInfo>(_mapper.ConfigurationProvider).ToList();
var preNoneDicomStudyList = _subjectVisitRepository.Where(t => subjectVisitIdList.Contains(t.Id))
.SelectMany(t => t.NoneDicomStudyList)
@@ -1338,8 +1439,11 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
foreach (var item in list)
{
item.DicomStudyList.Where(t => inQuery.IsImageSegmentLabel == false ? t.ModalityForEdit == "XA" : true).ToList()
.AddRange(preDicomStudyList.Where(t => t.SubjectVisitId == item.SourceSubjectVisitId).ToList());
var dicomList = item.DicomStudyList.Where(t => inQuery.IsImageSegmentLabel == false ? t.ModalityForEdit == "XA" || t.ModalityForEdit == "DSA" || t.ModalityForEdit == "OCT" : true).ToList();
//dicomList.AddRange(preDicomStudyList.Where(t => t.SubjectVisitId == item.SourceSubjectVisitId).ToList());
item.DicomStudyList = dicomList;
item.NoneDicomStudyList = _mapper.Map<List<NoneDicomStudyBasicInfo>>(preNoneDicomStudyList.Where(t => t.SubjectVisitId == item.SourceSubjectVisitId).ToList());
}
@@ -1412,7 +1516,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
public async Task<IResponseOutput> GetIRReadingDownloadStudyInfo(IRDownloadQuery inQuery)
{
var info = await _readingQuestionCriterionTrialRepository.Where(t => t.Id == inQuery.TrialReadingCriterionId)
.Select(t => new { t.IsImageFilter, t.CriterionModalitys, t.TrialId, t.IsReadingTaskViewInOrder }).FirstNotNullAsync();
.Select(t => new { t.IsImageFilter, t.CriterionModalitys, t.TrialId, t.IsReadingTaskViewInOrder, t.Trial.TrialCode }).FirstNotNullAsync();
var isQueryDicom = inQuery.DicomStudyIdList.Count > 0;
var isQueryNoneDicom = inQuery.NoneDicomStudyIdList.Count > 0;
@@ -1430,12 +1534,12 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
#region DIR文件
//有传输语法值的导出 才生成DIR
if (_subjectVisitRepository.Where(t => t.SubjectId == inQuery.SubjectId).SelectMany(t => t.StudyList.SelectMany(t => t.InstanceList)).All(c => c.TransferSytaxUID != string.Empty))
if (_subjectVisitRepository.Where(t => t.SubjectId == inQuery.SubjectId && t.CheckState == CheckStateEnum.CVPassed).SelectMany(t => t.StudyList.SelectMany(t => t.InstanceList)).All(c => c.TransferSytaxUID != string.Empty))
{
var dirInfolist = _subjectRepository.Where(t => t.Id == inQuery.SubjectId).SelectMany(t => t.SubjectVisitList.Where(t => subjectVisitIdList.Contains(t.Id))).SelectMany(t => t.StudyList)
.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false)
.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
.Where(t => inQuery.IsImageSegmentLabel == false ? t.ModalityForEdit == "XA" : true)
.Where(t => info.IsImageFilter && inQuery.IsImageSegmentLabel == null ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
.Where(t => inQuery.IsImageSegmentLabel == false ? t.ModalityForEdit == "XA" || t.ModalityForEdit == "DSA" : true)
.SelectMany(t => t.InstanceList.Where(t => t.IsReading && t.DicomSerie.IsReading))
.Select(t => new StudyDIRInfo()
{
@@ -1443,7 +1547,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
DicomStudyId = t.DicomStudy.Id,
PatientId = t.DicomStudy.PatientId,
PatientId = info.TrialCode + "-" + t.DicomStudy.PatientId,
PatientName = t.DicomStudy.PatientName,
PatientBirthDate = t.DicomStudy.PatientBirthDate,
PatientSex = t.DicomStudy.PatientSex,
@@ -1478,11 +1582,12 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var taskDirInfolist = _taskInstanceRepository.Where(t => t.SubjectVisitId != null)
.Where(t => subjectVisitIdList.Contains((Guid)t.SubjectVisitId)).Select(t => new StudyDIRInfo()
{
IsTaskStudy = true,
SubjectVisitId = (Guid)t.SubjectVisitId,
DicomStudyId = t.TaskStudy.Id,
PatientId = t.TaskStudy.PatientId,
PatientId = info.TrialCode + "-" + t.TaskStudy.Subject.Code,
PatientName = t.TaskStudy.PatientName,
PatientBirthDate = t.TaskStudy.PatientBirthDate,
PatientSex = t.TaskStudy.PatientSex,
@@ -1521,14 +1626,25 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
{
var visitId = item.First().SubjectVisitId;
var isTaskStudy = item.First().IsTaskStudy;
var ossFolder = $"{pathInfo.TrialId}/Image/{pathInfo.SubjectId}/{visitId}/{item.Key.StudyInstanceUid}";
var studyUid = item.Key.StudyInstanceUid;
var isSucess = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), dirDic, ossFolder, _oSSService));
if (isSucess)
{
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/DICOMDIR" });
if (isTaskStudy)
{
await _taskStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new TaskStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic[$"{studyUid}_DICOMDIR"]}" });
}
else
{
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic[$"{studyUid}_DICOMDIR"]}" });
}
}
}
@@ -1536,23 +1652,23 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
#endregion
var query = from sv in _subjectRepository.Where(t => t.Id == inQuery.SubjectId).SelectMany(t => t.SubjectVisitList.Where(t => subjectVisitIdList.Contains(t.Id)))
var query = from sv in _subjectRepository.Where(t => t.Id == inQuery.SubjectId).SelectMany(t => t.SubjectVisitList.Where(t => subjectVisitIdList.Contains(t.Id) && t.CheckState == CheckStateEnum.CVPassed))
//一致性分析,导致查询出来两条数据
join visitTask in _visitTaskRepository.Where(t => taskIdList.Contains(t.Id)) on sv.Id equals visitTask.SourceSubjectVisitId into cc
from leftVisitTask in cc.DefaultIfEmpty()
select new ImageDownloadDto()
{
TrialId=sv.TrialId,
SubjectId=sv.SubjectId,
TrialSiteCode=sv.TrialSite.TrialSiteCode,
VisitId=sv.Id,
TrialId = sv.TrialId,
SubjectId = sv.SubjectId,
TrialSiteCode = sv.TrialSite.TrialSiteCode,
VisitId = sv.Id,
SubjectCode = inQuery.SubjectCode,
VisitName = sv.VisitName,
TaskBlindName = leftVisitTask.TaskBlindName,
StudyList = sv.StudyList.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false)
.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
.Where(t => inQuery.IsImageSegmentLabel == false ? t.ModalityForEdit == "XA" : true)
.Where(t => info.IsImageFilter && inQuery.IsImageSegmentLabel == null ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
.Where(t => inQuery.IsImageSegmentLabel == false ? t.ModalityForEdit == "XA" || t.ModalityForEdit == "DSA" : true)
.Select(u => new DownloadDicomStudyDto()
{
PatientId = u.PatientId,
@@ -1576,35 +1692,35 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
}).ToList()
}).ToList(),
TaskStudyList =
sv.TaskStudyList.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false)
//.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
.Select(u => new DownloadDicomStudyDto()
{
PatientId = u.PatientId,
StudyTime = u.StudyTime,
StudyCode = u.StudyCode,
StudyInstanceUid = u.StudyInstanceUid,
StudyDIRPath = u.StudyDIRPath,
//TaskStudyList =
//sv.TaskStudyList.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false)
////.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
//.Select(u => new DownloadDicomStudyDto()
//{
// PatientId = u.PatientId,
// StudyTime = u.StudyTime,
// StudyCode = u.StudyCode,
// StudyInstanceUid = u.StudyInstanceUid,
// StudyDIRPath = u.StudyDIRPath,
SeriesList = u.SeriesList.Select(z => new DownloadDicomSeriesDto()
{
Modality = z.Modality,
// SeriesList = u.SeriesList.Select(z => new DownloadDicomSeriesDto()
// {
// Modality = z.Modality,
InstanceList = z.InstanceList.Select(k => new DownloadDicomInstanceDto()
{
IsEncapsulated = k.IsEncapsulated,
InstanceId = k.Id,
FileName = string.Empty,
Path = k.Path,
FileSize = k.FileSize
}).ToList()
}).ToList()
}).ToList()
,
// InstanceList = z.InstanceList.Select(k => new DownloadDicomInstanceDto()
// {
// IsEncapsulated = k.IsEncapsulated,
// InstanceId = k.Id,
// FileName = string.Empty,
// Path = k.Path,
// FileSize = k.FileSize
// }).ToList()
// }).ToList()
//}).ToList()
//,
NoneDicomStudyList = inQuery.IsImageSegmentLabel != false ? sv.NoneDicomStudyList.Where(t => isQueryNoneDicom ? inQuery.NoneDicomStudyIdList.Contains(t.Id) : false)
.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.Modality + "|") : true)
.Where(t => info.IsImageFilter && inQuery.IsImageSegmentLabel == null ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.Modality + "|") : true)
.Where(t => t.IsReading)
.Select(nd => new DownloadNoneDicomStudyDto()
{
@@ -1648,7 +1764,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
//靶段标注处理
if (inQuery.IsImageSegmentLabel == false)
{
result.StudyList.AddRange(result.TaskStudyList);
//result.StudyList.AddRange(result.TaskStudyList);
}
foreach (var item in result.StudyList.SelectMany(t => t.SeriesList).SelectMany(t => t.InstanceList))
@@ -1696,11 +1812,14 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
[HttpPost]
public async Task<IResponseOutput> GetTaskUploadedDicomStudyList(IRTaskUploadedDicomStudyQuery inQuery)
{
var info = await _visitTaskRepository.Where(t => t.Id == inQuery.VisitTaskId)
var info = await _visitTaskRepository
.WhereIf(inQuery.VisitTaskId != null, t => t.Id == inQuery.VisitTaskId)//正常后处理
.Select(t => new { t.TrialReadingCriterion.IsImageFilter, t.TrialReadingCriterion.CriterionModalitys }).FirstNotNullAsync();
var query = _taskStudyRepository.Where(t => t.VisitTaskId == inQuery.VisitTaskId)
.Where(t => info.IsImageFilter ? ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
var query = _taskStudyRepository
.WhereIf(inQuery.VisitTaskId != null, t => t.VisitTaskId == inQuery.VisitTaskId)//正常后处理
.WhereIf(inQuery.VisitTaskId == null && inQuery.SubjectVisitId != null, t => t.SubjectVisitId == inQuery.SubjectVisitId)//靶段标注处理的影像
.WhereIf(info.IsImageFilter && inQuery.VisitTaskId != null, t => ("|" + info.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|"))
.Select(t => new
{
StudyId = t.Id,
@@ -1890,6 +2009,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var isExportReading = inCommand.IsExportReading == true;
var dirDic = new Dictionary<string, string>();
if (inCommand.IsKeyImage)
{
var downloadInfoList = _visitTaskRepository.Where(t => t.TrialId == inCommand.TrialId && (t.ReadingCategory == ReadingCategory.Visit || t.ReadingCategory == ReadingCategory.Global)
@@ -2137,6 +2258,77 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
}
else
{
#region DIR文件
//sql 超时
//_subjectVisitRepository.Where(t => inCommand.SubjectVisitIdList.Contains(t.Id))
// .SelectMany(t => t.StudyList.SelectMany(t => t.InstanceList.Where(t => isExportReading ? (t.IsReading && t.DicomSerie.IsReading) : true)))
// .All(c => c.TransferSytaxUID != string.Empty)
//有传输语法值的导出 才生成DIR
if (!_instanceRepository.Where(t => isExportReading ? (t.IsReading && t.DicomSerie.IsReading) : true)
.Where(t => inCommand.SubjectVisitIdList.Contains(t.SubjectVisitId)).Any(c => c.TransferSytaxUID == string.Empty))
{
var dirInfolist = _subjectVisitRepository.Where(t => inCommand.SubjectVisitIdList.Contains(t.Id)).SelectMany(t => t.StudyList)
.SelectMany(t => t.InstanceList.Where(t => isExportReading ? (t.IsReading && t.DicomSerie.IsReading) : true))
.Select(t => new StudyDIRInfo()
{
SubjectId = t.SubjectId,
SubjectVisitId = t.SubjectVisitId,
DicomStudyId = t.DicomStudy.Id,
PatientId = t.Trial.TrialCode + "-" + t.DicomStudy.Subject.Code,
PatientName = t.DicomStudy.PatientName,
PatientBirthDate = t.DicomStudy.PatientBirthDate,
PatientSex = t.DicomStudy.PatientSex,
StudyInstanceUid = t.StudyInstanceUid,
StudyId = t.DicomStudy.StudyId,
DicomStudyDate = t.DicomStudy.DicomStudyDate,
DicomStudyTime = t.DicomStudy.DicomStudyTime,
AccessionNumber = t.DicomStudy.AccessionNumber,
StudyDescription = t.DicomStudy.Description,
SeriesInstanceUid = t.DicomSerie.SeriesInstanceUid,
Modality = t.DicomSerie.Modality,
DicomSeriesDate = t.DicomSerie.DicomSeriesDate,
DicomSeriesTime = t.DicomSerie.DicomSeriesTime,
SeriesNumber = t.DicomSerie.SeriesNumber,
SeriesDescription = t.DicomSerie.Description,
InstanceId = t.Id,
SopInstanceUid = t.SopInstanceUid,
SOPClassUID = t.SOPClassUID,
InstanceNumber = t.InstanceNumber,
MediaStorageSOPClassUID = t.MediaStorageSOPClassUID,
MediaStorageSOPInstanceUID = t.MediaStorageSOPInstanceUID,
TransferSytaxUID = t.TransferSytaxUID,
}).ToList();
foreach (var item in dirInfolist.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId }))
{
var visitId = item.First().SubjectVisitId;
var subjectId = item.First().SubjectId;
var studyUid = item.Key.StudyInstanceUid;
var ossFolder = $"{inCommand.TrialId}/Image/{subjectId}/{visitId}/{item.Key.StudyInstanceUid}";
var isSucess = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), dirDic, ossFolder, _oSSService));
if (isSucess)
{
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic[$"{studyUid}_DICOMDIR"]}" });
}
}
}
#endregion
var downloadInfo = _trialRepository.Where(t => t.Id == inCommand.TrialId).Select(t => new
{
@@ -2152,14 +2344,19 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
u.PatientId,
u.StudyTime,
u.StudyCode,
StudyDIRPath = u.StudyDIRPath,
SeriesList = u.SeriesList.Where(t => isExportReading ? t.IsReading : true).Select(z => new
{
z.Modality,
InstancePathList = z.DicomInstanceList.Where(t => isExportReading ? t.IsReading : true).Select(k => new
InstanceList = z.DicomInstanceList.Where(t => isExportReading ? t.IsReading : true).Select(k => new DownloadDicomInstanceDto()
{
k.Path
IsEncapsulated = k.IsEncapsulated,
InstanceId = k.Id,
FileName = string.Empty,
Path = k.Path,
FileSize = k.FileSize
})
})
@@ -2183,6 +2380,18 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
}).FirstOrDefault();
foreach (var item in downloadInfo.VisitList.SelectMany(t => t.StudyList.SelectMany(t => t.SeriesList).SelectMany(t => t.InstanceList)))
{
var key = item.InstanceId.ToString();
if (dirDic.ContainsKey(key))
{
item.FileName = dirDic[key];
}
}
return ResponseOutput.Ok(downloadInfo);
}
@@ -2202,23 +2411,27 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
[HttpPost]
public async Task<IResponseOutput<PageOutput<SubjectVisitMarkDTO>>> GetTrialSubjectVisitMarkList(SubjectVisitMarkQuery inQuery)
{
var list = await _subjectRepository.Where(t => t.TrialId == inQuery.TrialId)
var list = await _subjectRepository.Where(t => t.TrialId == inQuery.TrialId && t.SubjectDoctorList.Any())
.WhereIf(inQuery.SubjectCode.IsNotNullOrEmpty(), t => t.Code.Contains(inQuery.SubjectCode))
.WhereIf(inQuery.IsUrgent != null, t => t.IsUrgent == inQuery.IsUrgent)
.Select(t => new SubjectVisitMarkDTO()
{
TrialId = t.TrialId,
SubjectId = t.Id,
SubjectCode=t.Code,
SubjectCode = t.Code,
IsUrgent = t.IsUrgent,
VisitCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted).Count(),
MarkVisitCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted).Count(t => t.TaskStudyList.Any()),
DicomStudyCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted).SelectMany(t => t.StudyList).Count(),
MarkDicomStudyCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted).SelectMany(t => t.TaskStudyList).Count(),
VisitCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted && t.CheckState == CheckStateEnum.CVPassed).Count(),
NoneDicomStudyCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted).SelectMany(t => t.NoneDicomStudyList).Count(),
MarkNoneDicomStudyCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted).SelectMany(t => t.NoneDicomStudyList).Where(t=>t.ImageLabelNoneDicomFileList.Any()).Count(),
MarkIVUSVisitCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted && t.CheckState == CheckStateEnum.CVPassed).Count(t => t.NoneDicomStudyList.Where(t => t.Modality == "IVUS").SelectMany(c => c.ImageLabelNoneDicomFileList).Any()),
MarkOCTVisitCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted && t.CheckState == CheckStateEnum.CVPassed).Count(t => t.NoneDicomStudyList.Where(t => t.Modality == "OCT").SelectMany(c => c.ImageLabelNoneDicomFileList).Any()),
DicomStudyCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted && t.CheckState == CheckStateEnum.CVPassed).SelectMany(t => t.StudyList).Count(),
//MarkDicomStudyCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted).SelectMany(t => t.TaskStudyList).Count(),
NoneDicomStudyCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted && t.CheckState == CheckStateEnum.CVPassed).SelectMany(t => t.NoneDicomStudyList).Count(),
//MarkNoneDicomStudyCount = t.SubjectVisitList.Where(t => t.SubmitState == SubmitStateEnum.Submitted).SelectMany(t => t.NoneDicomStudyList).Where(t => t.ImageLabelNoneDicomFileList.Any()).Count(),
}).ToPagedListAsync(inQuery);
return ResponseOutput.Ok(list);
@@ -3,7 +3,9 @@
// 生成时间 2021-12-06 10:54:55
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using DocumentFormat.OpenXml.EMMA;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using Medallion.Threading;
@@ -34,7 +36,8 @@ namespace IRaCIS.Core.Application.Contracts
[FromQuery] Guid? nonedicomStudyId,
[FromQuery] bool isFilterZip,
[FromQuery] Guid? visitTaskId,
[FromQuery] bool isReading)
[FromQuery] bool isReading,
[FromQuery] bool? isImageSegmentLabel)
{
var qcAuditState = await _subjectVisitRepository.Where(s => s.Id == subjectVisitId).Select(t => t.AuditState).FirstOrDefaultAsync();
@@ -44,33 +47,74 @@ namespace IRaCIS.Core.Application.Contracts
//质控过程中并且不是IQC时,可以看到删除的(不需要忽略过滤器) 质控中iqc 也需要看到删除的
var isViewDelete = !isQCFinished;
var isFilterIVUSNoneDicom = false;
CriterionType? criterionType = null;
IQueryable<NoneDicomStudyView> noneDicomStudyQueryable = default;
if (visitTaskId == null)
if (visitTaskId == null || visitTaskId == Guid.Empty)
{
//质控过程中,需要忽略过滤质控设置删除的检查,以及设置删除的文件,质控通过后才
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.SubjectVisitId == subjectVisitId, ignoreQueryFilters: isViewDelete)
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
if (visitTaskId == null)
{
//质控过程中,需要忽略过滤质控设置删除的检查,以及设置删除的文件,质控通过后才
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.SubjectVisitId == subjectVisitId, ignoreQueryFilters: isViewDelete)
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
.ProjectTo<NoneDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, isReading = isReading });
}
else
{
//靶段标注上传后查看影像
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.SubjectVisitId == subjectVisitId, ignoreQueryFilters: isViewDelete)
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
.ProjectTo<ImageLabelNoneDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, isReading = isReading });
}
.ProjectTo<NoneDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, isReading = isReading });
//ivus-在访视的时候visitTaskId==null 需要过滤掉空检查 ,但是在靶段标注的查看访视级别的时候不能过滤
if (_subjectVisitRepository.Where(t => t.Id == subjectVisitId).SelectMany(t => t.Trial.TrialReadingCriterionList)
.Where(t => t.CriterionType == CriterionType.IVUS || t.CriterionType == CriterionType.OCT).Distinct().Count() == 2
&& visitTaskId == null)
{
isFilterIVUSNoneDicom = true;
}
}
else
{
var taskinfo = await _visitTaskRepository.Where(x => x.Id == visitTaskId).Select(t => new { t.BlindSubjectCode, t.TrialReadingCriterionId, t.TrialReadingCriterion.IsImageFilter, t.TrialReadingCriterion.CriterionModalitys }).FirstNotNullAsync();
var taskinfo = await _visitTaskRepository.Where(x => x.Id == visitTaskId).Select(t => new { t.BlindSubjectCode, t.TrialReadingCriterionId, t.TrialReadingCriterion.CriterionType, t.TrialReadingCriterion.IsImageFilter, t.TrialReadingCriterion.CriterionModalitys }).FirstNotNullAsync();
criterionType = taskinfo.CriterionType;
if ((taskinfo.CriterionType == CriterionType.IVUS || taskinfo.CriterionType == CriterionType.OCT) && isImageSegmentLabel == false)
{
//后处理原始影像预览
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.SubjectVisitId == subjectVisitId, ignoreQueryFilters: isViewDelete)
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
.Where(t => taskinfo.IsImageFilter ? ("|" + taskinfo.CriterionModalitys + "|").Contains("|" + t.Modality + "|") : true)
.ProjectTo<ImageLabelNoneDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, visiTaskId = visitTaskId, isReading = isReading });
}
else
{
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.TaskNoneDicomFileList.Any(t => t.VisitTaskId == visitTaskId), ignoreQueryFilters: isViewDelete)
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
.Where(t => taskinfo.IsImageFilter ? ("|" + taskinfo.CriterionModalitys + "|").Contains("|" + t.Modality + "|") : true)
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
.ProjectTo<TaskDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, visiTaskId = visitTaskId, isReading = isReading });
}
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.TaskNoneDicomFileList.Any(t => t.VisitTaskId == visitTaskId), ignoreQueryFilters: isViewDelete)
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
.Where(t => taskinfo.IsImageFilter ? ("|" + taskinfo.CriterionModalitys + "|").Contains("|" + t.Modality + "|") : true)
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
.ProjectTo<TaskDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, visiTaskId = visitTaskId, isReading = isReading });
}
var list = await noneDicomStudyQueryable.OrderBy(x => x.ImageDate).ThenBy(x => x.CreateTime).ToListAsync();
var config = await _subjectVisitRepository.Where(t => t.Id == subjectVisitId).Select(t => new { t.Trial.ImageFormatList, t.Trial.StudyNameList, t.Trial.IsShowStudyName, AuditState = qcAuditState }).FirstOrDefaultAsync();
var list = await noneDicomStudyQueryable.Where(t => isFilterIVUSNoneDicom ? t.Modality != "IVUS" : true)
.OrderBy(x => x.ImageDate).ThenBy(x => x.CreateTime).ToListAsync();
var config = await _subjectVisitRepository.Where(t => t.Id == subjectVisitId).Select(t => new { t.Trial.ImageFormatList, t.Trial.StudyNameList, t.Trial.IsShowStudyName, AuditState = qcAuditState, CriterionType=criterionType }).FirstOrDefaultAsync();
return ResponseOutput.Ok(list, config);
}
@@ -17,7 +17,18 @@ namespace IRaCIS.Core.Application.Services
{
/// <summary>
/// 更新缩略图路径
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> UpdateImageResizePath(UpdateImageResizeDTO dto)
{
await _seriesRepository.UpdatePartialFromQueryAsync(t => t.Id == dto.SeriesId, u => new DicomSeries() { ImageResizePath = dto.ImageResizePath }, true);
return ResponseOutput.Ok();
}
//医生读片那一块有耦合,关键序列 这里暂时留存
/// <summary> 指定资源Id,获取Dicom检查所属序列信息列表 </summary>
@@ -382,7 +382,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
Id = t.Id,
Bodypart = t.BodyPartExamined,
Bodypart = t.BodyPartForEdit,
BodyPartForEditOther = t.BodyPartForEditOther,
Modalities = t.Modalities,
@@ -433,6 +434,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
Id = t.Id,
Bodypart = t.BodyPart,
BodyPartForEditOther=t.BodyPartForEditOther,
Modalities = t.Modality,
@@ -92,6 +92,9 @@ namespace IRaCIS.Core.Application.Service
CreateMap<DicomSeries, DicomSeriesDTO>()
.ForMember(o => o.SubjectCode, t => t.MapFrom(u => u.SubjectVisit.Subject.Code))
.ForMember(o => o.VisitName, t => t.MapFrom(u => u.SubjectVisit.VisitName));
CreateMap<TaskSeries, DicomSeriesDTO>()
.ForMember(o => o.SubjectCode, t => t.MapFrom(u => u.SubjectVisit.Subject.Code))
.ForMember(o => o.VisitName, t => t.MapFrom(u => u.SubjectVisit.VisitName));
CreateMap<SCPSeries, DicomSeriesDTO>();
@@ -846,10 +846,26 @@ namespace IRaCIS.Core.Application.Service
if (jsonObject["DictionaryCode"] != null && jsonObject["DictionaryCode"].ToString() != string.Empty)
{
jsonObject[item.Code] = await _dictionaryRepository.Where(x => x.Code == jsonObject["DictionaryCode"].ToString()).Join(_dictionaryRepository.Where(x => x.Code == jsonObject[item.Code].ToString()), a => a.Id, b => b.ParentId, (a, b) => new
if (jsonObject[item.Code].ToString().Contains(","))
{
value = _userInfo.IsEn_Us ? b.Value : b.ValueCN
}).Select(x => x.value).FirstOrDefaultAsync();
var data = jsonObject[item.Code].ToString().Split(',').ToList();
var codeList = await _dictionaryRepository.Where(x => x.Code == jsonObject["DictionaryCode"].ToString()).Join(_dictionaryRepository.Where(x => data.Contains(x.Code)), a => a.Id, b => b.ParentId, (a, b) => new
{
value = _userInfo.IsEn_Us ? b.Value : b.ValueCN
}).Select(x => x.value).ToListAsync();
jsonObject[item.Code] = string.Join(",", codeList);
}
else
{
jsonObject[item.Code] = await _dictionaryRepository.Where(x => x.Code == jsonObject["DictionaryCode"].ToString()).Join(_dictionaryRepository.Where(x => x.Code == jsonObject[item.Code].ToString()), a => a.Id, b => b.ParentId, (a, b) => new
{
value = _userInfo.IsEn_Us ? b.Value : b.ValueCN
}).Select(x => x.value).FirstOrDefaultAsync();
}
}
jsonList.Add(jsonObject);
@@ -917,10 +933,25 @@ namespace IRaCIS.Core.Application.Service
//通过字典项的code 翻译 枚举或者 bool
else
{
jsonDataDic[item.Key] = await _dictionaryRepository.Where(x => x.Code == item.Code).Join(_dictionaryRepository.Where(x => x.Code == value.ToString()), a => a.Id, b => b.ParentId, (a, b) => new
if (value.ToString().Contains(","))
{
value = _userInfo.IsEn_Us ? b.Value : b.ValueCN
}).Select(x => x.value).FirstOrDefaultAsync();
var data = value.ToString().Split(',').ToList();
var codeList = await _dictionaryRepository.Where(x => x.Code == item.Code).Join(_dictionaryRepository.Where(x => data.Contains(x.Code)), a => a.Id, b => b.ParentId, (a, b) => new
{
value = _userInfo.IsEn_Us ? b.Value : b.ValueCN
}).Select(x => x.value).ToListAsync();
jsonDataDic[item.Key] = string.Join(",", codeList);
}
else
{
jsonDataDic[item.Key] = await _dictionaryRepository.Where(x => x.Code == item.Code).Join(_dictionaryRepository.Where(x => x.Code == value.ToString()), a => a.Id, b => b.ParentId, (a, b) => new
{
value = _userInfo.IsEn_Us ? b.Value : b.ValueCN
}).Select(x => x.value).FirstOrDefaultAsync();
}
}
}
}
@@ -94,6 +94,10 @@ namespace IRaCIS.Application.Contracts
public string UserCode { get; set; }
public string EMail { get; set; }
public string HiddenEmail { get; set; }
public int Status { get; set; }
public bool IsTestUser { get; set; }
public bool IsZhiZhun { get; set; }
@@ -279,8 +283,15 @@ namespace IRaCIS.Application.Contracts
public string OldPassWord { get; set; } = string.Empty;
}
public class AdminResetUserPwdCommand
{
public Guid IdentityUserId { get; set; }
public string BaseUrl { get; set; } = string.Empty;
public string RouteUrl { get; set; } = string.Empty;
}
public class UserAccountDto
{
public Guid Id { get; set; }
@@ -16,7 +16,7 @@ namespace IRaCIS.Core.Application.Service
Task<IResponseOutput> SendMFAEmail(SendMfaCommand sendMfa);
Task<UserBasicInfo> GetUserBasicInfo(Guid userId, string pwd);
Task<IResponseOutput> ModifyPassword(EditPasswordCommand editPwModel);
Task<IResponseOutput> ResetPassword(Guid userId);
Task<IResponseOutput> ResetPassword(AdminResetUserPwdCommand inCommand);
Task<IResponseOutput> UpdateUser(UserCommand model);
//Task<IResponseOutput> UpdateUserState(Guid userId, UserStateEnum state);
@@ -286,17 +286,17 @@ namespace IRaCIS.Core.Application.Service
/// <summary>
/// 重置密码为 默认密码
/// </summary>
/// <param name="identityUserId"></param>
/// <returns></returns>
[HttpGet("{identityUserId:guid}")]
[UnitOfWork]
public async Task<IResponseOutput> ResetPassword(Guid identityUserId)
[HttpPost]
public async Task<IResponseOutput> ResetPassword(AdminResetUserPwdCommand inCommand)
{
var identityUserId = inCommand.IdentityUserId;
var pwd = IRCEmailPasswordHelper.GenerateRandomPassword(10);
await _mailVerificationService.AdminResetPwdSendEmailAsync(identityUserId, pwd);
await _mailVerificationService.AdminResetPwdSendEmailAsync(identityUserId, pwd, inCommand.BaseUrl);
await _identityUserRepository.UpdatePartialFromQueryAsync(t => t.Id == identityUserId, u => new IdentityUser()
{
@@ -308,6 +308,8 @@ namespace IRaCIS.Core.Application.Service
await _fusionCache.RemoveAsync(CacheKeys.UserLoginError(userName));
await _fusionCache.RemoveByTagAsync(CacheKeys.UserMFATag(identityUserId));
await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, ActionIdentityUserId = _userInfo.IdentityUserId, ActionUserName = _userInfo.UserName, TargetIdentityUserId = identityUserId, OptType = UserOptType.ResetPassword }, true);
return ResponseOutput.Ok();
@@ -316,7 +318,7 @@ namespace IRaCIS.Core.Application.Service
/// <summary>
/// 重置密码发邮件 (未登陆修改)
/// 重置密码发邮件 (未登陆修改-忘记密码
/// </summary>
/// <param name="email"></param>
/// <returns></returns>
@@ -357,6 +359,8 @@ namespace IRaCIS.Core.Application.Service
await _mailVerificationService.AnolymousSendEmailForResetAccount(email, verificationCode);
await _fusionCache.RemoveByTagAsync(CacheKeys.UserMFATag(existUser.Id));
return ResponseOutput.Ok();
}
@@ -438,6 +442,10 @@ namespace IRaCIS.Core.Application.Service
await _mailVerificationService.AfterUserModifyPasswordSendEmailAsync(identityUserId);
var find = await _identityUserRepository.FindAsync(identityUserId);
await _fusionCache.RemoveAsync(CacheKeys.UserLoginError(find.UserName));
return ResponseOutput.Ok();
}
@@ -485,6 +493,8 @@ namespace IRaCIS.Core.Application.Service
await _mailVerificationService.AfterUserModifyPasswordSendEmailAsync(_userInfo.IdentityUserId);
await _fusionCache.RemoveByTagAsync(CacheKeys.UserMFATag(_userInfo.IdentityUserId));
return ResponseOutput.Result(success);
@@ -861,8 +871,9 @@ namespace IRaCIS.Core.Application.Service
if (isRemember)
{
await _fusionCache.SetAsync(CacheKeys.UserMFAVerifyPass(identityUserId, _userInfo.BrowserFingerprint), _userInfo.BrowserFingerprint, TimeSpan.FromMinutes(_serviceVerifyConfigConfig.UserMFAVerifyMinutes));
await _fusionCache.SetAsync(CacheKeys.UserMFAVerifyPass(identityUserId, _userInfo.BrowserFingerprint), _userInfo.BrowserFingerprint,
TimeSpan.FromMinutes(_serviceVerifyConfigConfig.UserMFAVerifyMinutes), new[] { CacheKeys.UserMFATag(identityUserId) });
Log.Logger.Warning($"MFA登录记录:{_userInfo.UserName} 浏览器标识: {_userInfo.BrowserFingerprint} 设置缓存分钟{_serviceVerifyConfigConfig.UserMFAVerifyMinutes}");
}
@@ -925,11 +936,11 @@ namespace IRaCIS.Core.Application.Service
[AllowAnonymous]
[HttpGet]
public async Task<IResponseOutput> LoginOut(Guid identityUserId, Guid userRoleId)
public async Task<IResponseOutput> LoginOut(Guid identityUserId, Guid? userRoleId)
{
await _fusionCache.RemoveAsync(CacheKeys.UserToken(identityUserId));
if (_identityUserRepository.Any(t => t.Id == identityUserId))
if (_identityUserRepository.Any(t => t.Id == identityUserId) && userRoleId != null)
{
var userName = await _userRoleRepository.Where(t => t.Id == userRoleId).Select(t => t.IdentityUser.UserName).FirstOrDefaultAsync();
@@ -989,14 +1000,6 @@ namespace IRaCIS.Core.Application.Service
var userLog = new UserLog();
if (failCount >= maxFailures)
{
await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, ActionUserName = userName, LoginPassword = password, OptType = UserOptType.TempLockLogin }, true);
//$"密码连续错误{maxFailures}次,当前账号已被限制登录,请等待 {lockoutMinutes} 分钟后再试。"
throw new BusinessValidationFailedException(_localizer["User_ErrorLimit", maxFailures, lockoutMinutes]);
}
var userLoginReturnModel = new IRCLoginReturnDTO();
@@ -1008,7 +1011,7 @@ namespace IRaCIS.Core.Application.Service
var isLoginUncommonly = false;
#region //登录用户是系统用户的时候,就要要记录异地登录
#region
//账号在系统存在
if (isExistAccount || loginUser != null)
@@ -1066,7 +1069,7 @@ namespace IRaCIS.Core.Application.Service
//异地登录
loginUser.LoginState = 2;
await _fusionCache.RemoveByTagAsync(CacheKeys.UserMFATag(loginUser.IdentityUserId));
}
}
}
@@ -1082,6 +1085,14 @@ namespace IRaCIS.Core.Application.Service
//错误次数累加
failCount++;
if (failCount >= maxFailures)
{
await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, ActionUserName = userName, LoginPassword = password, OptType = UserOptType.TempLockLogin }, true);
//$"密码连续错误{maxFailures}次,当前账号已被限制登录,请等待 {lockoutMinutes} 分钟后再试。"
throw new BusinessValidationFailedException(_localizer["User_ErrorLimit", maxFailures, lockoutMinutes]);
}
await _fusionCache.SetAsync(cacheKey, failCount, TimeSpan.FromMinutes(lockoutMinutes));
await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, ActionUserName = userName, LoginPassword = password, OptType = UserOptType.AccountOrPasswordError, IsLoginUncommonly = isLoginUncommonly }, true);
@@ -1176,22 +1187,27 @@ namespace IRaCIS.Core.Application.Service
if (_verifyConfig.CurrentValue.OpenLoginMFA)
{
if ((await _fusionCache.GetOrDefaultAsync(CacheKeys.UserMFAVerifyPass(identityUserId, _userInfo.BrowserFingerprint), "")) == _userInfo.BrowserFingerprint)
{
userLoginReturnModel.IsMFA = false;
Log.Logger.Warning($"MFA登录:{userName} 浏览器标识: {_userInfo.BrowserFingerprint},判断缓存里存在 ");
}
else
{
//MFA 发送邮件
userLoginReturnModel.IsMFA = true;
Log.Logger.Warning($"MFA登录:{userName} 浏览器标识: {_userInfo.BrowserFingerprint} 判断缓存已经不存在");
}
var email = userLoginReturnModel.BasicInfo.EMail;
var hiddenEmail = IRCEmailPasswordHelper.MaskEmail(email);
userLoginReturnModel.BasicInfo.EMail = hiddenEmail;
userLoginReturnModel.BasicInfo.HiddenEmail = hiddenEmail;
//修改密码 || 90天修改密码再mfa 之前
if (userLoginReturnModel.BasicInfo.IsFirstAdd || userLoginReturnModel.BasicInfo.NeedChangePassWord)
@@ -1207,6 +1223,10 @@ namespace IRaCIS.Core.Application.Service
}
}
else
{
userLoginReturnModel.BasicInfo.HiddenEmail = userLoginReturnModel.BasicInfo.EMail;
}
await _fusionCache.SetAsync(CacheKeys.UserToken(identityUserId), userLoginReturnModel.JWTStr, TimeSpan.FromDays(7));
@@ -20,6 +20,8 @@ namespace IRaCIS.Core.Application.Contracts
public bool IsDeleted { get; set; }
public bool IsBeMark { get; set; }
public DateTime? MarkTime { get; set; }
}
@@ -31,6 +31,11 @@ namespace IRaCIS.Core.Application.Contracts
{
}
public class ImageLabelNoneDicomStudyView : NoneDicomStudyView
{
}
///<summary>NoneDicomStudyQuery 列表查询参数模型</summary>
public class NoneDicomStudyQuery
{
@@ -265,6 +265,7 @@ namespace IRaCIS.Core.Application.Contracts.DTO
{
public bool ExistsManual { get; set; }
public bool IsHaveStudyClinicalData { get; set; }
public bool QCRiskControl { get; set; }
public SubjectClinicalDataDto SubjectClinicalData { get; set; } = new SubjectClinicalDataDto();
public List<NoneDicomStudyView> NoneDicomStudyList { get; set; } = new List<NoneDicomStudyView>();
@@ -1,6 +1,7 @@
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
using IRaCIS.Core.Application.Contracts.DTO;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Share;
using MiniExcelLibs.Attributes;
using Newtonsoft.Json;
@@ -989,6 +990,13 @@ namespace IRaCIS.Core.Application.Contracts
/// </summary>
[DictionaryTranslateAttribute("YesOrNo")]
public bool IsInvalid { get; set; }
public List<GetMedicalReviewDialogOutDto> DialogList { get; set; }
public string DialogStr => string.Join("\n\n", DialogList.OrderBy(t => t.CreateTime).Select(c => c.CreateUserName + " (" + ExportExcelConverterDate.DateTimeInternationalToString(c.CreateTime) + ") :" + c.ResultContent));
}
public class TrialImageDownloadExportDto
@@ -1248,6 +1256,10 @@ namespace IRaCIS.Core.Application.Contracts
//问题标识,肿瘤评估用于区分是什么问题
public QuestionType? QuestionType { get; set; }
public OptionType OptionTypeEnum { get; set; }
public ValueUnit? Unit { get; set; }
public Guid QuestionId { get; set; }
public string QuestionName { get; set; }
@@ -1413,6 +1425,7 @@ namespace IRaCIS.Core.Application.Contracts
public string TableName { get; set; }
#endregion
public OptionType OptionTypeEnum { get; set; }
public Guid TableQuesionId { get; set; }
@@ -7,6 +7,7 @@ using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using MassTransit.Initializers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NPOI.SS.Formula.Functions;
using System.Linq;
@@ -25,6 +26,7 @@ namespace IRaCIS.Core.Application.Image.QA
IRepository<DicomSeries> _dicomSeriesRepository,
IRepository<NoneDicomStudy> _noneDicomStudyRepository,
IRepository<QCChallenge> _qcChallengeRepository,
IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig,
IRepository<SubjectVisitImageBackRecord> _subjectVisitImageBackRecordReposiotry,
IRepository<UserRole> _userRoleReposiotry,
IReadingImageTaskService _IReadingImageTaskService, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IQCListService
@@ -456,7 +458,7 @@ namespace IRaCIS.Core.Application.Image.QA
return new TrialVisitQADTO
{
QCQuestionAnswerList = qacheckList,
QCRiskControl = _verifyConfig.CurrentValue.QCRiskControl,
SecondReviewList = secondReviewList,
IsHaveStudyClinicalData = await _clinicalDataTrialSetRepository.AnyAsync(x => x.IsConfirm && x.TrialId == sv.TrialId && x.ClinicalDataLevel == ClinicalLevel.Study),
@@ -772,7 +774,7 @@ namespace IRaCIS.Core.Application.Image.QA
var query = _inspectionFileRepository.Where(t => t.TrialId == inQuery.TrialId && (t.CheckState == EDCCheckState.Success || t.CheckState == EDCCheckState.Failed))
.ProjectTo<GetUserUploadFileDto>(_mapper.ConfigurationProvider);
return await query.ToPagedListAsync(inQuery);
return await query.ToPagedListAsync(inQuery,nameof(GetUserUploadFileDto.CreateTime));
}
@@ -1981,6 +1981,23 @@ namespace IRaCIS.Core.Application.Image.QA
SubjectId = dbSubjectVisit.SubjectId
});
if (await _readingQuestionCriterionTrialRepository.AnyAsync(x => x.Id == trialReadingCriterionId && x.IsOncologyReading))
{
await _readModuleRepository.AddAsync(new ReadModule()
{
TrialReadingCriterionId = trialReadingCriterionId,
ReadingSetType = ReadingSetType.TumorReading,
SubjectVisitId = dbSubjectVisit.Id,
ModuleName = $"O-{dbSubjectVisit.VisitName}",
ModuleType = ModuleTypeEnum.Oncology,
ReadModuleAddTypeEnum = dbSubjectVisit.PDState == PDStateEnum.PDProgress ? ReadModuleAddType.PDConfirmation : ReadModuleAddType.FinalVisit,
IsUrgent = dbSubjectVisit.IsUrgent,
TrialId = dbSubjectVisit.TrialId,
SubjectId = dbSubjectVisit.SubjectId
});
}
}
}
@@ -179,13 +179,14 @@ namespace IRaCIS.Core.Application.Service
.ForMember(o => o.MedicalNo, t => t.MapFrom(u => u.VisitTask.Subject.MedicalNo))
.ForMember(o => o.DoctorUserName, t => t.MapFrom(u => u.VisitTask.DoctorUser.UserName))
.ForMember(o => o.MedicalManagerUserName, t => t.MapFrom(u => u.MedicalManagerUser.UserName))
.ForMember(o => o.QuestionContent, t => t.MapFrom(u => string.Join("\n\n", u.ReadingMedicalReviewDialogList.Where(t => t.IsHaveQuestion).Select(t => t.Questioning))));
.ForMember(o => o.QuestionContent, t => t.MapFrom(u => string.Join("\n\n", u.ReadingMedicalReviewDialogList.Where(t => t.IsHaveQuestion).Select(t => t.Questioning))))
.ForMember(o => o.DialogList, t => t.MapFrom(u => u.ReadingMedicalReviewDialogList));
CreateMap<VisitTask, TaskMedicalReviewExportDto>()
.ForMember(o => o.TrialReadingCriterionName, t => t.MapFrom(u => u.TrialReadingCriterion.CriterionName))
.ForMember(o => o.TrialSiteCode, t => t.MapFrom(u => u.IsSelfAnalysis == true ? u.BlindTrialSiteCode : u.Subject.TrialSite.TrialSiteCode))
.ForMember(o => o.SubjectCode, t => t.MapFrom(u => u.IsSelfAnalysis == true ? u.BlindSubjectCode : u.Subject.Code))
;
@@ -245,6 +246,8 @@ namespace IRaCIS.Core.Application.Service
.OrderBy(k => k.ReadingQuestionTrial.ShowOrder)
.Select(c => new CommonQuesionInfo()
{
Unit=c.ReadingQuestionTrial.Unit,
OptionTypeEnum=c.ReadingQuestionTrial.OptionTypeEnum,
QuestionType = c.ReadingQuestionTrial.QuestionType,
QuestionName = isEn_Us ? c.ReadingQuestionTrial.QuestionEnName : c.ReadingQuestionTrial.QuestionName,
QuestionValue = c.IsGlobalChange ? c.GlobalChangeAnswer : c.Answer,
@@ -284,7 +287,10 @@ namespace IRaCIS.Core.Application.Service
.OrderBy(k => k.ReadingQuestionTrial.ShowOrder)
.Select(c => new CommonQuesionInfo()
{
Unit=c.ReadingQuestionTrial.Unit,
Group = isEn_Us ? c.ReadingQuestionTrial.GroupEnName : c.ReadingQuestionTrial.GroupName,
QuestionType = c.ReadingQuestionTrial.QuestionType,
OptionTypeEnum=c.ReadingQuestionTrial.OptionTypeEnum,
QuestionId = c.ReadingQuestionTrial.Id,
QuestionName = isEn_Us ? c.ReadingQuestionTrial.QuestionEnName : c.ReadingQuestionTrial.QuestionName,
QuestionValue = c.IsGlobalChange ? c.GlobalChangeAnswer : c.Answer,
@@ -303,9 +309,10 @@ namespace IRaCIS.Core.Application.Service
.ForMember(o => o.LessionAnswerList, t => t.MapFrom(u => u.LesionAnswerList.Where(c => c.ReadingTableQuestionTrial.ExportResultStr.Contains(((int)readingExportType).ToString()))));
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.ShowOrder, t => t.MapFrom(u => u.ReadingTableQuestionTrial.ShowOrder))
//.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.OptionTypeEnum, t => t.MapFrom(u => u.ReadingTableQuestionTrial.OptionTypeEnum))
.ForMember(o => o.ShowOrder, t => t.MapFrom(u => u.ReadingTableQuestionTrial.ShowOrder))
.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))
@@ -754,6 +761,13 @@ namespace IRaCIS.Core.Application.Service
.OrderBy(t => t.CreateTime).ThenBy(t => t.FileName)))
.ForMember(d => d.CodeView, u => u.MapFrom(s => s.StudyCode));
CreateMap<NoneDicomStudy, ImageLabelNoneDicomStudyView>()
.ForMember(d => d.NoneDicomStudyFileList, u => u.MapFrom(s =>
s.ImageLabelNoneDicomFileList.Where(t => isFilterZip ? !t.FileType.Contains(StaticData.FileType.Zip) : true)
.Where(t => isReading ? t.IsReading && t.IsDeleted == false : true)
.OrderBy(t => t.CreateTime).ThenBy(t => t.FileName)))
.ForMember(d => d.CodeView, u => u.MapFrom(s => s.StudyCode));
CreateMap<SubjectVisitImageBackRecord, ImageBackViewModel>()
.ForMember(d => d.SubjectCode, u => u.MapFrom(s => s.SubjectVisit.Subject.Code))
@@ -252,21 +252,50 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// <summary>
/// 第一次
/// </summary>
public int FirstData { get; set; }
public decimal FirstData { get; set; }
/// <summary>
/// 第二次
/// </summary>
public int SecondData { get; set; }
public decimal SecondData { get; set; }
/// <summary>
/// 第三次
/// </summary>
public int ThirdData { get; set; }
public decimal ThirdData { get; set; }
/// <summary>
/// 巨噬细胞浸润测量
/// </summary>
public string MacrophageInfiltrationMeasurement { get; set; }
/// <summary>
/// 巨噬细胞浸润角度测量
/// </summary>
public decimal? MacrophageInfiltrationAngle { get; set; }
/// <summary>
/// 微通道测量
/// </summary>
public string MicrochannelMeasurement { get; set; }
/// <summary>
/// 胆固醇结晶测量
/// </summary>
public string CholesterolCrystalMeasurement { get; set; }
/// <summary>
/// 官腔面积测量
/// </summary>
public decimal? LumenAreaMeasurement { get; set; }
public decimal Avg { get {
return ( FirstData*1m + SecondData * 1m + ThirdData * 1m) / 3;
return ( FirstData + SecondData + ThirdData) / 3;
} }
}
@@ -282,6 +311,27 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// 值
/// </summary>
public decimal Data { get; set; }
/// <summary>
/// 巨噬细胞浸润测量
/// </summary>
public string MacrophageInfiltrationMeasurement { get; set; }
/// <summary>
/// 巨噬细胞浸润角度测量
/// </summary>
public decimal? MacrophageInfiltrationAngle { get; set; }
/// <summary>
/// 微通道测量
/// </summary>
public string MicrochannelMeasurement { get; set; }
/// <summary>
/// 胆固醇结晶测量
/// </summary>
public string CholesterolCrystalMeasurement { get; set; }
}
public class FileToDataTableDto
@@ -1,4 +1,4 @@
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Domain.Share;
using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -143,6 +143,11 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ShowChartType ShowChartTypeEnum { get; set; } = ShowChartType.NotShow;
/// <summary>
/// 选项类型
/// </summary>
public OptionType OptionTypeEnum { get; set; } = OptionType.Defalut;
/// <summary>
/// 高亮问题的答案
/// </summary>
@@ -170,6 +175,8 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public string Answer { get; set; }
public bool IsHighlight { get; set; } = false;
public bool IsGlobalChange { get; set; } = false;
/// <summary>
@@ -191,9 +198,9 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public GetReportVerifyOutDto CalculateResult { get; set; }
public ReadingTaskState ReadingTaskState { get; set; }
public List<VisitTaskInfo> VisitTaskList { get; set; }
public List<VisitTaskInfo> VisitTaskList { get; set; } =new List<VisitTaskInfo> { };
public List<ReadingReportDto> TaskQuestions { get; set; }
public List<ReadingReportDto> TaskQuestions { get; set; } =new List<ReadingReportDto> { };
public List<LesionDto> LesionCountList { get; set; } = new List<LesionDto>();
@@ -484,6 +491,56 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public List<string> Value { get; set; }
}
public class GetReportsChartTypeDataInDto
{
public ReportChartType ReportChartTypeEnum { get; set; }
public CriterionType CriterionType { get; set; }
public GetReadingReportEvaluationOutDto Data { get; set; }
}
public class GetReportsChartSummaryInDto
{
public Guid SubjectId { get; set; }
public Guid TrialCriterionId { get; set; }
}
public class GetReportsChartSummaryOutDto
{
public EvaluationColumn Evaluation { get; set; }
public List<ChartItem> TargetCharts { get; set; } = new List<ChartItem>();
public List<ChartItem> QuestionCharts { get; set; } = new List<ChartItem>();
}
public class EvaluationColumn
{
public List<List<EvaluationValue>> Evaluation { get; set; }
public string DictionaryCode { get; set; }
}
public class EvaluationValue
{
public string Value { get; set; }
public string DictionaryCode { get; set; }
}
public class ChartItem
{
public Arm Arm { get; set; }
public ReportChartType? ChartType { get; set; }
public string GroupName { get; set; }
public ValueUnit? Unit { get; set; }
public List<string> VisitTaskNameList { get; set; } = new List<string>();
public List<DateTime?> LatestScanDateList { get; set; } = new List<DateTime?>();
public List<ReportChartData> ChartDataList { get; set; } = new List<ReportChartData>();
}
public class GetReportsChartDataInDto
{
public Guid VisitTaskId { get; set; }
@@ -954,6 +1011,9 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public class CopyTableAnswerRowInfo : ReadingTableAnswerRowInfoBase
{
public Guid? IdentityRowId { get; set; }
public Guid OriginalId { get; set; }
public LesionType? LesionType { get; set; }
@@ -1514,6 +1574,19 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public string Answer { get; set; }
/// <summary>
/// 数值类型
/// </summary>
public ValueOfType? ValueType { get; set; }
/// <summary>
/// 自定义单位
/// </summary>
public string? CustomUnit { get; set; }
public ValueUnit? Unit { get; set; }
/// <summary>
/// 问题类型
@@ -1855,6 +1928,13 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ValueOfType? ValueType { get; set; }
/// <summary>
/// 自定义单位
/// </summary>
public string? CustomUnit { get; set; }
/// <summary>
/// 类型
/// </summary>
@@ -2305,6 +2385,17 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public ValueUnit? Unit { get; set; }
/// <summary>
/// 自定义单位
/// </summary>
public string? CustomUnit { get; set; }
/// <summary>
/// 数值类型
/// </summary>
public ValueOfType? ValueType { get; set; }
public JudgeReadingQuestionType QuestionType { get; set; } = JudgeReadingQuestionType.Question;
}
@@ -343,6 +343,8 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public List<Domain.Models.OSSImageInfo> FileList { get; set; } = new List<Domain.Models.OSSImageInfo>();
//存储最后翻译的内容
public string ResultContent { get; set; }
}
@@ -631,6 +633,7 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public string TypeValue { get; set; }
/// <summary>
/// 是否启用
/// </summary>
@@ -756,6 +759,19 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public string Answer { get; set; }
/// <summary>
/// 数值类型
/// </summary>
public ValueOfType? ValueType { get; set; }
/// <summary>
/// 自定义单位
/// </summary>
public string? CustomUnit { get; set; }
public ValueUnit? Unit { get; set; }
/// <summary>
/// 排序
/// </summary>
@@ -437,6 +437,11 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ShowChartType ShowChartTypeEnum { get; set; } = ShowChartType.NotShow;
/// <summary>
/// 选项类型
/// </summary>
public OptionType OptionTypeEnum { get; set; } = OptionType.Defalut;
public Guid? Id { get; set; }
@@ -572,6 +577,11 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ShowChartType ShowChartTypeEnum { get; set; } = ShowChartType.NotShow;
/// <summary>
/// 选项类型
/// </summary>
public OptionType OptionTypeEnum { get; set; } = OptionType.Defalut;
/// <summary>
/// 单位
/// </summary>
@@ -973,6 +983,10 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ImageMark? ImageMarkEnum { get; set; }
public string QuestionGroupName { get; set; }
public string QuestionGroupEnName { get; set; }
/// <summary>
/// 影像工具
/// </summary>
@@ -1257,6 +1271,11 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ShowChartType ShowChartTypeEnum { get; set; } = ShowChartType.NotShow;
/// <summary>
/// 选项类型
/// </summary>
public OptionType OptionTypeEnum { get; set; } = OptionType.Defalut;
/// <summary>
/// 问题英文名称
/// </summary>
@@ -1373,6 +1392,11 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ShowChartType ShowChartTypeEnum { get; set; } = ShowChartType.NotShow;
/// <summary>
/// 选项类型
/// </summary>
public OptionType OptionTypeEnum { get; set; } = OptionType.Defalut;
/// <summary>
/// 单位
/// </summary>
@@ -1843,6 +1867,11 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ShowChartType ShowChartTypeEnum { get; set; } = ShowChartType.NotShow;
/// <summary>
/// 选项类型
/// </summary>
public OptionType OptionTypeEnum { get; set; } = OptionType.Defalut;
[Comment("限制编辑")]
public LimitEdit LimitEdit { get; set; } = LimitEdit.None;
@@ -2254,6 +2283,11 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ShowChartType ShowChartTypeEnum { get; set; } = ShowChartType.NotShow;
/// <summary>
/// 选项类型
/// </summary>
public OptionType OptionTypeEnum { get; set; } = OptionType.Defalut;
/// <summary>
/// 单位
/// </summary>
@@ -2666,6 +2700,11 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
/// </summary>
public ShowChartType ShowChartTypeEnum { get; set; } = ShowChartType.NotShow;
/// <summary>
/// 选项类型
/// </summary>
public OptionType OptionTypeEnum { get; set; } = OptionType.Defalut;
/// <summary>
/// 单位
/// </summary>
@@ -250,6 +250,9 @@ namespace IRaCIS.Core.Application.Service
var visitTaskAnswer = await _readingTaskQuestionAnswerRepository.Where(x => taskIds.Contains(x.VisitTaskId) && x.ReadingQuestionTrial.IsJudgeQuestion).Select(x => new JudgeQuestionAnswerInfo
{
Answer = x.Answer,
Unit=x.ReadingQuestionTrial.Unit,
ValueType=x.ReadingQuestionTrial.ValueType,
CustomUnit=x.ReadingQuestionTrial.CustomUnit,
VisitTaskId = x.VisitTaskId,
DictionaryCode = x.ReadingQuestionTrial.DictionaryCode,
ShowOrder = x.ReadingQuestionTrial.ShowOrder,
@@ -338,6 +341,7 @@ namespace IRaCIS.Core.Application.Service
ShowOrder = data.ShowOrder,
ParentId = data.ParentId,
TypeValue = data.TypeValue,
Answer = leftquestionAnswer.Answer
};
@@ -565,7 +565,9 @@ namespace IRaCIS.Core.Application.Service
{
if (indto.Id != null)
{
if (await _readingQuestionTrialRepository.AnyAsync(x => x.Id == indto.Id && x.IsJudgeQuestion && x.JudgeType != JudgeTypeEnum.None))
var isArbitrationReading = await _readingQuestionCriterionTrialRepository.Where(x => x.Id == indto.ReadingQuestionCriterionTrialId).Select(x => x.IsArbitrationReading).FirstOrDefaultAsync();
if (isArbitrationReading && (await _readingQuestionTrialRepository.AnyAsync(x => x.Id == indto.Id && x.IsJudgeQuestion && x.JudgeType != JudgeTypeEnum.None)))
{
//---当前问题已经设置了裁判标准了,修改失败
throw new BusinessValidationFailedException(_localizer["ReadingQuestion_JudgmentSet"]);
@@ -269,7 +269,8 @@ namespace IRaCIS.Core.Application.Service
ShowOrder = y.ReadingQuestionTrial.ShowOrder,
Type = y.ReadingQuestionTrial.Type,
TypeValue = y.ReadingQuestionTrial.TypeValue,
ValueType = y.ReadingQuestionTrial.ValueType,
CustomUnit = y.ReadingQuestionTrial.CustomUnit,
Unit = y.ReadingQuestionTrial.Unit,
IsJudgeQuestion = y.ReadingQuestionTrial.IsJudgeQuestion,
Answer = y.Answer,
}).ToList()
@@ -313,7 +314,7 @@ namespace IRaCIS.Core.Application.Service
ValueType = y.ValueType,
IsJudgeQuestion = y.IsJudgeQuestion,
Answer = string.Empty,
CustomUnit = y.CustomUnit,
});
}
@@ -399,7 +400,7 @@ namespace IRaCIS.Core.Application.Service
JudgeType = lr.question.JudgeType,
TypeValue = lr.question.TypeValue,
ValueType = lr.question.ValueType,
CustomUnit = lr.question.CustomUnit,
}).ToList();
@@ -524,7 +525,7 @@ namespace IRaCIS.Core.Application.Service
ShowOrder = x.Dictionary.ShowOrder,
ParentCode = x.Dictionary.Parent.Code,
Value = x.Dictionary.Value,
ValueCN = x.Dictionary.ValueCN
ValueCN = x.Dictionary.ValueCN,
}).OrderBy(x => x.ParentCode).ThenBy(x => x.ShowOrder).ToListAsync();
result.AssessTypeList = assessTypeList;
@@ -1,4 +1,5 @@
using DocumentFormat.OpenXml;
using Amazon.Runtime.Internal.Transform;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Drawing.Charts;
using DocumentFormat.OpenXml.Office2019.Excel.ThreadedComments;
using IRaCIS.Core.Application.Contracts;
@@ -93,6 +94,17 @@ namespace IRaCIS.Core.Application.Service
return await _readingCalculateService.GetReportsChartData(inDto);
}
/// <summary>
/// 获取图表数据汇总
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<GetReportsChartSummaryOutDto> GetReportsChartSummary(GetReportsChartSummaryInDto inDto)
{
return await _readingCalculateService.GetReportsChartSummary(inDto);
}
/// <summary>
/// 设置已阅读关键文件
/// </summary>
@@ -463,7 +475,6 @@ namespace IRaCIS.Core.Application.Service
.WhereIf(userType == UserTypeEnum.IndependentReviewer, t => t.FileType.Code == "2" || t.FileType.Code == "6")
.WhereIf(userType == UserTypeEnum.IQC, t => t.FileType.Code == "4" || t.FileType.Code == "5")
.WhereIf(!canViewUserType.Contains(userType), t => false)
.IgnoreQueryFilters()
.Select(x => new GetManualListOutDto()
{
Id = x.Id,
@@ -1777,6 +1788,8 @@ namespace IRaCIS.Core.Application.Service
answers.Add("CTSeriesId", rowInfo == null ? string.Empty : rowInfo.CTSeriesId.ToString());
answers.Add("SeriesId", rowInfo == null ? string.Empty : rowInfo.SeriesId.ToString());
answers.Add("IsCurrentTaskAdd", rowInfo == null ? false.ToString() : rowInfo.IsCurrentTaskAdd.ToString());
answers.Add("SplitRowId", rowInfo == null ? string.Empty : rowInfo.SplitRowId==null?string.Empty: rowInfo.SplitRowId.ToString());
answers.Add("MergeRowId", rowInfo == null ? string.Empty : rowInfo.MergeRowId == null ? string.Empty : rowInfo.MergeRowId.ToString());
answers.Add("SplitOrMergeLesionName", rowInfo == null ? string.Empty : rowInfo.SplitName.IsNullOrEmpty() ? rowInfo.MergeName : rowInfo.SplitName);
answers.Add("SplitOrMergeType", rowInfo == null ? string.Empty : rowInfo.SplitOrMergeType == null ? string.Empty : ((int)rowInfo.SplitOrMergeType).ToString());
@@ -1959,24 +1972,70 @@ namespace IRaCIS.Core.Application.Service
var mark = mergeRow.ReadingQuestionTrial.OrderMark + mergeRow.RowIndex.GetLesionMark();
await _readingTableQuestionAnswerRepository.BatchUpdateNoTrackingAsync(x => inDto.MergeRowIdList.Contains(x.RowId) && x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.State, x => new ReadingTableQuestionAnswer()
{
Answer = TargetState.Loss.GetEnumInt(),
});
var isLymph = ReadingYesOrNo.Yes.GetEnumInt();
await _readingTableQuestionAnswerRepository.BatchUpdateNoTrackingAsync(x => inDto.MergeRowIdList.Contains(x.RowId) && (x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.MajorAxis || x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.ShortAxis), x => new ReadingTableQuestionAnswer()
foreach (var item in inDto.MergeRowIdList)
{
Answer = "0",
});
switch (taskinfo.TrialReadingCriterion.CriterionType)
{
case CriterionType.Lugano2014:
case CriterionType.Lugano2014WithoutPET:
await _readingTableQuestionAnswerRepository.BatchUpdateNoTrackingAsync(x => x.RowId == item &&
(x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.MajorAxis || x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.ShortAxis), x => new ReadingTableQuestionAnswer()
{
Answer = "0",
});
break;
default:
//淋巴结的长短经 为0 非淋巴的 长径0 短径为空
if (await _readingTableQuestionAnswerRepository.AnyAsync(x => x.RowId == item && x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.IsLymph && x.Answer == isLymph))
{
await _readingTableQuestionAnswerRepository.BatchUpdateNoTrackingAsync(x => x.RowId == item &&
(x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.MajorAxis || x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.ShortAxis), x => new ReadingTableQuestionAnswer()
{
Answer = "0",
});
}
else
{
await _readingTableQuestionAnswerRepository.BatchUpdateNoTrackingAsync(x => x.RowId == item &&
x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.ShortAxis, x => new ReadingTableQuestionAnswer()
{
Answer = string.Empty,
});
await _readingTableQuestionAnswerRepository.BatchUpdateNoTrackingAsync(x => x.RowId == item &&
x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.MajorAxis, x => new ReadingTableQuestionAnswer()
{
Answer = "0",
});
}
break;
}
await _readingTableAnswerRowInfoRepository.UpdatePartialFromQueryAsync(x => inDto.MergeRowIdList.Contains(x.Id), x => new ReadingTableAnswerRowInfo()
{
MergeRowId = inDto.MergeMainRowId,
SplitOrMergeType = SplitOrMergeType.Merge,
});
}
await _readingTableAnswerRowInfoRepository.UpdatePartialFromQueryAsync(inDto.MergeMainRowId, x => new ReadingTableAnswerRowInfo()
await _readingTableAnswerRowInfoRepository.UpdatePartialFromQueryAsync(x => inDto.MergeRowIdList.Contains(x.Id), x => new ReadingTableAnswerRowInfo()
{
MergeRowId = inDto.MergeMainRowId,
SplitOrMergeType = SplitOrMergeType.Merge,
});
await _readingTableAnswerRowInfoRepository.BatchUpdateNoTrackingAsync(x=>x.Id== inDto.MergeMainRowId, x => new ReadingTableAnswerRowInfo()
{
SplitOrMergeType = SplitOrMergeType.MergeMain,
});
@@ -2049,6 +2108,9 @@ namespace IRaCIS.Core.Application.Service
);
break;
default:
query = query.Where(x => !x.LesionAnswerList.Any(y => y.ReadingTableQuestionTrial.QuestionMark == QuestionMark.State&&y.Answer!=string.Empty));
break;
}
@@ -3075,6 +3137,7 @@ namespace IRaCIS.Core.Application.Service
//await _readingTableQuestionAnswerRepository.BatchDeleteNoTrackingAsync(x => x.RowId == (inDto.RowId ?? default(Guid)));
//await _readingTableAnswerRowInfoRepository.BatchDeleteNoTrackingAsync(x => x.Id == (inDto.RowId ?? default(Guid)));
_mapper.Map(inDto, rowInfo);
rowInfo.IdentityRowId = rowInfo.IdentityRowId == null ? NewId.NextGuid() : rowInfo.IdentityRowId;
rowInfo.Id = inDto.RowId == null ? NewId.NextGuid() : inDto.RowId.Value;
result.RowId = rowInfo.Id;
rowInfo.IsCurrentTaskAdd = isCurrentTaskAdd;
@@ -3301,7 +3364,9 @@ namespace IRaCIS.Core.Application.Service
//验证后处理影像必须传
if (_visitTaskRepository.Any(t => t.Id == inDto.VisitTaskId && t.TrialReadingCriterion.ImageUploadEnum != ReadingImageUpload.None))
{
if (!_taskStudyRepository.Any(t => t.VisitTaskId == inDto.VisitTaskId))
// 校验当前访视存在上传的非dicom文件就可以提交了
if (!_taskStudyRepository.Any(t => t.VisitTaskId == inDto.VisitTaskId) && !_noneDicomStudyFileRepository.Any(t=>t.VisitTaskId==inDto.VisitTaskId) )
{
return ResponseOutput.NotOk(_localizer["ReadingImage_BackImageNotExist"]);
}
@@ -3775,7 +3840,7 @@ namespace IRaCIS.Core.Application.Service
task.ReadingVersionEnum = criterionInfo.ReadingVersionEnum;
task.ReadingToolList = criterionInfo.ReadingToolList;
task.IsExistUnprocessedFeedback = await _userFeedBackRepository.AnyAsync(x => x.VisitTaskId == task.VisitTaskId && x.State == 0);
task.IsViewStudyPart= visitTaskInfo.ReadingCategory==ReadingCategory.Judge|| visitTaskInfo.IsViewStudyPart;
task.IsViewStudyPart = visitTaskInfo.ReadingCategory == ReadingCategory.Judge || visitTaskInfo.IsViewStudyPart;
task.IsReadKeyFile = await _criterionKeyFileReadRepository.AnyAsync(x => x.IdentityUserId == _userInfo.IdentityUserId && x.TrialCriterionId == task.TrialReadingCriterionId);
task.IsHaveKeyFile = await _trialCriterionKeyFileRepository.AnyAsync(x => x.TrialCriterionId == task.TrialReadingCriterionId);
// 添加默认答案
@@ -265,6 +265,9 @@ namespace IRaCIS.Core.Application.Service
Answer = x.Answer,
Type = x.ReadingQuestionTrial.Type,
Unit = x.ReadingQuestionTrial.Unit,
CustomUnit= x.ReadingQuestionTrial.CustomUnit,
ValueType=x.ReadingQuestionTrial.ValueType,
QuestionId = x.ReadingQuestionTrial.Id,
QuestionName = x.ReadingQuestionTrial.QuestionName.LanguageName(x.ReadingQuestionTrial.QuestionEnName, _userInfo.IsEn_Us),
QuestionGenre = x.ReadingQuestionTrial.QuestionGenre,
@@ -314,10 +317,13 @@ namespace IRaCIS.Core.Application.Service
Answer = x.Answer,
Type = x.Type,
Unit = x.Unit,
CustomUnit=x.CustomUnit,
QuestionId = x.QuestionId!.Value,
QuestionName = x.QuestionName,
QuestionGenre = x.QuestionGenre,
DictionaryCode = x.DictionaryCode,
ValueType=x.ValueType,
}).ToList()
};
@@ -358,6 +364,7 @@ namespace IRaCIS.Core.Application.Service
QuestionName = x.QuestionName,
QuestionGenre = x.QuestionGenre,
DictionaryCode = x.DictionaryCode,
ValueType=x.ValueType,
}).ToList()
};
@@ -1,10 +1,15 @@
using Amazon.Runtime.Internal.Auth;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;
using DocumentFormat.OpenXml.Office.SpreadSheetML.Y2023.MsForms;
using DocumentFormat.OpenXml.Office2010.Excel;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore.Common;
using IRaCIS.Core.Infra.EFCore.Migrations;
using MassTransit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
@@ -99,22 +104,34 @@ namespace IRaCIS.Core.Application.Service
await _readingNoneDicomMarkRepository.AddAsync(mark);
}
await _readingNoneDicomMarkBindingRepository.BatchDeleteNoTrackingAsync(x =>
x.VisitTaskId == inDto.VisitTaskId &&
var binding = await _readingNoneDicomMarkBindingRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId &&
x.QuestionId == inDto.QuestionId &&
x.RowId == inDto.RowId &&
x.TableQuestionId == inDto.TableQuestionId);
await _readingNoneDicomMarkBindingRepository.AddAsync(new ReadingNoneDicomMarkBinding()
x.TableQuestionId == inDto.TableQuestionId).FirstOrDefaultAsync();
if (binding == null)
{
Id = NewId.NextGuid(),
VisitTaskId = inDto.VisitTaskId,
QuestionId = inDto.QuestionId,
RowId = inDto.RowId,
TableQuestionId = inDto.TableQuestionId,
NoneDicomMarkId = mark.Id,
MarkId = mark.MarkId
});
await _readingNoneDicomMarkBindingRepository.AddAsync(new ReadingNoneDicomMarkBinding()
{
Id = NewId.NextGuid(),
VisitTaskId = inDto.VisitTaskId,
QuestionId = inDto.QuestionId,
RowId = inDto.RowId,
TableQuestionId = inDto.TableQuestionId,
NoneDicomMarkId = mark.Id,
MarkId = mark.MarkId
});
}
else
{
await _readingNoneDicomMarkBindingRepository.UpdatePartialFromQueryAsync(binding.Id,x=> new ReadingNoneDicomMarkBinding()
{
NoneDicomMarkId = mark.Id,
MarkId = mark.MarkId
});
}
@@ -468,13 +485,16 @@ namespace IRaCIS.Core.Application.Service
List<NoneDicomStudyView> result = await noneDicomStudyQueryable.OrderBy(x => x.ImageDate).ThenBy(x => x.CreateTime).ToListAsync();
// 获取非DIOCM标记
var nonoDicomStudyFileIds = await _readingNoneDicomMarkRepository.Where(x => x.VisitTaskId == inDto.VisistTaskId).ToListAsync();
var markFileId = nonoDicomStudyFileIds.Select(x => x.NoneDicomFileId).ToList();
if (nonoDicomStudyFileIds.Count > 0 && taskinfo.ReadingTaskState == ReadingTaskState.HaveSigned)
{
var studyId = nonoDicomStudyFileIds.Select(x => x.StudyId).FirstOrDefault();
var noneDicomids = nonoDicomStudyFileIds.Select(x => x.NoneDicomFileId).ToList();
var noneDicomStudyViewMark = new NoneDicomStudyView() { Id = Guid.NewGuid() };
noneDicomStudyViewMark.IsCriticalSequence = true;
noneDicomStudyViewMark.NoneDicomStudyFileList = await _noneDicomStudyFileRepository.Where(x => noneDicomids.Contains(x.Id)).ProjectTo<NoneDicomStudyFileView>(_mapper.ConfigurationProvider).ToListAsync();
@@ -489,6 +509,14 @@ namespace IRaCIS.Core.Application.Service
}
foreach (var item in result)
{
foreach (var item1 in item.NoneDicomStudyFileList)
{
item1.IsBeMark= markFileId.Contains(item1.Id);
}
}
var trialInfo = await _trialRepository.Where(x => x.Id == inDto.TrialId).Select(x => new
{
@@ -503,6 +531,7 @@ namespace IRaCIS.Core.Application.Service
x.CriterionName,
x.Id,
x.ReadingTool,
x.CriterionType,
}).FirstOrDefaultAsync();
@@ -533,7 +562,8 @@ namespace IRaCIS.Core.Application.Service
IsReadingShowSubjectInfo = criterionInfo.IsReadingShowSubjectInfo,
IsExistsClinicalData = isExistsClinicalData,
TrialCriterionName = criterionInfo.CriterionName,
TrialCriterionId = criterionInfo.Id
TrialCriterionId = criterionInfo.Id,
criterionInfo.CriterionType,
});
}
@@ -202,6 +202,8 @@ namespace IRaCIS.Core.Application.Service
TypeValue = y.ReadingQuestionTrial.TypeValue,
ValueType = y.ReadingQuestionTrial.ValueType,
IsJudgeQuestion = y.ReadingQuestionTrial.IsJudgeQuestion,
Unit = y.ReadingQuestionTrial.Unit,
CustomUnit = y.ReadingQuestionTrial.CustomUnit,
Answer = y.Answer,
}).ToList()
}).ToListAsync();
@@ -285,6 +287,9 @@ namespace IRaCIS.Core.Application.Service
QuestionName = y.QuestionName,
QuestionGenre = y.QuestionGenre,
DictionaryCode = y.DictionaryCode,
Unit=y.Unit,
CustomUnit=y.CustomUnit,
ValueType = y.ValueType,
Answer = y.Answer
}).ToList();
x.IsHaveChange = result.ReadingCategory == ReadingCategory.Visit ? false : globalVisits.Where(y => x.VisitTaskId == y.VisitTaskId).SelectMany(y => y.AfterQuestionList).Any(y => y.IsHaveChange);
@@ -374,7 +374,8 @@ namespace IRaCIS.Core.Application.Service
CreateMap<AddOrUpdateReadingQuestionTrialInDto, ReadingQuestionTrial>()
.ForMember(dest => dest.CreateUserRole, opt => opt.Ignore());
CreateMap<ReadingQuestionTrial, ReadingQuestionTrialView>()
.ForMember(d => d.QuestionGroupName, u => u.MapFrom(s => s.GroupInfo == null ? s.GroupName : s.GroupInfo.GroupName))
.ForMember(d => d.QuestionGroupEnName, u => u.MapFrom(s => s.GroupInfo == null ? s.GroupEnName : s.GroupInfo.GroupEnName))
.ForMember(d => d.GroupName, u => u.MapFrom(s => s.GroupInfo == null ? s.GroupName : s.GroupInfo.GroupName))
.ForMember(d => d.GroupEnName, u => u.MapFrom(s => s.GroupInfo == null ? s.GroupEnName : s.GroupInfo.GroupEnName))
.ForMember(d => d.ParentQuestionName, u => u.MapFrom(s => s.ParentReadingQuestionTrial == null ? string.Empty : s.ParentReadingQuestionTrial.QuestionName))
@@ -1,4 +1,5 @@
using IRaCIS.Core.Application.Helper;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
@@ -16,6 +17,9 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
{
public class GeneralCalculateService(IRepository<ReadingTableQuestionAnswer> _readingTableQuestionAnswerRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<ReadingTaskQuestionMark> _readingTaskQuestionMarkRepository,
IRepository<ReadingNoneDicomMark> _readingNoneDicomMarkRepository,
IRepository<ReadingNoneDicomMarkBinding> _readingNoneDicomMarkBindingRepository,
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
ILogger<GeneralCalculateService> _logger,
IRepository<ReadingTableQuestionTrial> _readingTableQuestionTrialRepository,
@@ -62,6 +66,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
foreach (var item in questions)
{
item.QuestionName = item.QuestionName.LanguageName(item.QuestionEnName, _userInfo.IsEn_Us);
item.GroupName = item.GroupName.LanguageName(item.GroupEnName, _userInfo.IsEn_Us);
item.ReportLayType = ReportLayType.Group;
item.Childrens = _mapper.Map<List<ReadingReportDto>>(questionList.Where(x => x.GroupId == item.QuestionId).OrderBy(x => x.ShowOrder));
@@ -208,6 +213,183 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
return questions;
}
/// <summary>
/// 复制既往新病灶答案
/// </summary>
/// <returns></returns>
public async Task CopyHistoryAnswer(VisitTask taskinfo, List<ReadingTableAnswerRowInfo> tableRowList,List<ReadingTableQuestionAnswer> tableAnswerList)
{
if (_userInfo.RequestUrl == "ReadingImageTask/resetReadingTask")
{
return;
}
if (taskinfo.IsCopyLesionAnswer)
{
var historyTaskId = await _visitTaskRepository.Where(x =>
x.ReadingCategory == ReadingCategory.Visit &&
x.TrialReadingCriterionId == taskinfo.TrialReadingCriterionId &&
x.IsAnalysisCreate == taskinfo.IsAnalysisCreate &&
x.DoctorUserId == taskinfo.DoctorUserId &&
x.IsSelfAnalysis == taskinfo.IsSelfAnalysis &&
x.SubjectId == taskinfo.SubjectId &&
x.ReadingTaskState == ReadingTaskState.HaveSigned &&
x.VisitTaskNum == taskinfo.VisitTaskNum &&
x.TaskState != TaskState.Effect &&
x.ArmEnum == taskinfo.ArmEnum)
.OrderByDescending(x => x.SignTime)
.Select(x => x.Id).FirstOrDefaultAsync();
// 1.标记信息复制有问题 2. 标记
if (historyTaskId != Guid.Empty)
{
var historyTableRowList = await _readingTableAnswerRowInfoRepository.Where(x => x.VisitTaskId == historyTaskId).ToListAsync();
var historyTableAnswerList = await _readingTableQuestionAnswerRepository.Where(x => x.VisitTaskId == historyTaskId).ToListAsync();
var answerList = await _readingTaskQuestionAnswerRepository.Where(x => x.VisitTaskId == historyTaskId).ToListAsync();
var questionMarkList=await _readingTaskQuestionMarkRepository.Where(x => x.VisitTaskId == historyTaskId).ToListAsync();
var noneDicomMarkList=await _readingNoneDicomMarkRepository.Where(x => x.VisitTaskId == historyTaskId).ToListAsync();
var noneDicomMarkBindingList=await _readingNoneDicomMarkBindingRepository.Where(x => x.VisitTaskId == historyTaskId).ToListAsync();
foreach (var item in tableRowList)
{
if (item.SplitOrMergeType == SplitOrMergeType.Merged)
{
continue;
}
var historyRow = historyTableRowList.Where(x =>
x.QuestionId == item.QuestionId &&
x.RowIndex == item.RowIndex &&
x.IdentityRowId==item.IdentityRowId &&
x.IdentityRowId !=null &&
x.OrganInfoId==item.OrganInfoId
).FirstOrDefault();
if (historyRow != null)
{
item.StudyId= historyRow.StudyId;
item.SeriesId= historyRow.SeriesId;
item.InstanceId= historyRow.InstanceId;
item.OtherStudyId = historyRow.OtherStudyId;
item.OtherSeriesId = historyRow.OtherSeriesId;
item.OtherInstanceId= historyRow.OtherInstanceId;
item.MeasureData= historyRow.MeasureData.Replace(historyTaskId.ToString(),taskinfo.Id.ToString());
item.OtherMeasureData = historyRow.OtherMeasureData.Replace(historyTaskId.ToString(), taskinfo.Id.ToString());
tableAnswerList.Where(x => x.RowId == item.Id).ForEach(x =>
{
x.Answer = x.Answer.IsNullOrEmpty() ?
historyTableAnswerList.Where(y => y.RowId == historyRow.Id && y.TableQuestionId == x.TableQuestionId).Select(x => x.Answer).FirstOrDefault()??string.Empty :
x.Answer;
});
}
}
// 复制外层问题
answerList.ForEach(x =>
{
x.Id = NewId.NextGuid();
x.VisitTaskId = taskinfo.Id;
});
await _readingTaskQuestionAnswerRepository.AddRangeAsync(answerList);
// 处理标记
Dictionary<Guid,Guid> dicomKeys=new Dictionary<Guid, Guid> ();
foreach (var item in questionMarkList)
{
item.Id = NewId.NextGuid();
item.VisitTaskId = taskinfo.Id;
item.FristAddTaskId = taskinfo.Id;
if (item.MarkId != null)
{
if (dicomKeys.ContainsKey(item.MarkId.Value))
{
item.MarkId = dicomKeys[item.MarkId.Value];
item.MeasureData = item.MeasureData.Replace(historyTaskId.ToString(), taskinfo.Id.ToString()).Replace(item.MarkId.Value.ToString(), dicomKeys[item.MarkId.Value].ToString());
}
else
{
var newguid = NewId.NextGuid();
item.MeasureData = item.MeasureData.Replace(historyTaskId.ToString(), taskinfo.Id.ToString()).Replace(item.MarkId.Value.ToString(), newguid.ToString());
dicomKeys.Add(item.MarkId.Value, newguid);
item.MarkId = dicomKeys[item.MarkId.Value];
}
}
}
await _readingTaskQuestionMarkRepository.AddRangeAsync(questionMarkList);
// 处理非Dicom标记
Dictionary<Guid, Guid> noneKeys = new Dictionary<Guid, Guid>();
foreach (var item in noneDicomMarkList)
{
var newid= NewId.NextGuid();
item.VisitTaskId = taskinfo.Id;
if (item.MarkId != null)
{
if (noneKeys.ContainsKey(item.MarkId.Value))
{
item.MarkId = noneKeys[item.MarkId.Value];
item.MeasureData = item.MeasureData.Replace(historyTaskId.ToString(), taskinfo.Id.ToString()).Replace(item.MarkId.Value.ToString(), noneKeys[item.MarkId.Value].ToString());
}
else
{
var newguid = NewId.NextGuid();
item.MeasureData = item.MeasureData.Replace(historyTaskId.ToString(), taskinfo.Id.ToString()).Replace(item.MarkId.Value.ToString(), newguid.ToString());
noneKeys.Add(item.MarkId.Value, newguid);
item.MarkId = noneKeys[item.MarkId.Value];
}
}
#region DICOM绑定
// 处理非DICOM绑定
foreach (var binding in noneDicomMarkBindingList.Where(z => z.MarkId == item.MarkId))
{
binding.Id = NewId.NextGuid();
binding.VisitTaskId = taskinfo.Id;
if (binding.MarkId != null)
{
binding.NoneDicomMarkId = newid;
if (noneKeys.ContainsKey(binding.MarkId.Value))
{
binding.MarkId = noneKeys[binding.MarkId.Value];
}
}
}
#endregion
item.Id = NewId.NextGuid();
}
await _readingNoneDicomMarkRepository.AddRangeAsync(noneDicomMarkList);
await _readingNoneDicomMarkBindingRepository.AddRangeAsync(noneDicomMarkBindingList);
}
}
}
/// <summary>
/// 从上传文件中获取Datatable
@@ -350,7 +532,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
/// <returns></returns>
public async Task<ReadingCalculateDto> GetReadingCalculateDto(Guid visitTaskId)
{
var visitTask = await _visitTaskRepository.Where(x => x.Id == visitTaskId).FirstNotNullAsync();
var visitTask = await _visitTaskRepository.Where(x => x.Id == visitTaskId).Include(x=>x.SourceSubjectVisit).FirstNotNullAsync();
var criterionInfo = await _readingQuestionCriterionTrialRepository.Where(x => x.Id == visitTask.TrialReadingCriterionId).FirstNotNullAsync();
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == (visitTask.SourceSubjectVisitId ?? default(Guid))).FirstOrDefaultAsync();
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs;
using Org.BouncyCastle.Asn1.X509;
using System.Data;
using System.IO;
@@ -22,8 +23,20 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
IHttpContextAccessor httpContext,
IOSSService oSSService,
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
IStringLocalizer _localizer, IUserInfo _userInfo
IStringLocalizer _localizer, IUserInfo _userInfo,
IRepository<ReadingTableQuestionAnswer> _readingTableQuestionAnswerRepository,
IRepository<ReadingTableQuestionTrial> _readingTableQuestionTrialRepository,
IRepository<ReadingTableAnswerRowInfo> _readingTableAnswerRowInfoRepository,
IRepository<ReadingQuestionTrial> _readingQuestionTrialRepository,
IRepository<OrganInfo> _organInfoRepository,
IRepository<ReadingGlobalTaskInfo> _readingGlobalTaskInfoRepository,
IRepository<SubjectVisit> _subjectVisitRepository,
IRepository<TumorAssessment_IRECIST1Point1> _tumorAssessmentRepository,
IGeneralCalculateService _generalCalculateService,
IRepository<ReadingTaskQuestionAnswer> _readingTaskQuestionAnswerRepository,
IVisitTaskHelpeService _visitTaskHelpeService,
IMapper _mapper
) : BaseService, IReadingCalculateService
{
private ICriterionCalculateService _useCriterion;
@@ -75,14 +88,14 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
await _readingImportFileRepository.AddAsync(new ReadingImportFile()
{
FilePath = ossRelativePath,
VisitTaskId = visitTaskId,
TrialId = visitTaskInfo.TrialId,
SubjectId = visitTaskInfo.SubjectId,
SubjectVisitId = visitTaskInfo.SourceSubjectVisitId,
TrialReadingCriterionId = visitTaskInfo.TrialReadingCriterionId,
TableName= tableName,
TableName = tableName,
});
@@ -247,6 +260,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<GetReportsChartDataOutDto> GetReportsChartData(GetReportsChartDataInDto inDto)
{
var criterionId = await _visitTaskRepository.Where(x => x.Id == inDto.VisitTaskId).Select(x => x.TrialReadingCriterionId).FirstNotNullAsync();
@@ -259,77 +273,26 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
VisitTaskId = inDto.VisitTaskId,
});
GetReportsChartDataOutDto result = new GetReportsChartDataOutDto()
GetReportsChartDataOutDto result = new GetReportsChartDataOutDto()
{
VisitTaskNameList = data.VisitTaskList.Select(x => x.BlindName).ToList(),
LatestScanDateList = data.VisitTaskList.Select(x => x.LatestScanDate).ToList(),
ChartDataList =new List<ReportChartData>() { },
ChartDataList = new List<ReportChartData>() { },
};
result.VisitTaskNameList = data.VisitTaskList.Select(x => x.BlindName).ToList();
if (inDto.ReportChartTypeEnum != null)
{
switch (inDto.ReportChartTypeEnum)
var reportData = await GetReportsChartTypeData(new GetReportsChartTypeDataInDto()
{
case ReportChartType.Target:
{
// 这是病灶
var target = data.TaskQuestions.SelectMany(x => x.Childrens)
.Where(x => x.LesionType == LesionType.TargetLesion).SelectMany(x=>x.Childrens)
.ToList();
foreach (var item in target)
{
ReportChartData chartData = new ReportChartData()
{
Name = item.QuestionName,
Value= new List<string>(),
};
for (var i = 0; i < result.VisitTaskNameList.Count; i++)
{
switch (criterionType)
{
case CriterionType.RECIST1Point1:
case CriterionType.RECIST1Pointt1_MB:
case CriterionType.mRECISTHCC:
case CriterionType.IRECIST1Point1:
// 淋巴结的短径
if (item.Childrens.Any(x => x.QuestionMark == QuestionMark.IsLymph && x.Answer[i].Answer.EqEnum(ReadingYesOrNo.Yes)))
{
chartData.Value.Add(item.Childrens.Where(x => x.QuestionMark == QuestionMark.ShortAxis).Select(x => x.Answer[i].Answer).FirstOrDefault());
result.Unit = item.Childrens.Where(x => x.QuestionMark == QuestionMark.ShortAxis).Select(x => x.Unit).FirstOrDefault();
}
else
{
chartData.Value.Add(item.Childrens.Where(x => x.QuestionMark == QuestionMark.MajorAxis).Select(x => x.Answer[i].Answer).FirstOrDefault());
result.Unit = item.Childrens.Where(x => x.QuestionMark == QuestionMark.ShortAxis).Select(x => x.Unit).FirstOrDefault();
}
break;
case CriterionType.Lugano2014:
case CriterionType.Lugano2014WithoutPET:
chartData.Value.Add(item.Childrens.Where(x => x.QuestionMark == QuestionMark.PPD).Select(x => x.Answer[i].Answer).FirstOrDefault());
result.Unit = item.Childrens.Where(x => x.QuestionMark == QuestionMark.PPD).Select(x => x.Unit).FirstOrDefault();
break;
}
}
result.ChartDataList.Add(chartData);
}
}
break;
default:
break;
}
CriterionType = criterionType,
Data = data,
ReportChartTypeEnum = inDto.ReportChartTypeEnum.Value,
});
result.ChartDataList = reportData.ChartDataList;
result.Unit = reportData.Unit;
}
else if (inDto.QuestionId != null)
{
@@ -384,6 +347,431 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
return result;
}
private async Task<GetReportsChartDataOutDto> GetReportsChartTypeData(GetReportsChartTypeDataInDto inDto)
{
var visitTaskNameList = inDto.Data.VisitTaskList.Select(x => x.BlindName).ToList();
GetReportsChartDataOutDto result = new GetReportsChartDataOutDto() {
ChartDataList=new List<ReportChartData>() { },
VisitTaskNameList= visitTaskNameList,
};
switch (inDto.ReportChartTypeEnum)
{
case ReportChartType.Target:
{
// 这是病灶
var target = inDto.Data.TaskQuestions.SelectMany(x => x.Childrens)
.Where(x => x.LesionType == LesionType.TargetLesion).SelectMany(x => x.Childrens)
.ToList();
foreach (var item in target)
{
ReportChartData chartData = new ReportChartData()
{
Name = item.QuestionName,
Value = new List<string>(),
};
for (var i = 0; i < visitTaskNameList.Count; i++)
{
switch (inDto.CriterionType)
{
case CriterionType.RECIST1Point1:
case CriterionType.RECIST1Pointt1_MB:
case CriterionType.mRECISTHCC:
case CriterionType.IRECIST1Point1:
// 淋巴结的短径
if (item.Childrens.Any(x => x.QuestionMark == QuestionMark.IsLymph && x.Answer[i].Answer.EqEnum(ReadingYesOrNo.Yes)))
{
chartData.Value.Add(item.Childrens.Where(x => x.QuestionMark == QuestionMark.ShortAxis).Select(x => x.Answer[i].Answer).FirstOrDefault());
result.Unit = item.Childrens.Where(x => x.QuestionMark == QuestionMark.MajorAxis).Select(x => x.Unit).FirstOrDefault();
}
else
{
chartData.Value.Add(item.Childrens.Where(x => x.QuestionMark == QuestionMark.MajorAxis).Select(x => x.Answer[i].Answer).FirstOrDefault());
result.Unit = item.Childrens.Where(x => x.QuestionMark == QuestionMark.MajorAxis).Select(x => x.Unit).FirstOrDefault();
}
break;
case CriterionType.Lugano2014:
case CriterionType.Lugano2014WithoutPET:
chartData.Value.Add(item.Childrens.Where(x => x.QuestionMark == QuestionMark.PPD).Select(x => x.Answer[i].Answer).FirstOrDefault());
result.Unit = item.Childrens.Where(x => x.QuestionMark == QuestionMark.PPD).Select(x => x.Unit).FirstOrDefault();
break;
}
}
result.ChartDataList.Add(chartData);
}
}
break;
case ReportChartType.BaseLineTarget:
// 这是病灶
var baseTarget = inDto.Data.TaskQuestions.SelectMany(x => x.Childrens)
.Where(x => x.LesionType == LesionType.BaselineLesions).SelectMany(x => x.Childrens)
.ToList();
foreach (var item in baseTarget)
{
ReportChartData chartData = new ReportChartData()
{
Name = item.QuestionName,
Value = new List<string>(),
};
for (var i = 0; i < result.VisitTaskNameList.Count; i++)
{
chartData.Value.Add(item.Childrens.Where(x => x.QuestionMark == QuestionMark.LesionNumber).Select(x => x.Answer[i].Answer).FirstOrDefault());
result.Unit = item.Childrens.Where(x => x.QuestionMark == QuestionMark.LesionNumber).Select(x => x.Unit).FirstOrDefault();
}
result.ChartDataList.Add(chartData);
}
break;
default:
break;
}
return result;
}
/// <summary>
/// 获取报告图表汇总
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<GetReportsChartSummaryOutDto> GetReportsChartSummary(GetReportsChartSummaryInDto inDto)
{
var criterion = await _readingQuestionCriterionTrialRepository.Where(x => x.Id == inDto.TrialCriterionId).FirstNotNullAsync();
var result= new GetReportsChartSummaryOutDto();
var r1Data = await GetData(new List<Arm>() { Arm.SingleReadingArm, Arm.DoubleReadingArm1 });
var r2Data = await GetData(new List<Arm>() { Arm.DoubleReadingArm2 });
var alldata = r1Data.VisitTaskList.Count() > r2Data.VisitTaskList.Count() ? r1Data : r2Data;
var visitTaskName = alldata.VisitTaskList.Select(x => x.BlindName).ToList();
var length = alldata.VisitTaskList.Count();
// -1转为空
List<QuestionType?> negativeToString = new List<QuestionType?>()
{
QuestionType.DaysBetween,
};
async Task<GetReadingReportEvaluationOutDto> GetData(List<Arm> arms)
{
var data = new GetReadingReportEvaluationOutDto() { };
var task = await _visitTaskRepository.Where(x =>
x.SubjectId == inDto.SubjectId
&& arms.Contains(x.ArmEnum)
&& x.ReadingCategory== ReadingCategory.Visit
&& x.ReadingTaskState == ReadingTaskState.HaveSigned
&& x.TaskState == TaskState.Effect
&& x.TrialReadingCriterionId==inDto.TrialCriterionId
).OrderByDescending(x => x.VisitTaskNum).FirstOrDefaultAsync();
if (task != null)
{
data = await GetReportsChartData(new GetReadingReportEvaluationInDto()
{
TrialId = task.TrialId,
VisitTaskId = task.Id,
});
}
return data;
}
EvaluationColumn BuildEvaluationTable(GetReadingReportEvaluationOutDto r1, GetReadingReportEvaluationOutDto r2)
{
EvaluationColumn result = new EvaluationColumn()
{
Evaluation = new List<List<EvaluationValue>>() { }
};
var baseLineAnswerType= QuestionType.ExistDisease;
var visitAnswerType = QuestionType.Tumor;
switch (criterion.CriterionType)
{
case CriterionType.PCWG3:
baseLineAnswerType= QuestionType.SiteVisitForTumorEvaluation;
visitAnswerType = QuestionType.SiteVisitForTumorEvaluation;
break;
case CriterionType.Lugano2014WithoutPET:
visitAnswerType = QuestionType.CTandMRI;
break;
case CriterionType.Lugano2014:
visitAnswerType = QuestionType.ImgOncology;
break;
}
result.Evaluation.Add(visitTaskName.Select(x=> new EvaluationValue() {
Value=x
}).ToList());
var r1baseLine= r1.TaskQuestions
.SelectMany(x => x.Childrens)
.Where(x => x.QuestionType == baseLineAnswerType)
.SelectMany(x => x.Answer.Select(a => new EvaluationValue
{
DictionaryCode = x.DictionaryCode,
Value = a.Answer
}))
.ToList();
var r1data = r1.TaskQuestions
.SelectMany(x => x.Childrens)
.Where(x => x.QuestionType == visitAnswerType)
.SelectMany(x => x.Answer.Select(a => new EvaluationValue
{
DictionaryCode = x.DictionaryCode,
Value = a.Answer
}))
.ToList();
r1data = r1baseLine.Take(1).Concat(r1data.Skip(1)).ToList();
r1data= r1data.Concat(Enumerable.Repeat(new EvaluationValue() { Value = "" }, length))
.Take(length)
.ToList();
result.Evaluation.Add(r1data);
if (criterion.ReadingType == ReadingMethod.Double)
{
var r2baseLine = r2.TaskQuestions
.SelectMany(x => x.Childrens)
.Where(x => x.QuestionType == baseLineAnswerType)
.SelectMany(x => x.Answer.Select(a => new EvaluationValue
{
DictionaryCode = x.DictionaryCode,
Value = a.Answer
}))
.ToList();
var r2data = r2.TaskQuestions
.SelectMany(x => x.Childrens)
.Where(x => x.QuestionType == visitAnswerType)
.SelectMany(x => x.Answer.Select(a => new EvaluationValue
{
DictionaryCode = x.DictionaryCode,
Value = a.Answer
}))
.ToList();
r2data = r2baseLine.Take(1).Concat(r2data.Skip(1)).ToList();
r2data = r2data.Concat(Enumerable.Repeat(new EvaluationValue() { Value = "" }, length))
.Take(length)
.ToList();
result.Evaluation.Add(r2data);
}
return result;
}
List<ChartItem> BuildShowChartGroups(GetReadingReportEvaluationOutDto data, Arm arm)
{
var groups = new List<ChartItem>();
if (alldata.TaskQuestions == null || alldata.VisitTaskList == null) return groups;
foreach (var g in alldata.TaskQuestions)
{
var items = g.Childrens?.Where(c => c.ShowChartTypeEnum != ShowChartType.NotShow).ToList() ?? new List<ReadingReportDto>();
if (items.Count == 0) continue;
var chartList = new List<ReportChartData>();
foreach (var item in items)
{
var itemdata= data.TaskQuestions.SelectMany(x => x.Childrens)
.Where(x => x.QuestionId == item.QuestionId)
.FirstOrDefault();
var cd = new ReportChartData
{
Name = item?.QuestionName??string.Empty,
Value = itemdata?.Answer?.Select(a => a.Answer).ToList() ?? new List<string>()
};
if (negativeToString.Contains(item.QuestionType))
{
cd.Value = cd.Value.Select(item => item == "-1"|| item == "-2" ? string.Empty : item).ToList();
}
chartList.Add(cd);
}
groups.Add(new ChartItem
{
Arm = arm,
ChartType = null,
GroupName = g.GroupName,
Unit = items.FirstOrDefault()?.Unit,
VisitTaskNameList = alldata.VisitTaskList.Select(x => x.BlindName).ToList(),
LatestScanDateList = alldata.VisitTaskList.Select(x => x.LatestScanDate).ToList(),
ChartDataList = chartList
});
}
return groups;
}
async Task<ChartItem> BuildTargetChart(GetReadingReportEvaluationOutDto data, Arm arm)
{
var item = new ChartItem
{
Arm = arm,
ChartType = ReportChartType.Target,
GroupName = _localizer["Tumor_TargetLesions"],
VisitTaskNameList = alldata.VisitTaskList?.Select(x => x.BlindName).ToList() ?? new List<string>(),
LatestScanDateList = alldata.VisitTaskList?.Select(x => x.LatestScanDate).ToList() ?? new List<DateTime?>(),
ChartDataList = new List<ReportChartData>(),
};
if (data.VisitTaskList != null && data.TaskQuestions != null)
{
var chart = await GetReportsChartTypeData(new GetReportsChartTypeDataInDto
{
CriterionType = criterion.CriterionType,
Data = data,
ReportChartTypeEnum = ReportChartType.Target
});
item.ChartDataList = chart.ChartDataList;
item.Unit = chart.Unit;
}
return item;
}
result.Evaluation = BuildEvaluationTable(r1Data, r2Data);
if (r1Data != null)
{
var r1Target = await BuildTargetChart(r1Data, Arm.DoubleReadingArm1);
result.TargetCharts.Add(r1Target);
var r1Groups = BuildShowChartGroups(r1Data, Arm.DoubleReadingArm1);
result.QuestionCharts.AddRange(r1Groups);
}
if (r2Data != null)
{
var r2Target = await BuildTargetChart(r2Data, Arm.DoubleReadingArm2);
result.TargetCharts.Add(r2Target);
var r2Groups = BuildShowChartGroups(r2Data, Arm.DoubleReadingArm2);
result.QuestionCharts.AddRange(r2Groups);
}
if (criterion.CriterionType == CriterionType.PCWG3)
{
result.TargetCharts = new List<ChartItem>() { };
}
return result;
}
/// <summary>
/// 获取报表数据
/// </summary>
/// <param name="indto"></param>
/// <returns></returns>
private async Task<GetReadingReportEvaluationOutDto> GetReportsChartData(GetReadingReportEvaluationInDto indto)
{
GetReadingReportEvaluationOutDto result = new GetReadingReportEvaluationOutDto();
var isConvertTask = await _visitTaskRepository.Where(x => x.Id == indto.VisitTaskId).Select(x => x.IsConvertedTask).FirstNotNullAsync();
var visitTaskInfo = await _visitTaskRepository.Where(x => x.Id == indto.VisitTaskId).FirstNotNullAsync();
result.CalculateResult = await this.GetReportVerify(new GetReportVerifyInDto()
{
VisitTaskId = indto.VisitTaskId,
IsConvertTask = isConvertTask,
BeforeConvertedTaskId = visitTaskInfo.BeforeConvertedTaskId,
});
result.ReadingTaskState = visitTaskInfo.ReadingTaskState;
var taskInfoList = await _generalCalculateService.GetReadingReportTaskList(indto.VisitTaskId);
result.VisitTaskList = taskInfoList;
var visitTaskIds = taskInfoList.Select(x => x.VisitTaskId).ToList();
var criterionId = visitTaskInfo.TrialReadingCriterionId;
var questionList = await _readingQuestionTrialRepository.Where(x => x.ReadingQuestionCriterionTrialId == criterionId)
.ToListAsync();
var tableQuestionList = await _readingTableQuestionTrialRepository.Where(x => x.TrialCriterionId == criterionId).OrderBy(x => x.ShowOrder).ToListAsync();
var tableAnsweRowInfos = await _readingTableAnswerRowInfoRepository.Where(x => x.VisitTaskId == indto.VisitTaskId).ProjectTo<TableAnsweRowInfo>(_mapper.ConfigurationProvider).ToListAsync();
result.LesionCountList = tableAnsweRowInfos.GroupBy(x => x.LesionType).Select(x => new LesionDto
{
LesionType = x.Key!.Value,
Count = x.ToList().Count()
}).ToList();
var lesionsIndexs = await _readingTableAnswerRowInfoRepository.Where(x => visitTaskIds.Contains(x.VisitTaskId)).GroupBy(x => new { x.QuestionId }).Select(x => new lesionsIndexDto()
{
QuestionId = x.Key.QuestionId,
Rowindexs = x.Select(x => x.RowIndex).Distinct().OrderBy(x => x).ToList()
}).ToListAsync();
var answers = await _readingTaskQuestionAnswerRepository.Where(x => visitTaskIds.Contains(x.VisitTaskId)).ToListAsync();
var tableAnswers = await _readingTableQuestionAnswerRepository.Where(x => visitTaskIds.Contains(x.VisitTaskId)).ToListAsync();
var globalanswerList = await _readingGlobalTaskInfoRepository.Where(x => visitTaskIds.Contains(x.TaskId) && x.GlobalVisitTask.TaskState == TaskState.Effect && x.Answer != string.Empty).Select(x => new Globalanswer()
{
TaskId = x.TaskId,
VisitTaskNum = x.GlobalVisitTask.VisitTaskNum,
QuestionId = x.QuestionId,
Answer = x.Answer
}).ToListAsync();
var alltableAnsweRowInfos = await _readingTableAnswerRowInfoRepository.Where(x => visitTaskIds.Contains(x.VisitTaskId)).ToListAsync();
var organIds = alltableAnsweRowInfos.Where(x => x.OrganInfoId != null).Select(x => x.OrganInfoId).Distinct().ToList();
var organInfos = await _organInfoRepository.Where(x => organIds.Contains(x.Id)).ToListAsync();
var needChangeType = new List<QuestionMark?>() {
QuestionMark.Organ,
QuestionMark.Location,
QuestionMark.Part,
};
// 第一级
List<ReadingReportDto> questions = await _generalCalculateService.GetReadingReportQuestion
(
questionList,
taskInfoList,
globalanswerList,
answers,
tableAnsweRowInfos,
tableQuestionList,
alltableAnsweRowInfos,
tableAnswers,
organInfos,
needChangeType
);
result.TaskQuestions = questions;
return result;
}
/// <summary>
/// 获取阅片报告
/// </summary>
@@ -408,6 +796,39 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
SetCrterionDictionaryGroup(y, dictionaryGroup);
});
// 处理标红 高亮
#region N个 NE的单个靶病灶 N+1 访
var index = result.TaskQuestions.SelectMany(x => x.Childrens).Where(x => x.QuestionType == QuestionType.NETarget).SelectMany(x => x.Answer).ToList().FindIndex(x => x.Answer == ExistOrNA.Exist.GetEnumInt());
if (index >= 0)
{
result.TaskQuestions.ForEach(x =>
{
x.Childrens.ForEach(y =>
{
if (y.QuestionType == QuestionType.LowVisit)
{
int idx = 0;
y.Answer.ForEach(z =>
{
if (idx > index)
{
z.IsHighlight = true;
}
idx++;
});
}
});
});
}
#endregion
return result;
}
@@ -633,7 +633,9 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
string errorMassage = string.Empty;
var rowAnswerList = await _readingTableAnswerRowInfoRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId && (x.MeasureData == string.Empty || x.MeasureData == null))
var rowAnswerList = await _readingTableAnswerRowInfoRepository.Where(x =>
x.SplitOrMergeType!= SplitOrMergeType.Merge &&x.SplitOrMergeType != SplitOrMergeType.Merged &&
x.VisitTaskId == inDto.VisitTaskId && (x.MeasureData == string.Empty || x.MeasureData == null))
.Select(x => new
{
x.ReadingQuestionTrial.OrderMark,
@@ -661,30 +663,33 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
List<string> stateIsNullList = tableAnswerList.Select(x => x.OrderMark + x.RowIndex.GetLesionMark()).ToList();
// 交集
List<string> allExists = measureDataList.Intersect(stateIsNullList).ToList();
// 差集
measureDataList = measureDataList.Except(allExists).ToList();
stateIsNullList = stateIsNullList.Except(allExists).ToList();
if (measureDataList.Count() > 0)
{
// 无标记
errorMassage += _localizer["ReadingCalculate_NoMarker", string.Join(',', measureDataList)] + ",";
}
if (tableAnswerList.Count > 0)
if (stateIsNullList.Count > 0)
{
// 状态为空
errorMassage += _localizer["ReadingCalculate_StatusIsEmpty", string.Join(',', stateIsNullList)] + ",";
}
if (allExists.Count > 0)
{
errorMassage += _localizer["ReadingCalculate_NoMarkerEmpty", string.Join(',', stateIsNullList)] + ",";
// 未做标记且状态为空
errorMassage += _localizer["ReadingCalculate_NoMarkerEmpty", string.Join(',', allExists)] + ",";
}
if (errorMassage != string.Empty)
{
errorMassage = _localizer["ReadingCalculate_Questionable"] + errorMassage;
throw new BusinessValidationFailedException(errorMassage);
}
@@ -704,7 +709,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
{
var visitTaskId = inDto.VisitTaskId;
var taskinfo = await _visitTaskRepository.Where(x => x.Id == visitTaskId).ProjectTo<VisitTaskDto>(_mapper.ConfigurationProvider).FirstNotNullAsync();
var taskinfo = await _visitTaskRepository.Where(x => x.Id == visitTaskId).FirstNotNullAsync();
var baseLineVisitId = await _subjectVisitRepository.Where(x => x.SubjectId == taskinfo.SubjectId && x.IsBaseLine).Select(x => x.Id).FirstOrDefaultAsync();
@@ -748,6 +753,20 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
tableRowAnswers.ForEach(x =>
{
switch (x.SplitOrMergeType)
{
case SplitOrMergeType.Merge:
case SplitOrMergeType.Merged:
x.SplitOrMergeType = SplitOrMergeType.Merged;
break;
case SplitOrMergeType.MergeMain:
case SplitOrMergeType.MergeMained:
x.SplitOrMergeType = SplitOrMergeType.MergeMained;
break;
default:
x.SplitOrMergeType = null;
break;
}
x.VisitTaskId = visitTaskId;
x.IsCurrentTaskAdd = false;
x.Id = NewId.NextGuid();
@@ -779,24 +798,35 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
// QuestionMark.ShortAxis,
//};
//}
var tableAnswers = copyTableAnswers.Select(x => new ReadingTableQuestionAnswer
{
Id = NewId.NextGuid(),
Answer = notNeedCopyMarks.Contains(x.QuestionMark) ? string.Empty : x.Answer,
QuestionId = x.QuestionId,
RowIndex = x.RowIndex,
RowId = tableRowAnswers.Where(y => y.OriginalId == x.RowId).Select(x => x.Id).FirstOrDefault(),
TableQuestionId = x.TableQuestionId,
TrialId = x.TrialId,
VisitTaskId = visitTaskId,
});
var mergedRowIds = tableRowAnswers.Where(x => x.SplitOrMergeType == SplitOrMergeType.Merged).Select(x => x.Id).ToHashSet();
var tableAnswers = copyTableAnswers
.Select(x =>
{
var rowId = tableRowAnswers.Where(y => y.OriginalId == x.RowId).Select(y => y.Id).FirstOrDefault();
var answer = notNeedCopyMarks.Contains(x.QuestionMark) ? string.Empty : x.Answer;
if (mergedRowIds.Contains(rowId) && (x.QuestionMark == QuestionMark.State || x.QuestionMark == QuestionMark.ShortAxis || x.QuestionMark == QuestionMark.MajorAxis))
{
answer = x.Answer;
}
return new ReadingTableQuestionAnswer
{
Id = NewId.NextGuid(),
Answer = answer,
QuestionId = x.QuestionId,
RowIndex = x.RowIndex,
RowId = rowId,
TableQuestionId = x.TableQuestionId,
TrialId = x.TrialId,
VisitTaskId = visitTaskId,
};
})
.ToList();
var addList = _mapper.Map<List<ReadingTableAnswerRowInfo>>(tableRowAnswers);
await _generalCalculateService.CopyHistoryAnswer(taskinfo, addList, tableAnswers);
await _readingTableAnswerRowInfoRepository.AddRangeAsync(addList);
await _readingTableQuestionAnswerRepository.AddRangeAsync(tableAnswers);
await _readingTableQuestionAnswerRepository.SaveChangesAsync();
@@ -1110,7 +1140,8 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate
return ExistOrNA.NA.GetEnumInt();
}
var result = inDto.QuestionInfo.Any(x => x.QuestionType == QuestionType.TargetLesion && x.Answer.EqEnum(TargetAssessment.NE));
var result = inDto.QuestionInfo.Where(x => x.LesionType == LesionType.TargetLesion).SelectMany(x => x.TableRowInfoList).ToList().Any(x => x.TableQuestionList.Any(y =>
y.QuestionMark == QuestionMark.State && y.Answer.EqEnum(TargetState.UnableEvaluate)));
return result ? ExistOrNA.Exist.GetEnumInt() : ExistOrNA.NotExist.GetEnumInt();
}

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