From d30ff01c35031ff31c9f969c7301a2579fac026f Mon Sep 17 00:00:00 2001 From: QiuSW Date: Mon, 13 Jul 2026 20:02:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(osi):=20=E6=A0=A1=E5=87=86=E6=9C=BA?= =?UTF-8?q?=E6=9E=84=E6=9F=A5=E8=AF=A2=E8=AF=B7=E6=B1=82=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/01-OSI接口规范分析.md | 3 ++- docs/04-字段与接口映射.md | 4 +++- docs/07-本项目HTTP接口.md | 7 ++++--- docs/openapi.yaml | 7 ++++--- osi/public.go | 9 ++++++--- osi/public_test.go | 27 +++++++++++++++++++++++++-- 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/docs/01-OSI接口规范分析.md b/docs/01-OSI接口规范分析.md index ced9643..831b1da 100644 --- a/docs/01-OSI接口规范分析.md +++ b/docs/01-OSI接口规范分析.md @@ -173,10 +173,11 @@ | 查询网格地址 | `/osi/api/auto/wgdzcx/query` | `WGDZ00001` | `parentCode`,`pageNo`,`operateUser` | `regionCode`,`regionName`,`isFamily`(层级) | | 责任医生查询 | `/osi/api/auto/zryscx/query` | `ZRYS00001` | `manaUnitId`,`operateUser` | `personId`,`personName` | | 药品目录查询 | `/osi/api/auto/ypmlcx/query` | `YPML00001` | `pageNo`,`ypmc`,`pym` | `ypmc`,`ypdw`,`ypgg`,`jldw` | -| 机构查询 | `/osi/api/auto/cxjg/query` | `CXJG00002` | `organizCode`,`parentId` | `organizCode`,`organizName`,`organizType`,`parentId` | +| 机构查询 | `/osi/api/auto/cxjg/query` | `CXJG00002` | `organizCode`,`parentId`(两个 key 均须上送,值可为空) | `organizCode`,`organizName`,`organizType`,`parentId`,`regionCode` | > 公共服务接口是**基础字典服务**:网格地址→`regionCode`、责任医生→`personId`、机构→`organizCode`。 > 它们正是创建类接口所需主数据(`regionCode`/`manaDoctorId`/`manaUnitId` 等)的来源,建议优先打通并本地缓存为字典。 +> 机构查询实测发现:即使 `organizCode` 或 `parentId` 为空,两个 `baseInfo` key 仍必须发送;省略空字段会触发平台参数错误。`regionCode` 可能为 null。 --- diff --git a/docs/04-字段与接口映射.md b/docs/04-字段与接口映射.md index 5e20e4d..c318c6e 100644 --- a/docs/04-字段与接口映射.md +++ b/docs/04-字段与接口映射.md @@ -235,10 +235,12 @@ checkId = 截断20位( 编码( 源系统标识 | dataType | 源记录主键 [ | | --- | --- | --- | --- | | 网格地址查询 | `WGDZ00001` | `code="01"` 且返回数组非空 | 用已存在档案的区划/网格码派生 `parentCode` 候选值验证 | | 责任医生查询 | `ZRYS00001` | `code="01"` 且返回数组非空 | 入参 `manaUnitId` 使用档案返回的 9 位机构码 | -| 机构查询 | `CXJG00002` | `code="01"` 且返回数组非空 | 入参 `organizCode` 使用档案返回的 9 位机构码 | +| 机构查询 | `CXJG00002` | `code="01"` 且返回数组非空 | `baseInfo` 必须含 `organizCode` 和 `parentId` 两个 key(值可为空);响应 `regionCode` 可为 null | | 药品目录查询 | `YPML00001` | 未做真实联调 | T-101 已完成客户端封装与单测;后续有药品关键字/拼音码时补样本 | > T-103 字典缓存可以先依赖网格、责任医生、机构三类真实验证结果。药品目录与健康档案创建闭环无直接依赖,不阻塞 T-103/T-206。 + +机构查询的 `OrgQuery` 不可为两个字段添加 `omitempty`:平台要求 `organizCode`、`parentId` 同时存在,即便查询只按其中一个条件过滤,另一个也应显式上送空字符串。机构响应的 `regionCode` 为所在网格码,平台可返回 null,Go 查询结构按空字符串承接。 --- ## 10. JKDA 创建/更新请求契约(T-206 本地基线) diff --git a/docs/07-本项目HTTP接口.md b/docs/07-本项目HTTP接口.md index 0d778f0..365f826 100644 --- a/docs/07-本项目HTTP接口.md +++ b/docs/07-本项目HTTP接口.md @@ -162,11 +162,12 @@ GET /api/dictionaries/orgs | 参数 | 必填 | 说明 | | --- | --- | --- | -| `organizCode` | 否 | 机构编码 | -| `parentId` | 否 | 上级机构 ID/编码 | +| `organizCode` | 否 | 机构编码;HTTP 未传时仍会向 OSI 显式上送空字符串 | +| `parentId` | 否 | 上级机构 ID/编码;HTTP 未传时仍会向 OSI 显式上送空字符串 | - 上游:`CXJG00002` `/auto/cxjg/query` -- 返回:`data` 为**数组**(机构主数据,含 `organizCode`、`organizName`、`organizType`、`parentId` 等) +- 返回:`data` 为**数组**(机构主数据,含 `organizCode`、`organizName`、`organizType`、`parentId`、`regionCode` 等);`regionCode` 可能为 null +- OSI 要求 `organizCode` 和 `parentId` 两个 `baseInfo` key 都存在,值可为空;本服务已保证该行为 ### 10. 查询老年人生活自理能力评估 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 69f32a8..9102ecc 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -401,7 +401,7 @@ paths: tags: - dictionaries summary: 查询机构 - description: 查询机构主数据,原样返回上游 CXJG00002 `/auto/cxjg/query` 响应。 + description: 查询机构主数据,原样返回上游 CXJG00002 `/auto/cxjg/query` 响应。上游要求 organizCode 和 parentId 两个 baseInfo key 均存在;本服务会把未传参数显式上送为空字符串。 operationId: listOrgs parameters: - $ref: '#/components/parameters/organizCode' @@ -423,6 +423,7 @@ paths: organizName: 测试机构 organizType: '1' parentId: '441625' + regionCode: '441625000000' '400': $ref: '#/components/responses/BadRequest' '405': @@ -542,14 +543,14 @@ components: name: organizCode in: query required: false - description: 机构编码。 + description: 机构编码;HTTP 未传时服务仍向 OSI 显式上送空字符串。 schema: type: string parentId: name: parentId in: query required: false - description: 上级机构 ID/编码。 + description: 上级机构 ID/编码;HTTP 未传时服务仍向 OSI 显式上送空字符串。 schema: type: string responses: diff --git a/osi/public.go b/osi/public.go index ab4c8e1..72f333b 100644 --- a/osi/public.go +++ b/osi/public.go @@ -39,15 +39,18 @@ type Drug struct { } type OrgQuery struct { - OrganizCode string `json:"organizCode,omitempty"` - ParentID string `json:"parentId,omitempty"` + // docx 二者均标必填;联调验证通过的请求是两 key 都在(organizCode + parentId 可为 "")。 + // 不加 omitempty,确保 parentId="" 也上送,避免像网格地址那样必填空值被丢导致平台 NPE(code=02)。 + OrganizCode string `json:"organizCode"` + ParentID string `json:"parentId"` } type Org struct { OrganizCode string `json:"organizCode"` OrganizName string `json:"organizName"` - OrganizType string `json:"organizType"` + OrganizType string `json:"organizType"` // A医院/B社区中心(站)/C卫生院/D门诊诊所村室/D6村卫生室/R市卫生局 ParentID string `json:"parentId"` + RegionCode string `json:"regionCode"` // 机构所在网格码;联调实测可为 null(→"") } func (c *Client) QueryGridAddress(ctx context.Context, query GridAddressQuery) ([]GridAddress, Result, error) { diff --git a/osi/public_test.go b/osi/public_test.go index 23ca925..278cfb1 100644 --- a/osi/public_test.go +++ b/osi/public_test.go @@ -132,7 +132,7 @@ func TestQueryOrgsCallsCXJG00002(t *testing.T) { if err := json.NewDecoder(r.Body).Decode(&seenPayload); err != nil { t.Fatalf("decode request: %v", err) } - _, _ = w.Write([]byte(`{"code":"01","message":"操作成功","data":[{"organizCode":"441625001","organizName":"测试机构","organizType":"1","parentId":"441625"}]}`)) + _, _ = w.Write([]byte(`{"code":"01","message":"操作成功","data":[{"organizCode":"441625001","organizName":"测试机构","organizType":"1","parentId":"441625","regionCode":"441625000000"}]}`)) })) defer server.Close() @@ -157,7 +157,30 @@ func TestQueryOrgsCallsCXJG00002(t *testing.T) { if baseInfo["organizCode"] != "441625001" || baseInfo["parentId"] != "441625" { t.Fatalf("baseInfo = %#v", baseInfo) } - if len(rows) != 1 || rows[0].OrganizCode != "441625001" || rows[0].OrganizName != "测试机构" || rows[0].OrganizType != "1" || rows[0].ParentID != "441625" { + if len(rows) != 1 || rows[0].OrganizCode != "441625001" || rows[0].OrganizName != "测试机构" || rows[0].OrganizType != "1" || rows[0].ParentID != "441625" || rows[0].RegionCode != "441625000000" { t.Fatalf("rows = %#v", rows) } } + +func TestQueryOrgsSendsEmptyRequiredKeys(t *testing.T) { + var seenPayload map[string]any + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err := json.NewDecoder(r.Body).Decode(&seenPayload); err != nil { + t.Fatalf("decode request: %v", err) + } + _, _ = w.Write([]byte(`{"code":"01","message":"操作成功","data":[]}`)) + })) + defer server.Close() + + client := newTestJKDAClient(t, server.URL) + if _, _, err := client.QueryOrgs(context.Background(), OrgQuery{}); err != nil { + t.Fatalf("QueryOrgs: %v", err) + } + baseInfo := seenPayload["uploadinfo"].(map[string]any)["baseInfo"].(map[string]any) + if _, ok := baseInfo["organizCode"]; !ok || baseInfo["organizCode"] != "" { + t.Fatalf("organizCode should be present and empty: %#v", baseInfo) + } + if _, ok := baseInfo["parentId"]; !ok || baseInfo["parentId"] != "" { + t.Fatalf("parentId should be present and empty: %#v", baseInfo) + } +}