陈昶聿
2 天以前 d9281d43e27ab6c510d390ddb26eaffc8ccc1add
妇幼-补充住院随访档案生成日志、完成事件及自检文件
已添加11个文件
1323 ■■■■■ 文件已修改
doc/api/妇保接口文档.md 646 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/event/FollowupCompletedEvent.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-task/src/main/java/com/ruoyi/task/event/FollowupCompletedEventPublisher.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/PatSpecificArchiveGenerateLog.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/listener/InhospFollowupArchiveListener.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/mapper/PatSpecificArchiveGenerateLogMapper.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/IPatSpecificArchiveGenerateLogService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/PatSpecificArchiveGenerateLogServiceImpl.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/PatSpecificArchiveGenerateLogMapper.xml 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/test/java/com/smartor/service/InhospFollowupArchiveLogicCheck.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/pat_specific_archive_generate_log_and_source_20261111.sql 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/api/¸¾±£½Ó¿ÚÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,646 @@
# å¦‡ä¿æŽ¥å£æ–‡æ¡£ï¼ˆå¦‡ç§‘ / å„¿ç§‘档案)
> æœ¬æ–‡æ¡£ä»…包含以下 8 ä¸ªæŽ¥å£ï¼š
| åºå· | æ¨¡å— | æŽ¥å£ | æ–¹æ³• | è¯´æ˜Ž |
| --- | --- | --- | --- | --- |
| 1 | å¦‡ç§‘档案 | `/smartor/maternityArchive/selectPatMaternityArchiveList` | POST | æŸ¥è¯¢å¦‡ç§‘档案列表 |
| 2 | å¦‡ç§‘档案 | `/smartor/maternityArchive/insertOrUpdateMaternityArchive` | POST | æ–°å¢žæˆ–修改妇科档案 |
| 3 | å¦‡ç§‘档案 | `/smartor/maternityArchive/batchDelete` | POST | æ‰¹é‡åˆ é™¤å¦‡ç§‘档案(含详情) |
| 4 | å¦‡ç§‘档案详情 | `/smartor/maternityArchiveDetail/getInfo/{archiveid}` | GET | èŽ·å–å¦‡ç§‘æ¡£æ¡ˆè¯¦æƒ… |
| 5 | å„¿ç§‘档案 | `/smartor/childArchive/selectPatChildArchiveList` | POST | æŸ¥è¯¢å„¿ç§‘档案列表 |
| 6 | å„¿ç§‘档案 | `/smartor/childArchive/insertOrUpdateChildArchive` | POST | æ–°å¢žæˆ–修改儿科档案 |
| 7 | å„¿ç§‘档案 | `/smartor/childArchive/batchDelete` | POST | æ‰¹é‡åˆ é™¤å„¿ç§‘档案(含详情) |
| 8 | å„¿ç§‘档案详情 | `/smartor/childArchiveDetail/getInfo/{archiveid}` | GET | èŽ·å–å„¿ç§‘æ¡£æ¡ˆè¯¦æƒ… |
---
## ä¸€ã€é€šç”¨è¯´æ˜Ž
### 1.1 æœåŠ¡åœ°å€
- å‰ç¼€ï¼š`http://服务IP:端口`(默认开发环境为 `http://localhost:8080`)
### 1.2 è®¤è¯æ–¹å¼
- æ‰€æœ‰æŽ¥å£å‡éœ€ç™»å½•后调用。
- è¯·æ±‚头携带:`Authorization: Bearer {token}`
### 1.3 è¯·æ±‚与响应格式
- è¯·æ±‚体:`Content-Type: application/json`
- åˆ—表接口响应结构(TableDataInfo):
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
| --- | --- | --- |
| total | long | æ€»è®°å½•æ•° |
| rows | array | æ•°æ®åˆ—表 |
| code | int | çŠ¶æ€ç ï¼Œ200 ä¸ºæˆåŠŸ |
| msg | string | æç¤ºä¿¡æ¯ï¼Œå¦‚“查询成功” |
- éžåˆ—表接口响应结构(AjaxResult):
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
| --- | --- | --- |
| code | int | çŠ¶æ€ç ï¼Œ200 ä¸ºæˆåŠŸï¼Œ500 ä¸ºå¤±è´¥ |
| msg | string | æç¤ºä¿¡æ¯ï¼Œå¦‚“操作成功”、“操作失败” |
| data | object | ä¸šåŠ¡æ•°æ®ï¼ˆå¯èƒ½ä¸ºç©ºï¼‰ |
---
## äºŒã€å¦‡ç§‘档案接口
### 2.1 æŸ¥è¯¢å¦‡ç§‘档案列表
- **URL**:`POST /smartor/maternityArchive/selectPatMaternityArchiveList`
- **说明**:分页查询妇科档案主表列表(未删除数据 `del_flag = '0'`)。
**请求体(JSON,全部可选)**
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| pageNum | Integer | å¦ | é¡µç ï¼ˆåˆ†é¡µæ—¶å¿…填) |
| pageSize | Integer | å¦ | æ¯é¡µæ¡æ•°ï¼ˆåˆ†é¡µæ—¶å¿…填) |
| archiveType | String | å¦ | æ¡£æ¡ˆç±»åž‹ï¼š1 æ—©å­•关爱,2 å›´ç»ç»æœŸMHI |
| outhospno | String | å¦ | é—¨è¯Šç¼–号(精确匹配) |
| inhospno | String | å¦ | ä½é™¢ç¼–号(精确匹配) |
| serialnum | String | å¦ | æµæ°´å·ï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
| patid | Long | å¦ | æ‚£è€…ID |
| patname | String | å¦ | æ‚£è€…姓名(模糊匹配) |
| parentname | String | å¦ | å®¶é•¿å§“名(模糊匹配) |
| birthdate | Date | å¦ | å‡ºç”Ÿæ—¥æœŸ |
| idcardno | String | å¦ | è¯ä»¶å·ç ï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
| phone | String | å¦ | è”系电话(精确匹配) |
| diagTime | Date | å¦ | è¯Šæ–­æ—¶é—´ |
| plannedFollowTime | Date | å¦ | æ‹Ÿéšè®¿æ—¶é—´ |
| followupCycleTimes | String | å¦ | éšè®¿å‘¨æœŸ(次数) |
| sex | Long | å¦ | æ€§åˆ«ï¼š1 ç”·ï¼Œ2 å¥³ |
| doctorCode | String | å¦ | ä¸»æ²»åŒ»ç”Ÿï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
| doctorName | String | å¦ | ä¸»æ²»åŒ»ç”Ÿå§“名(模糊匹配) |
| managementDoctorCode | String | å¦ | ç»ç®¡åŒ»ç”Ÿï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
| managementDoctorName | String | å¦ | ç»ç®¡åŒ»ç”Ÿå§“名(模糊匹配) |
| deptcode | String | å¦ | ç§‘室编号(精确匹配) |
| deptname | String | å¦ | ç§‘室名称(模糊匹配) |
| wardcode | String | å¦ | ç—…区编号(精确匹配) |
| wardname | String | å¦ | ç—…区名称(模糊匹配) |
| icd10code | String | å¦ | è¯Šæ–­ID(精确匹配) |
| diagname | String | å¦ | è¯Šæ–­åç§°ï¼ˆæ¨¡ç³ŠåŒ¹é…ï¼‰ |
| pregnancySituation | String | å¦ | å¦Šå¨ æƒ…况 |
| closeStatus | String | å¦ | ç»“案状态 |
| registerUser | String | å¦ | ç™»è®°äººå‘˜ |
| orgid | String | å¦ | åŒ»é™¢æœºæž„ID(后端按登录用户自动注入) |
| campusid | String | å¦ | é™¢åŒºID(后端按登录用户自动注入) |
| params.beginTime | String | å¦ | å»ºæ¡£æ—¶é—´èŒƒå›´å¼€å§‹ï¼Œæ ¼å¼ yyyy-MM-dd |
| params.endTime | String | å¦ | å»ºæ¡£æ—¶é—´èŒƒå›´ç»“束,格式 yyyy-MM-dd |
**请求示例**
```json
{
  "pageNum": 1,
  "pageSize": 10,
  "patname": "å¼ ",
  "archiveType": "1"
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "查询成功",
  "total": 1,
  "rows": [
    {
      "id": 1001,
      "archiveType": "1",
      "outhospno": "MZ20261001001",
      "inhospno": null,
      "serialnum": "LS20261001001",
      "patid": 9001,
      "patname": "张三",
      "parentname": null,
      "birthdate": "1995-06-01 00:00:00",
      "idcardno": "330102199506010022",
      "phone": "13800000000",
      "diagTime": "2026-10-01 09:30:00",
      "archiveTime": "2026-10-01 10:00:00",
      "plannedFollowTime": "2026-11-01 00:00:00",
      "followupCycleTimes": "4",
      "sex": 2,
      "doctorCode": "D001",
      "doctorName": "李医生",
      "managementDoctorCode": "D002",
      "managementDoctorName": "王医生",
      "deptcode": "FK",
      "deptname": "妇科",
      "wardcode": null,
      "wardname": null,
      "icd10code": "O20.0",
      "diagname": "先兆流产",
      "pregnancySituation": "孕12周",
      "closeStatus": "0",
      "registerUser": "李医生",
      "orgid": "1",
      "campusid": "1",
      "delFlag": "0"
    }
  ]
}
```
---
### 2.2 æ–°å¢žæˆ–修改妇科档案
- **URL**:`POST /smartor/maternityArchive/insertOrUpdateMaternityArchive`
- **说明**:同时维护妇科档案主表与详情表。
  - ä¸»è¡¨æœ‰ `archiveid`(或详情有 `id` ä¸”能反查)且存在时更新,否则新增。
  - è¿”回 `data` ä¸ºå¦‡ç§‘档案主表ID。
**请求体(JSON)**
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| id | Long | å¦ | å¦‡ç§‘档案详情主键ID(更新详情时传入) |
| archiveid | Long | å¦ | å¦‡ç§‘档案主表ID(更新主表时传入) |
| archiveType | String | å¦ | æ¡£æ¡ˆç±»åž‹ï¼š1 æ—©å­•关爱,2 å›´ç»ç»æœŸMHI |
| sourceType | String | å¦ | æ¥æºç±»åž‹ï¼š1 é—¨è¯ŠæŽ¥è¯Šï¼Œ2 ä½é™¢éšè®¿ï¼Œ3 æ‰‹å·¥æ–°å¢ž |
| sourceBizId | Long | å¦ | æ¥æºä¸šåŠ¡ID:门诊记录ID/住院记录ID |
| sourceBizNo | String | å¦ | æ¥æºä¸šåŠ¡ç¼–å·ï¼šé—¨è¯Šå·/住院号 |
| sourceSubId | Long | å¦ | æ¥æºéšè®¿å­ä»»åŠ¡ID:住院随访生成时为 service_subtask.id |
| outhospno | String | å¦ | é—¨è¯Šç¼–号 |
| inhospno | String | å¦ | ä½é™¢ç¼–号 |
| serialnum | String | å¦ | æµæ°´å· |
| patid | Long | å¦ | æ‚£è€…ID |
| patname | String | å¦ | æ‚£è€…姓名 |
| birthdate | Date | å¦ | å‡ºç”Ÿæ—¥æœŸï¼Œæ ¼å¼ yyyy-MM-dd HH:mm:ss |
| idcardno | String | å¦ | è¯ä»¶å·ç  |
| phone | String | å¦ | è”系电话 |
| diagTime | Date | å¦ | è¯Šæ–­æ—¶é—´ |
| archiveTime | Date | å¦ | å»ºæ¡£æ—¶é—´ |
| plannedFollowTime | Date | å¦ | æ‹Ÿéšè®¿æ—¶é—´ |
| sex | Long | å¦ | æ€§åˆ«ï¼š1 ç”·ï¼Œ2 å¥³ |
| doctorCode | String | å¦ | ä¸»æ²»åŒ»ç”Ÿ |
| doctorName | String | å¦ | ä¸»æ²»åŒ»ç”Ÿå§“名 |
| managementDoctorCode | String | å¦ | ç»ç®¡åŒ»ç”Ÿ |
| managementDoctorName | String | å¦ | ç»ç®¡åŒ»ç”Ÿå§“名 |
| deptcode | String | å¦ | ç§‘室编号 |
| deptname | String | å¦ | ç§‘室名称 |
| wardcode | String | å¦ | ç—…区编号 |
| wardname | String | å¦ | ç—…区名称 |
| icd10code | String | å¦ | è¯Šæ–­ID |
| diagname | String | å¦ | è¯Šæ–­åç§° |
| pregnancySituation | String | å¦ | å¦Šå¨ æƒ…况 |
| specificSituation | String | å¦ | å…·ä½“情况 |
| handleSuggestion | String | å¦ | å¤„理建议 |
| closeStatus | String | å¦ | ç»“案状态 |
| isRecordEstablished | String | å¦ | æ˜¯å¦å»ºå†Œï¼š0 å¦ï¼Œ1 æ˜¯ |
| dueDate | Date | å¦ | é¢„产期 |
| registerUser | String | å¦ | ç™»è®°äººå‘˜ |
| followupPeriod | String | å¦ | éšè®¿å‘¨æœŸ |
| followupCycleTimes | String | å¦ | éšè®¿å‘¨æœŸ(次数) |
| lastMenstrualTime | Date | å¦ | æœ«æ¬¡æœˆç» |
| firstDoctorCode | String | å¦ | é¦–诊医生 |
| firstDoctorName | String | å¦ | é¦–诊医生姓名 |
| contactPhone | String | å¦ | è”系人联系电话 |
| address | String | å¦ | å±…住地址 |
| epEarlyPregnancyFollowupForm | String | å¦ | æ—©å­•关爱-早孕随访表 |
| mhiMenopauseFollowupSystemIssuesForm | String | å¦ | å›´ç»ç»æœŸMHI-更年期随访系统问题表 |
| mhiMhtInitialVisitMedicalRecordForm | String | å¦ | å›´ç»ç»æœŸMHI-MHT首诊病历表 |
| mhiMhtFollowUpMedicalRecordForm | String | å¦ | å›´ç»ç»æœŸMHI-MHT复诊病历表 |
| mhiModifiedKuppermanScoringScaleForm | String | å¦ | å›´ç»ç»æœŸMHI-改良Kupperman评分标准表 |
| mhiMenopauseRatingScaleForm | String | å¦ | å›´ç»ç»æœŸMHI-更年期评定量表(MRS) |
| mhiPittsburghSleepQualityIndexForm | String | å¦ | å›´ç»ç»æœŸMHI-匹兹堡睡眠质量指数量表(PSQI) |
| remark | String | å¦ | å¤‡æ³¨ |
| orgid | String | å¦ | åŒ»é™¢æœºæž„ID(后端按登录用户自动覆盖) |
| campusid | String | å¦ | é™¢åŒºID(后端按登录用户自动覆盖) |
| delFlag | String | å¦ | åˆ é™¤æ ‡å¿—:0 æœªåˆ é™¤ï¼Œ1 å·²åˆ é™¤ï¼ˆé»˜è®¤ 0) |
**请求示例(新增)**
```json
{
  "archiveType": "1",
  "patid": 9001,
  "patname": "张三",
  "birthdate": "1995-06-01 00:00:00",
  "idcardno": "330102199506010022",
  "phone": "13800000000",
  "diagTime": "2026-10-01 09:30:00",
  "archiveTime": "2026-10-01 10:00:00",
  "sex": 2,
  "deptcode": "FK",
  "deptname": "妇科",
  "pregnancySituation": "孕12周",
  "closeStatus": "0"
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": 1001
}
```
---
### 2.3 æ‰¹é‡åˆ é™¤å¦‡ç§‘档案
- **URL**:`POST /smartor/maternityArchive/batchDelete`
- **说明**:按主表ID批量删除妇科档案,同时逻辑删除对应的妇科档案详情表数据(`del_flag` ç½®ä¸º 1),两步在同一事务中。
**请求体(JSON)**
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| ids | Long[] | æ˜¯ | å¦‡ç§‘档案主表ID数组 |
**请求示例**
```json
[1001, 1002, 1003]
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功"
}
```
---
### 2.4 èŽ·å–å¦‡ç§‘æ¡£æ¡ˆè¯¦æƒ…
- **URL**:`GET /smartor/maternityArchiveDetail/getInfo/{archiveid}`
- **说明**:按档案主表ID查询妇科档案详情(未删除数据 `del_flag = '0'`),返回第一条详情记录;无数据时 `data` ä¸º `null`。
**路径参数**
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| archiveid | Long | æ˜¯ | å¦‡ç§‘档案主表ID(`pat_maternity_archive.id`) |
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "id": 5001,
    "archiveid": 1001,
    "archiveType": "1",
    "sourceType": "1",
    "outhospno": "MZ20261001001",
    "inhospno": null,
    "serialnum": "LS20261001001",
    "patid": 9001,
    "patname": "张三",
    "birthdate": "1995-06-01 00:00:00",
    "idcardno": "330102199506010022",
    "phone": "13800000000",
    "diagTime": "2026-10-01 09:30:00",
    "archiveTime": "2026-10-01 10:00:00",
    "plannedFollowTime": "2026-11-01 00:00:00",
    "sex": 2,
    "doctorCode": "D001",
    "doctorName": "李医生",
    "managementDoctorCode": "D002",
    "managementDoctorName": "王医生",
    "deptcode": "FK",
    "deptname": "妇科",
    "wardcode": null,
    "wardname": null,
    "icd10code": "O20.0",
    "diagname": "先兆流产",
    "pregnancySituation": "孕12周",
    "specificSituation": "具体情况",
    "handleSuggestion": "注意休息",
    "closeStatus": "0",
    "isRecordEstablished": "1",
    "dueDate": "2027-03-20 00:00:00",
    "registerUser": "李医生",
    "followupPeriod": "每月",
    "followupCycleTimes": "4",
    "lastMenstrualTime": "2026-06-10 00:00:00",
    "firstDoctorCode": "D001",
    "firstDoctorName": "李医生",
    "contactPhone": "13900000000",
    "address": "杭州市西湖区",
    "orgid": "1",
    "campusid": "1",
    "delFlag": "0"
  }
}
```
> è¿”回对象 `data` ä¸ºå¦‡ç§‘档案详情对象,字段说明同 2.2 è¯·æ±‚体字段。
---
## ä¸‰ã€å„¿ç§‘档案接口
### 3.1 æŸ¥è¯¢å„¿ç§‘档案列表
- **URL**:`POST /smartor/childArchive/selectPatChildArchiveList`
- **说明**:分页查询儿科档案主表列表(未删除数据 `del_flag = '0'`)。
**请求体(JSON,全部可选)**
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| pageNum | Integer | å¦ | é¡µç ï¼ˆåˆ†é¡µæ—¶å¿…填) |
| pageSize | Integer | å¦ | æ¯é¡µæ¡æ•°ï¼ˆåˆ†é¡µæ—¶å¿…填) |
| archiveType | String | å¦ | æ¡£æ¡ˆç±»åž‹ï¼š1 è¥å…»æ€§ç–¾ç—…,2 å­¦ä¹ å›°éš¾ï¼Œ3 é«˜å±å„¿ä¸“案 |
| outhospno | String | å¦ | é—¨è¯Šç¼–号(精确匹配) |
| inhospno | String | å¦ | ä½é™¢ç¼–号(精确匹配) |
| serialnum | String | å¦ | æµæ°´å·ï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
| patid | Long | å¦ | æ‚£è€…ID |
| patname | String | å¦ | æ‚£è€…姓名(模糊匹配) |
| parentname | String | å¦ | å®¶é•¿å§“名(模糊匹配) |
| birthdate | Date | å¦ | å‡ºç”Ÿæ—¥æœŸ |
| idcardno | String | å¦ | è¯ä»¶å·ç ï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
| phone | String | å¦ | è”系电话(精确匹配) |
| diagTime | Date | å¦ | è¯Šæ–­æ—¶é—´ |
| plannedFollowTime | Date | å¦ | æ‹Ÿéšè®¿æ—¶é—´ |
| followupCycleTimes | String | å¦ | éšè®¿å‘¨æœŸ(次数) |
| sex | Long | å¦ | æ€§åˆ«ï¼š1 ç”·ï¼Œ2 å¥³ |
| doctorCode | String | å¦ | ä¸»æ²»åŒ»ç”Ÿï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
| doctorName | String | å¦ | ä¸»æ²»åŒ»ç”Ÿå§“名(模糊匹配) |
| managementDoctorCode | String | å¦ | ç»ç®¡åŒ»ç”Ÿï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
| managementDoctorName | String | å¦ | ç»ç®¡åŒ»ç”Ÿå§“名(模糊匹配) |
| deptcode | String | å¦ | ç§‘室编号(精确匹配) |
| deptname | String | å¦ | ç§‘室名称(模糊匹配) |
| wardcode | String | å¦ | ç—…区编号(精确匹配) |
| wardname | String | å¦ | ç—…区名称(模糊匹配) |
| icd10code | String | å¦ | è¯Šæ–­ID(精确匹配) |
| diagname | String | å¦ | è¯Šæ–­åç§°ï¼ˆæ¨¡ç³ŠåŒ¹é…ï¼‰ |
| closeStatus | String | å¦ | ç»“案状态 |
| registerUser | String | å¦ | ç™»è®°äººå‘˜ |
| orgid | String | å¦ | åŒ»é™¢æœºæž„ID(后端按登录用户自动注入) |
| campusid | String | å¦ | é™¢åŒºID(后端按登录用户自动注入) |
| params.beginTime | String | å¦ | å»ºæ¡£æ—¶é—´èŒƒå›´å¼€å§‹ï¼Œæ ¼å¼ yyyy-MM-dd |
| params.endTime | String | å¦ | å»ºæ¡£æ—¶é—´èŒƒå›´ç»“束,格式 yyyy-MM-dd |
**请求示例**
```json
{
  "pageNum": 1,
  "pageSize": 10,
  "patname": "王",
  "archiveType": "1"
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "查询成功",
  "total": 1,
  "rows": [
    {
      "id": 2001,
      "archiveType": "1",
      "outhospno": "MZ20261001002",
      "inhospno": null,
      "serialnum": "LS20261001002",
      "patid": 9002,
      "patname": "王小宝",
      "parentname": "王强",
      "birthdate": "2023-01-15 00:00:00",
      "idcardno": null,
      "phone": "13800000001",
      "diagTime": "2026-10-01 09:00:00",
      "archiveTime": "2026-10-01 09:30:00",
      "plannedFollowTime": "2026-11-01 00:00:00",
      "followupCycleTimes": "3",
      "sex": 1,
      "doctorCode": "D003",
      "doctorName": "赵医生",
      "managementDoctorCode": "D004",
      "managementDoctorName": "孙医生",
      "deptcode": "EK",
      "deptname": "儿科",
      "wardcode": null,
      "wardname": null,
      "icd10code": "E44.0",
      "diagname": "中度营养不良",
      "closeStatus": "0",
      "registerUser": "赵医生",
      "orgid": "1",
      "campusid": "1",
      "delFlag": "0"
    }
  ]
}
```
---
### 3.2 æ–°å¢žæˆ–修改儿科档案
- **URL**:`POST /smartor/childArchive/insertOrUpdateChildArchive`
- **说明**:同时维护儿科档案主表与详情表。
  - ä¸»è¡¨æœ‰ `archiveid`(或详情有 `id` ä¸”能反查)且存在时更新,否则新增。
  - è¿”回 `data` ä¸ºå„¿ç§‘档案主表ID。
**请求体(JSON)**
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| id | Long | å¦ | å„¿ç§‘档案详情主键ID(更新详情时传入) |
| archiveid | Long | å¦ | å„¿ç§‘档案主表ID(更新主表时传入) |
| archiveType | String | å¦ | æ¡£æ¡ˆç±»åž‹ï¼š1 è¥å…»æ€§ç–¾ç—…,2 å­¦ä¹ å›°éš¾ï¼Œ3 é«˜å±å„¿ä¸“案 |
| sourceType | String | å¦ | æ¥æºç±»åž‹ï¼š1 é—¨è¯ŠæŽ¥è¯Šï¼Œ2 ä½é™¢éšè®¿ï¼Œ3 æ‰‹å·¥æ–°å¢ž |
| sourceBizId | Long | å¦ | æ¥æºä¸šåŠ¡ID:门诊记录ID/住院记录ID |
| sourceBizNo | String | å¦ | æ¥æºä¸šåŠ¡ç¼–å·ï¼šé—¨è¯Šå·/住院号 |
| sourceSubId | Long | å¦ | æ¥æºéšè®¿å­ä»»åŠ¡ID:住院随访生成时为 service_subtask.id |
| outhospno | String | å¦ | é—¨è¯Šç¼–号 |
| inhospno | String | å¦ | ä½é™¢ç¼–号 |
| serialnum | String | å¦ | æµæ°´å· |
| patid | Long | å¦ | æ‚£è€…ID |
| patname | String | å¦ | æ‚£è€…姓名 |
| parentname | String | å¦ | å®¶é•¿å§“名 |
| parentRelation | String | å¦ | å®¶é•¿ä¸Žå„¿ç«¥å…³ç³» |
| parentPhone | String | å¦ | å®¶é•¿è”系电话 |
| parentIdcardno | String | å¦ | å®¶é•¿è¯ä»¶å·ç  |
| parentAddress | String | å¦ | å®¶é•¿è”系地址 |
| birthdate | Date | å¦ | å‡ºç”Ÿæ—¥æœŸï¼Œæ ¼å¼ yyyy-MM-dd HH:mm:ss |
| idcardno | String | å¦ | è¯ä»¶å·ç  |
| phone | String | å¦ | è”系电话 |
| diagTime | Date | å¦ | è¯Šæ–­æ—¶é—´ |
| archiveTime | Date | å¦ | å»ºæ¡£æ—¶é—´ |
| sex | Long | å¦ | æ€§åˆ«ï¼š1 ç”·ï¼Œ2 å¥³ |
| doctorCode | String | å¦ | ä¸»æ²»åŒ»ç”Ÿ |
| doctorName | String | å¦ | ä¸»æ²»åŒ»ç”Ÿå§“名 |
| managementDoctorCode | String | å¦ | ç»ç®¡åŒ»ç”Ÿ |
| managementDoctorName | String | å¦ | ç»ç®¡åŒ»ç”Ÿå§“名 |
| deptcode | String | å¦ | ç§‘室编号 |
| deptname | String | å¦ | ç§‘室名称 |
| wardcode | String | å¦ | ç—…区编号 |
| wardname | String | å¦ | ç—…区名称 |
| icd10code | String | å¦ | è¯Šæ–­ID |
| diagname | String | å¦ | è¯Šæ–­åç§° |
| nutritionIcd10Code | String | å¦ | è¥å…»æ€§ç–¾ç—…ID |
| nutritionDiagName | String | å¦ | è¥å…»æ€§ç–¾ç—…名称 |
| ndPhysicalMeasurements | String | å¦ | è¥å…»æ€§ç–¾ç—…-体格测量 |
| ndDietaryAssessment | String | å¦ | è¥å…»æ€§ç–¾ç—…-膳食评估 |
| ndInterventionProgram | String | å¦ | è¥å…»æ€§ç–¾ç—…-干预方案 |
| appointmentSituation | String | å¦ | å­¦ä¹ å›°éš¾-预约情况 |
| ldLearningAbilityAssessment | String | å¦ | å­¦ä¹ å›°éš¾-学习能力评估 |
| ldBehavioralAssessment | String | å¦ | å­¦ä¹ å›°éš¾-行为评估 |
| ldInterventionTrainingRecord | String | å¦ | å­¦ä¹ å›°éš¾-干预训练记录 |
| highRiskChildType | String | å¦ | é«˜å±å„¿ä¸“案-高危儿分类 |
| highRiskChildName | String | å¦ | é«˜å±å„¿ä¸“案-高危儿名称 |
| plannedFollowTime | Date | å¦ | é«˜å±å„¿ä¸“案-拟随访时间 |
| hrcGrowthDevelopmentMonitoring | String | å¦ | é«˜å±å„¿ä¸“案-生长发育检测 |
| hrcNeurologicalEvaluation | String | å¦ | é«˜å±å„¿ä¸“案-神经系统评估 |
| hrcInterventionRecords | String | å¦ | é«˜å±å„¿ä¸“案-干预记录 |
| closeStatus | String | å¦ | ç»“案状态 |
| gestationalWeek | BigDecimal | å¦ | å‡ºç”Ÿå­•周 |
| birthWeight | BigDecimal | å¦ | å‡ºç”Ÿä½“重(kg) |
| registerUser | String | å¦ | ç™»è®°äººå‘˜ |
| remark | String | å¦ | å¤‡æ³¨ |
| orgid | String | å¦ | åŒ»é™¢æœºæž„ID(后端按登录用户自动覆盖) |
| campusid | String | å¦ | é™¢åŒºID(后端按登录用户自动覆盖) |
| delFlag | String | å¦ | åˆ é™¤æ ‡å¿—:0 æœªåˆ é™¤ï¼Œ1 å·²åˆ é™¤ï¼ˆé»˜è®¤ 0) |
**请求示例(新增)**
```json
{
  "archiveType": "1",
  "patid": 9002,
  "patname": "王小宝",
  "parentname": "王强",
  "parentRelation": "父子",
  "parentPhone": "13900000001",
  "birthdate": "2023-01-15 00:00:00",
  "phone": "13800000001",
  "diagTime": "2026-10-01 09:00:00",
  "archiveTime": "2026-10-01 09:30:00",
  "sex": 1,
  "deptcode": "EK",
  "deptname": "儿科",
  "icd10code": "E44.0",
  "diagname": "中度营养不良",
  "nutritionIcd10Code": "E44.0",
  "nutritionDiagName": "中度营养不良",
  "closeStatus": "0"
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": 2001
}
```
---
### 3.3 æ‰¹é‡åˆ é™¤å„¿ç§‘档案
- **URL**:`POST /smartor/childArchive/batchDelete`
- **说明**:按主表ID批量删除儿科档案,同时逻辑删除对应的儿科档案详情表数据(`del_flag` ç½®ä¸º 1),两步在同一事务中。
**请求体(JSON)**
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| ids | Long[] | æ˜¯ | å„¿ç§‘档案主表ID数组 |
**请求示例**
```json
[2001, 2002, 2003]
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功"
}
```
---
### 3.4 èŽ·å–å„¿ç§‘æ¡£æ¡ˆè¯¦æƒ…
- **URL**:`GET /smartor/childArchiveDetail/getInfo/{archiveid}`
- **说明**:按档案主表ID查询儿科档案详情(未删除数据 `del_flag = '0'`),返回第一条详情记录;无数据时 `data` ä¸º `null`。
**路径参数**
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| archiveid | Long | æ˜¯ | å„¿ç§‘档案主表ID(`pat_child_archive.id`) |
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "id": 6001,
    "archiveid": 2001,
    "archiveType": "1",
    "sourceType": "1",
    "outhospno": "MZ20261001002",
    "inhospno": null,
    "serialnum": "LS20261001002",
    "patid": 9002,
    "patname": "王小宝",
    "parentname": "王强",
    "parentRelation": "父子",
    "parentPhone": "13900000001",
    "parentIdcardno": null,
    "parentAddress": "杭州市拱墅区",
    "birthdate": "2023-01-15 00:00:00",
    "idcardno": null,
    "phone": "13800000001",
    "diagTime": "2026-10-01 09:00:00",
    "archiveTime": "2026-10-01 09:30:00",
    "sex": 1,
    "doctorCode": "D003",
    "doctorName": "赵医生",
    "managementDoctorCode": "D004",
    "managementDoctorName": "孙医生",
    "deptcode": "EK",
    "deptname": "儿科",
    "wardcode": null,
    "wardname": null,
    "icd10code": "E44.0",
    "diagname": "中度营养不良",
    "nutritionIcd10Code": "E44.0",
    "nutritionDiagName": "中度营养不良",
    "closeStatus": "0",
    "gestationalWeek": 39.5,
    "birthWeight": 3.2,
    "registerUser": "赵医生",
    "orgid": "1",
    "campusid": "1",
    "delFlag": "0"
  }
}
```
> è¿”回对象 `data` ä¸ºå„¿ç§‘档案详情对象,字段说明同 3.2 è¯·æ±‚体字段。
ruoyi-common/src/main/java/com/ruoyi/common/core/event/FollowupCompletedEvent.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package com.ruoyi.common.core.event;
import java.io.Serializable;
import java.util.Date;
/**
 * éšè®¿å®Œæˆäº‹ä»¶ã€‚
 *
 * <p>仅携带子任务ID,消费方按ID重新加载最新数据,避免事件内容与库内状态不一致。</p>
 *
 * @author ruoyi
 * @date 2026-11-11
 */
public class FollowupCompletedEvent implements Serializable {
    private static final long serialVersionUID = 1L;
    /** éšè®¿å­ä»»åŠ¡ID */
    private Long subId;
    /** å®Œæˆæ—¶é—´ */
    private Date finishTime;
    public FollowupCompletedEvent() {
    }
    public FollowupCompletedEvent(Long subId, Date finishTime) {
        this.subId = subId;
        this.finishTime = finishTime;
    }
    public Long getSubId() {
        return subId;
    }
    public void setSubId(Long subId) {
        this.subId = subId;
    }
    public Date getFinishTime() {
        return finishTime;
    }
    public void setFinishTime(Date finishTime) {
        this.finishTime = finishTime;
    }
}
ruoyi-task/src/main/java/com/ruoyi/task/event/FollowupCompletedEventPublisher.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.task.event;
import com.ruoyi.common.core.event.FollowupCompletedEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
 * éšè®¿å®Œæˆäº‹ä»¶å‘布器。
 *
 * <p>任务模块在子任务状态更新为已完成(sendstate=6)后发布事件,
 * æ¡£æ¡ˆç­‰ä¸‹æ¸¸æ¨¡å—通过 {@code @TransactionalEventListener} å¼‚步消费。</p>
 *
 * @author ruoyi
 * @date 2026-11-11
 */
@Slf4j
@Component
public class FollowupCompletedEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    public void publish(Long subId) {
        if (subId == null) {
            return;
        }
        try {
            applicationEventPublisher.publishEvent(new FollowupCompletedEvent(subId, new Date()));
        } catch (Exception e) {
            log.error("发布随访完成事件失败,subId={}", subId, e);
        }
    }
}
smartor/src/main/java/com/smartor/domain/PatSpecificArchiveGenerateLog.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
package com.smartor.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * ä¸“科档案生成日志对象 pat_specific_archive_generate_log。
 *
 * <p>用于住院随访完成后的幂等控制、来源追溯和补偿任务筛选。</p>
 *
 * @author ruoyi
 * @date 2026-11-11
 */
@Data
@ApiModel(value = "专科档案生成日志对象")
public class PatSpecificArchiveGenerateLog implements Serializable {
    private static final long serialVersionUID = 1L;
    /** ä¸»é”®ID */
    @ApiModelProperty(value = "主键ID")
    private Long id;
    /** æ¥æºç±»åž‹ï¼š1门诊接诊 2住院随访 */
    @ApiModelProperty(value = "来源类型:1门诊接诊 2住院随访")
    private String sourceType;
    /** æ¥æºä¸šåŠ¡ID:门诊记录ID/住院记录ID */
    @ApiModelProperty(value = "来源业务ID:门诊记录ID/住院记录ID")
    private Long sourceBizId;
    /** æ¥æºä¸šåŠ¡ç¼–å·ï¼šé—¨è¯Šå·/住院号 */
    @ApiModelProperty(value = "来源业务编号:门诊号/住院号")
    private String sourceBizNo;
    /** æ¥æºéšè®¿å­ä»»åŠ¡ID:触发档案生成的 service_subtask.id */
    @ApiModelProperty(value = "来源随访子任务ID:触发档案生成的 service_subtask.id")
    private Long sourceSubId;
    /** æ‚£è€…ID */
    @ApiModelProperty(value = "患者ID")
    private Long patid;
    /** æœºæž„ID */
    @ApiModelProperty(value = "机构ID")
    private String orgid;
    /** é™¢åŒºID */
    @ApiModelProperty(value = "院区ID")
    private String campusid;
    /** éƒ¨é—¨åˆ†ç±»ï¼š1妇科 2儿科 */
    @ApiModelProperty(value = "部门分类:1妇科 2儿科")
    private String deptCategory;
    /** æ¡£æ¡ˆç±»åž‹ */
    @ApiModelProperty(value = "档案类型")
    private String archiveType;
    /** å‘½ä¸­çš„配置ID;未命中配置时为0 */
    @ApiModelProperty(value = "命中的配置ID;未命中配置时为0")
    private Integer configId;
    /** ç”Ÿæˆæˆ–命中的档案主表ID */
    @ApiModelProperty(value = "生成或命中的档案主表ID")
    private Long archiveId;
    /** å¤„理结果:1生成 2已存在跳过 3未匹配 4失败 */
    @ApiModelProperty(value = "处理结果:1生成 2已存在跳过 3未匹配 4失败")
    private String processResult;
    /** å¤„理说明 */
    @ApiModelProperty(value = "处理说明")
    private String processMsg;
    /** é‡è¯•次数 */
    @ApiModelProperty(value = "重试次数")
    private Integer retryCount;
    /** åˆ›å»ºæ—¶é—´ */
    @ApiModelProperty(value = "创建时间")
    private Date createTime;
    /** æ›´æ–°æ—¶é—´ */
    @ApiModelProperty(value = "更新时间")
    private Date updateTime;
}
smartor/src/main/java/com/smartor/listener/InhospFollowupArchiveListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package com.smartor.listener;
import com.ruoyi.common.core.event.FollowupCompletedEvent;
import com.smartor.service.IPatSpecificArchiveGenerateService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
/**
 * éšè®¿å®Œæˆç”Ÿæˆä¸“科档案监听器。
 *
 * <p>使用 AFTER_COMMIT ä¿è¯éšè®¿å®ŒæˆçŠ¶æ€å·²è½åº“ï¼›å¼‚å¸¸åªè®°å½•æ—¥å¿—ï¼Œä¸å½±å“éšè®¿ä¸»æµç¨‹ã€‚</p>
 *
 * @author ruoyi
 * @date 2026-11-11
 */
@Slf4j
@Component
public class InhospFollowupArchiveListener {
    @Autowired
    private IPatSpecificArchiveGenerateService patSpecificArchiveGenerateService;
    @Async("threadPoolTaskExecutor")
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
    public void onFollowupCompleted(FollowupCompletedEvent event) {
        if (event == null || event.getSubId() == null) {
            return;
        }
        try {
            int generated = patSpecificArchiveGenerateService.generateByInhospFollowup(event.getSubId());
            if (generated > 0) {
                log.info("住院随访完成生成专科档案成功,subId={},生成数量={}", event.getSubId(), generated);
            }
        } catch (Throwable e) {
            log.error("住院随访完成生成专科档案失败,subId={}", event.getSubId(), e);
        }
    }
}
smartor/src/main/java/com/smartor/mapper/PatSpecificArchiveGenerateLogMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.smartor.mapper;
import com.smartor.domain.PatSpecificArchiveGenerateLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * ä¸“科档案生成日志Mapper接口。
 *
 * @author ruoyi
 * @date 2026-11-11
 */
@Mapper
public interface PatSpecificArchiveGenerateLogMapper {
    /**
     * æŒ‰æ¥æº + éšè®¿å­ä»»åŠ¡ + é…ç½®æŸ¥è¯¢æ—¥å¿—。
     *
     * @param sourceType  æ¥æºç±»åž‹
     * @param sourceSubId æ¥æºéšè®¿å­ä»»åŠ¡ID
     * @param configId    é…ç½®ID,未命中配置传0
     * @return æ—¥å¿—记录,不存在返回 null
     */
    PatSpecificArchiveGenerateLog selectBySourceSubAndConfig(@Param("sourceType") String sourceType,
                                                             @Param("sourceSubId") Long sourceSubId,
                                                             @Param("configId") Integer configId);
    /**
     * æŸ¥è¯¢æ¥æºéšè®¿å­ä»»åŠ¡ä¸‹æ˜¯å¦å·²æœ‰æˆåŠŸ/跳过/未匹配的结果。
     *
     * @param sourceType  æ¥æºç±»åž‹
     * @param sourceSubId æ¥æºéšè®¿å­ä»»åŠ¡ID
     * @return å·²å¤„理条数
     */
    int countProcessedBySourceSub(@Param("sourceType") String sourceType,
                                  @Param("sourceSubId") Long sourceSubId);
    int insertPatSpecificArchiveGenerateLog(PatSpecificArchiveGenerateLog log);
    int updatePatSpecificArchiveGenerateLog(PatSpecificArchiveGenerateLog log);
}
smartor/src/main/java/com/smartor/service/IPatSpecificArchiveGenerateLogService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.smartor.service;
import com.smartor.domain.PatSpecificArchiveGenerateLog;
/**
 * ä¸“科档案生成日志Service接口。
 *
 * @author ruoyi
 * @date 2026-11-11
 */
public interface IPatSpecificArchiveGenerateLogService {
    PatSpecificArchiveGenerateLog selectBySourceSubAndConfig(String sourceType, Long sourceSubId, Integer configId);
    boolean existsProcessedBySourceSub(String sourceType, Long sourceSubId);
    int saveOrUpdate(PatSpecificArchiveGenerateLog log);
}
smartor/src/main/java/com/smartor/service/impl/PatSpecificArchiveGenerateLogServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package com.smartor.service.impl;
import com.smartor.domain.PatSpecificArchiveGenerateLog;
import com.smartor.mapper.PatSpecificArchiveGenerateLogMapper;
import com.smartor.service.IPatSpecificArchiveGenerateLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
 * ä¸“科档案生成日志Service业务层处理。
 *
 * @author ruoyi
 * @date 2026-11-11
 */
@Service
public class PatSpecificArchiveGenerateLogServiceImpl implements IPatSpecificArchiveGenerateLogService {
    @Autowired
    private PatSpecificArchiveGenerateLogMapper logMapper;
    @Override
    public PatSpecificArchiveGenerateLog selectBySourceSubAndConfig(String sourceType, Long sourceSubId, Integer configId) {
        return logMapper.selectBySourceSubAndConfig(sourceType, sourceSubId,
                configId == null ? 0 : configId);
    }
    @Override
    public boolean existsProcessedBySourceSub(String sourceType, Long sourceSubId) {
        return logMapper.countProcessedBySourceSub(sourceType, sourceSubId) > 0;
    }
    @Override
    public int saveOrUpdate(PatSpecificArchiveGenerateLog log) {
        if (log == null) {
            return 0;
        }
        if (log.getSourceType() == null) {
            log.setSourceType("2");
        }
        if (log.getConfigId() == null) {
            log.setConfigId(0);
        }
        if (log.getRetryCount() == null) {
            log.setRetryCount(0);
        }
        Date now = new Date();
        log.setUpdateTime(now);
        PatSpecificArchiveGenerateLog old = logMapper.selectBySourceSubAndConfig(
                log.getSourceType(), log.getSourceSubId(), log.getConfigId());
        if (old == null) {
            log.setCreateTime(now);
            try {
                return logMapper.insertPatSpecificArchiveGenerateLog(log);
            } catch (DuplicateKeyException e) {
                // å¹¶å‘下另一个线程已写入,转为更新,保证幂等
                old = logMapper.selectBySourceSubAndConfig(
                        log.getSourceType(), log.getSourceSubId(), log.getConfigId());
                if (old == null) {
                    throw e;
                }
                log.setId(old.getId());
                return logMapper.updatePatSpecificArchiveGenerateLog(log);
            }
        }
        log.setId(old.getId());
        if (log.getCreateTime() == null) {
            log.setCreateTime(old.getCreateTime());
        }
        return logMapper.updatePatSpecificArchiveGenerateLog(log);
    }
}
smartor/src/main/resources/mapper/smartor/PatSpecificArchiveGenerateLogMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smartor.mapper.PatSpecificArchiveGenerateLogMapper">
    <resultMap type="com.smartor.domain.PatSpecificArchiveGenerateLog" id="PatSpecificArchiveGenerateLogResult">
        <result property="id" column="id"/>
        <result property="sourceType" column="source_type"/>
        <result property="sourceBizId" column="source_biz_id"/>
        <result property="sourceBizNo" column="source_biz_no"/>
        <result property="sourceSubId" column="source_sub_id"/>
        <result property="patid" column="patid"/>
        <result property="orgid" column="orgid"/>
        <result property="campusid" column="campusid"/>
        <result property="deptCategory" column="dept_category"/>
        <result property="archiveType" column="archive_type"/>
        <result property="configId" column="config_id"/>
        <result property="archiveId" column="archive_id"/>
        <result property="processResult" column="process_result"/>
        <result property="processMsg" column="process_msg"/>
        <result property="retryCount" column="retry_count"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>
    <sql id="selectPatSpecificArchiveGenerateLogVo">
        select id,
               source_type,
               source_biz_id,
               source_biz_no,
               source_sub_id,
               patid,
               orgid,
               campusid,
               dept_category,
               archive_type,
               config_id,
               archive_id,
               process_result,
               process_msg,
               retry_count,
               create_time,
               update_time
        from pat_specific_archive_generate_log
    </sql>
    <select id="selectBySourceSubAndConfig" resultMap="PatSpecificArchiveGenerateLogResult">
        <include refid="selectPatSpecificArchiveGenerateLogVo"/>
        where source_type = #{sourceType}
          and source_sub_id = #{sourceSubId}
          and config_id = #{configId}
        limit 1
    </select>
    <select id="countProcessedBySourceSub" resultType="int">
        select count(1)
        from pat_specific_archive_generate_log
        where source_type = #{sourceType}
          and source_sub_id = #{sourceSubId}
          and process_result in ('1', '2', '3')
    </select>
    <insert id="insertPatSpecificArchiveGenerateLog" parameterType="com.smartor.domain.PatSpecificArchiveGenerateLog"
            useGeneratedKeys="true" keyProperty="id">
        insert into pat_specific_archive_generate_log
        (source_type, source_biz_id, source_biz_no, source_sub_id, patid, orgid, campusid,
         dept_category, archive_type, config_id, archive_id, process_result,
         process_msg, retry_count, create_time, update_time)
        values
        (#{sourceType}, #{sourceBizId}, #{sourceBizNo}, #{sourceSubId}, #{patid}, #{orgid}, #{campusid},
         #{deptCategory}, #{archiveType}, #{configId}, #{archiveId}, #{processResult},
         #{processMsg}, #{retryCount}, #{createTime}, #{updateTime})
    </insert>
    <update id="updatePatSpecificArchiveGenerateLog" parameterType="com.smartor.domain.PatSpecificArchiveGenerateLog">
        update pat_specific_archive_generate_log
        <trim prefix="SET" suffixOverrides=",">
            <if test="sourceBizNo != null">source_biz_no = #{sourceBizNo},</if>
            <if test="sourceSubId != null">source_sub_id = #{sourceSubId},</if>
            <if test="patid != null">patid = #{patid},</if>
            <if test="orgid != null">orgid = #{orgid},</if>
            <if test="campusid != null">campusid = #{campusid},</if>
            <if test="deptCategory != null">dept_category = #{deptCategory},</if>
            <if test="archiveType != null">archive_type = #{archiveType},</if>
            <if test="archiveId != null">archive_id = #{archiveId},</if>
            <if test="processResult != null">process_result = #{processResult},</if>
            <if test="processMsg != null">process_msg = #{processMsg},</if>
            <if test="retryCount != null">retry_count = #{retryCount},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
        </trim>
        where id = #{id}
    </update>
</mapper>
smartor/src/test/java/com/smartor/service/InhospFollowupArchiveLogicCheck.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,130 @@
package com.smartor.service;
import com.ruoyi.patarchive.domain.PatSpecificArchiveGenerateConfig;
import com.ruoyi.task.domain.ServiceSubtask;
import com.smartor.service.impl.PatSpecificArchiveGenerateServiceImpl;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
 * ä½é™¢éšè®¿ç”Ÿæˆä¸“科档案逻辑自检(纯 main,不依赖 JUnit / Spring / æ•°æ®åº“)。
 *
 * <p>覆盖:住院随访范围判断、科室/病区/诊断配置匹配、JSON/逗号配置解析、Mapper XML å¯è§£æžæ€§ã€‚</p>
 */
public final class InhospFollowupArchiveLogicCheck {
    private static int passed = 0;
    private static int failed = 0;
    private static void check(String key, Object expected, Object actual) {
        boolean ok = expected == null ? actual == null : expected.equals(actual);
        if (ok) {
            passed++;
            System.out.println("  [OK]   " + key + " = " + actual);
        } else {
            failed++;
            System.out.println("  [FAIL] " + key + " æœŸæœ›=" + expected + " å®žé™…=" + actual);
        }
    }
    private static void setField(Object target, String name, Object value) throws Exception {
        Field field = target.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(target, value);
    }
    private static ServiceSubtask subtask(String hospType, String serviceType) {
        ServiceSubtask subtask = new ServiceSubtask();
        subtask.setId(1001L);
        subtask.setHospType(hospType);
        subtask.setServiceType(serviceType);
        return subtask;
    }
    private static PatSpecificArchiveGenerateConfig config(String deptType, String archiveType,
                                                           String deptCodes, String icd10Codes) {
        PatSpecificArchiveGenerateConfig config = new PatSpecificArchiveGenerateConfig();
        config.setId(1L);
        config.setDeptType(deptType);
        config.setDeptCategory("2");
        config.setArchiveType(archiveType);
        config.setDeptCodes(deptCodes);
        config.setIcd10Codes(icd10Codes);
        return config;
    }
    public static void main(String[] args) throws Exception {
        System.out.println("========== ä½é™¢éšè®¿ç”Ÿæˆä¸“科档案逻辑自检 ==========");
        PatSpecificArchiveGenerateServiceImpl service = new PatSpecificArchiveGenerateServiceImpl();
        setField(service, "inhospFollowupServiceTypes", "2,13,18,19");
        Method isInhosp = PatSpecificArchiveGenerateServiceImpl.class
                .getDeclaredMethod("isInhospFollowup", ServiceSubtask.class);
        isInhosp.setAccessible(true);
        System.out.println("-- 1) ä½é™¢éšè®¿èŒƒå›´ --");
        check("出院随访 serviceType=2", true, isInhosp.invoke(service, subtask("2", "2")));
        check("入院随访 serviceType=18", true, isInhosp.invoke(service, subtask("6", "18")));
        check("门诊任务 hospType=1", false, isInhosp.invoke(service, subtask("1", "3")));
        check("宣教任务 serviceType=4", false, isInhosp.invoke(service, subtask("2", "4")));
        check("serviceType ä¸ºç©º", false, isInhosp.invoke(service, subtask("2", null)));
        Method match = PatSpecificArchiveGenerateServiceImpl.class.getDeclaredMethod(
                "matchConfig", PatSpecificArchiveGenerateConfig.class,
                String.class, String.class, String.class, String.class);
        match.setAccessible(true);
        System.out.println("-- 2) ç§‘室匹配 --");
        PatSpecificArchiveGenerateConfig deptConfig = config("1", "1", "[\"D001\"]", null);
        check("科室 D001 å‘½ä¸­", true, match.invoke(service, deptConfig, "D001", null, null, null));
        check("科室 D002 ä¸å‘½ä¸­", false, match.invoke(service, deptConfig, "D002", null, null, null));
        System.out.println("-- 3) ç—…区匹配 --");
        PatSpecificArchiveGenerateConfig wardConfig = config("2", "3", "[\"W001\"]", null);
        check("病区 W001 å‘½ä¸­", true, match.invoke(service, wardConfig, "D001", "W001", null, null));
        check("科室 D001 å¯¹ç—…区配置不命中", false,
                match.invoke(service, wardConfig, "D001", "D001", null, null));
        System.out.println("-- 4) è¯Šæ–­åŒ¹é… --");
        PatSpecificArchiveGenerateConfig diagConfig = config("1", "1", null, "[\"E46.x00x003\"]");
        check("诊断码命中", true, match.invoke(service, diagConfig, "D001", null, "E46.x00x003", "营养不良"));
        check("无诊断码时按诊断名称兜底", true,
                match.invoke(service, diagConfig, "D001", null, null, "E46.x00x003"));
        check("诊断不命中", false, match.invoke(service, diagConfig, "D001", null, "R00", "其他"));
        System.out.println("-- 5) é…ç½®ä¸ºç©º / éžæ³• JSON --");
        PatSpecificArchiveGenerateConfig emptyConfig = config("1", "1", null, null);
        check("科室和诊断都为空", false, match.invoke(service, emptyConfig, "D001", null, null, null));
        PatSpecificArchiveGenerateConfig commaConfig = config("1", "1", "D001,D002", null);
        check("逗号分隔配置兜底", true, match.invoke(service, commaConfig, "D002", null, null, null));
        System.out.println("-- 6) Mapper XML å¯è§£æžæ€§ --");
        String[] xmlFiles = new String[]{
                "smartor/src/main/resources/mapper/smartor/PatMaternityArchiveMapper.xml",
                "smartor/src/main/resources/mapper/smartor/PatChildArchiveMapper.xml",
                "smartor/src/main/resources/mapper/smartor/PatMaternityArchiveDetailMapper.xml",
                "smartor/src/main/resources/mapper/smartor/PatChildArchiveDetailMapper.xml",
                "smartor/src/main/resources/mapper/smartor/PatSpecificArchiveGenerateLogMapper.xml",
                "ruoyi-task/src/main/resources/mapper/task/ServiceSubtaskMapper.xml"
        };
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        for (String xmlFile : xmlFiles) {
            try {
                factory.newDocumentBuilder().parse(new File(xmlFile));
                check(xmlFile, "OK", "OK");
            } catch (Exception e) {
                check(xmlFile, "OK", e.getClass().getSimpleName() + ":" + e.getMessage());
            }
        }
        System.out.println("========== ç»“果:PASS=" + passed + ",FAIL=" + failed + " ==========");
        if (failed > 0) {
            System.exit(1);
        }
    }
}
sql/pat_specific_archive_generate_log_and_source_20261111.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
-- =============================================================================
-- ä½é™¢éšè®¿å®Œæˆç”Ÿæˆå¦‡ç§‘/儿科专科档案
-- 1. æ¡£æ¡ˆä¸»è¡¨/详情表增加来源字段
-- 2. æ–°å¢žä¸“科档案生成日志表(幂等 + è¡¥å¿ + è¿½æº¯ï¼‰
-- 3. å¢žåŠ æ‚£è€…æ¡£æ¡ˆæŸ¥è¯¢ç´¢å¼•
-- æ—¥æœŸï¼š2026-11-11
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. æ¡£æ¡ˆä¸»è¡¨æ¥æºå­—段
-- -----------------------------------------------------------------------------
ALTER TABLE pat_maternity_archive
    ADD COLUMN source_type CHAR(1) DEFAULT NULL COMMENT '来源类型:1门诊接诊 2住院随访 3手工新增',
    ADD COLUMN source_biz_id BIGINT DEFAULT NULL COMMENT '来源业务ID:门诊记录ID/住院记录ID',
    ADD COLUMN source_biz_no VARCHAR(64) DEFAULT NULL COMMENT '来源业务编号:门诊号/住院号',
    ADD COLUMN source_sub_id BIGINT DEFAULT NULL COMMENT '来源随访子任务ID:service_subtask.id';
ALTER TABLE pat_child_archive
    ADD COLUMN source_type CHAR(1) DEFAULT NULL COMMENT '来源类型:1门诊接诊 2住院随访 3手工新增',
    ADD COLUMN source_biz_id BIGINT DEFAULT NULL COMMENT '来源业务ID:门诊记录ID/住院记录ID',
    ADD COLUMN source_biz_no VARCHAR(64) DEFAULT NULL COMMENT '来源业务编号:门诊号/住院号',
    ADD COLUMN source_sub_id BIGINT DEFAULT NULL COMMENT '来源随访子任务ID:service_subtask.id';
-- -----------------------------------------------------------------------------
-- 2. æ¡£æ¡ˆè¯¦æƒ…表补充来源业务ID/编号(source_type åŽŸè¡¨å·²æœ‰ï¼‰
-- -----------------------------------------------------------------------------
ALTER TABLE pat_maternity_archive_detail
    ADD COLUMN source_biz_id BIGINT DEFAULT NULL COMMENT '来源业务ID:门诊记录ID/住院记录ID',
    ADD COLUMN source_biz_no VARCHAR(64) DEFAULT NULL COMMENT '来源业务编号:门诊号/住院号',
    ADD COLUMN source_sub_id BIGINT DEFAULT NULL COMMENT '来源随访子任务ID:service_subtask.id';
ALTER TABLE pat_child_archive_detail
    ADD COLUMN source_biz_id BIGINT DEFAULT NULL COMMENT '来源业务ID:门诊记录ID/住院记录ID',
    ADD COLUMN source_biz_no VARCHAR(64) DEFAULT NULL COMMENT '来源业务编号:门诊号/住院号',
    ADD COLUMN source_sub_id BIGINT DEFAULT NULL COMMENT '来源随访子任务ID:service_subtask.id';
-- -----------------------------------------------------------------------------
-- 3. ç”Ÿæˆæ—¥å¿—表
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS pat_specific_archive_generate_log
(
    id            BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    source_type   CHAR(1)       NOT NULL DEFAULT '2' COMMENT '来源类型:1门诊接诊 2住院随访',
    source_biz_id BIGINT        DEFAULT NULL COMMENT '来源业务ID:门诊记录ID/住院记录ID',
    source_biz_no VARCHAR(64)   DEFAULT NULL COMMENT '来源业务编号:门诊号/住院号',
    source_sub_id BIGINT        DEFAULT NULL COMMENT '来源随访子任务ID:service_subtask.id',
    patid         BIGINT        DEFAULT NULL COMMENT '患者ID',
    orgid         VARCHAR(64)   DEFAULT NULL COMMENT '机构ID',
    campusid      VARCHAR(64)   DEFAULT NULL COMMENT '院区ID',
    dept_category CHAR(1)       DEFAULT NULL COMMENT '部门分类:1妇科 2儿科',
    archive_type  VARCHAR(32)   DEFAULT NULL COMMENT '档案类型',
    config_id     INT           NOT NULL DEFAULT 0 COMMENT '命中的配置ID;未命中配置时为0',
    archive_id    BIGINT        DEFAULT NULL COMMENT '生成或命中的档案主表ID',
    process_result CHAR(1)      NOT NULL COMMENT '处理结果:1生成 2已存在跳过 3未匹配 4失败',
    process_msg   VARCHAR(1000) DEFAULT NULL COMMENT '处理说明',
    retry_count   INT           NOT NULL DEFAULT 0 COMMENT '重试次数',
    create_time   DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time   DATETIME      DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (id),
    UNIQUE KEY uk_pasgl_source_sub_config (source_type, source_sub_id, config_id),
    KEY idx_pasgl_biz_id (source_biz_id),
    KEY idx_pasgl_patid (patid, dept_category, archive_type),
    KEY idx_pasgl_archive (archive_id)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4 COMMENT ='专科档案生成日志表';
-- -----------------------------------------------------------------------------
-- 4. æ‚£è€…已有档案查询索引
-- -----------------------------------------------------------------------------
ALTER TABLE pat_maternity_archive
    ADD INDEX idx_pma_patid_type (orgid, patid, archive_type, del_flag);
ALTER TABLE pat_child_archive
    ADD INDEX idx_pca_patid_type (orgid, patid, archive_type, del_flag);
-- -----------------------------------------------------------------------------
-- 5. æ¥æºç±»åž‹å­—典(如字典表已有同类型数据,可跳过)
-- -----------------------------------------------------------------------------
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '专科档案来源', 'pat_archive_source_type', '0', 'system', NOW(), '专科档案生成来源'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM sys_dict_type WHERE dict_type = 'pat_archive_source_type'
);
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, is_default, status, create_by, create_time, remark)
SELECT 1, '门诊接诊', '1', 'pat_archive_source_type', 'N', '0', 'system', NOW(), '门诊接诊完成生成'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM sys_dict_data WHERE dict_type = 'pat_archive_source_type' AND dict_value = '1'
);
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, is_default, status, create_by, create_time, remark)
SELECT 2, '住院随访', '2', 'pat_archive_source_type', 'N', '0', 'system', NOW(), '住院随访完成生成'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM sys_dict_data WHERE dict_type = 'pat_archive_source_type' AND dict_value = '2'
);
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, is_default, status, create_by, create_time, remark)
SELECT 3, '手工新增', '3', 'pat_archive_source_type', 'N', '0', 'system', NOW(), '手工新增/导入'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM sys_dict_data WHERE dict_type = 'pat_archive_source_type' AND dict_value = '3'
);