diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0ca3d29 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,144 @@ +# AGENTS.md + +This document provides guidance for agentic coding tools working on the UrbanOps (urbanops) codebase. + +## Build & Test Commands + +### Build Commands +```bash +# Clean and compile all modules +mvn clean compile + +# Build entire project (skipping tests) +mvn clean install -DskipTests + +# Build specific module with dependencies +mvn clean install -DskipTests -pl -am +``` + +### Test Commands +```bash +# Run all tests +mvn test + +# Run all tests for a specific module +mvn test -pl urbanops-module-system + +# Run a single test class +mvn test -Dtest=AdminUserServiceImplTest + +# Run a single test method +mvn test -Dtest=AdminUserServiceImplTest#testCreateUser + +# Run tests with specific profile +mvn test -Punit-test +``` + +## Project Structure + +- `urbanops-server` - Main Spring Boot application entry point +- `urbanops-module-system` - System management (users, roles, permissions, etc.) +- `urbanops-module-infra` - Infrastructure (files, jobs, configs, code generation) +- `urbanops-module-bpm` - Business Process Management (Flowable) +- `urbanops-module-xxx` - Business domain modules (garden, workorder, report, etc.) +- `urbanops-framework` - Shared framework components (security, redis, mybatis, etc.) +- `urbanops-dependencies` - Maven dependency version management + +## Code Style Guidelines + +### Package Structure +``` +com.zteits.urbanops.module.{module-name} +├── controller/admin - Admin API controllers +├── controller/app - App API controllers +├── controller/bridge - Legacy bridge controllers +├── service/.../impl - Service implementations +├── dal/dataobject - Database entities (DO classes) +├── dal/mysql - MyBatis mappers +├── controller/.../vo - View Objects (Request/Response VOs) +├── convert - MapStruct converters +└── enums - Module-specific enums +``` + +### Naming Conventions +- **Controllers**: `XxxController` (e.g., `UserController`) +- **Services**: `XxxService` (interface) and `XxxServiceImpl` (implementation) +- **Mappers**: `XxxMapper` (e.g., `AdminUserMapper`) +- **Data Objects**: `XxxDO` (e.g., `AdminUserDO`) +- **View Objects**: `XxxPageReqVO`, `XxxSaveReqVO`, `XxxRespVO`, `XxxSimpleRespVO` +- **Converters**: `XxxConvert` (MapStruct interface) + +### Code Organization +- DO classes extend `TenantBaseDO` or `BaseDO`, use `@TableName`, `@KeySequence`, `@Data`, `@EqualsAndHashCode(callSuper = true)`, `@Builder` +- VO classes use `@Schema` for documentation, `@NotBlank`/`@NotNull`/`@Size` for validation +- Controllers use `@Tag`, `@Operation`, `@PreAuthorize`, `@Valid`, `CommonResult` for responses +- Services use `@Service`, `@Slf4j`, `@Transactional(rollbackFor = Exception.class)`, `@Resource` + +### Error Handling +- Use `exception(ErrorCode)` from `ServiceExceptionUtil` to throw business exceptions +- Define error codes in module's `ErrorCodeConstants` interface +- Error code format: `1-002-xxx-xxx-xxx` (system module: 1-002-xxx-xxx-xxx) + +### Imports & Dependencies +- Organize: standard library → third-party → project packages +- No wildcard imports (e.g., avoid `import java.util.*`) +- Use Jakarta EE: `jakarta.*` imports +- Use Spring Boot 3.x and Spring Framework 6.x APIs + +### Validation +- Use Jakarta Bean Validation: `@NotNull`, `@NotBlank`, `@Size`, `@Pattern`, `@Email` +- Use `@Valid` for nested object validation +- Use `@AssertTrue` for complex validation with custom methods + +### Lombok Usage +- `@Data` for POJOs, `@Builder` for construction, `@EqualsAndHashCode(callSuper = true)` for DOs +- Configured in `lombok.config`: `lombok.accessors.chain=true`, `lombok.tostring.callsuper=CALL` + +### Testing Guidelines +- Extend `BaseMockitoUnitTest` (no DB) or `BaseDbUnitTest` (H2 database) +- Use JUnit 5: `@Test`, `@BeforeEach`, `@BeforeAll` +- Naming: `test{MethodName}_{scenario}` +- Use `assertPojoEquals()` for comparing DOs, `assertServiceException()` for exceptions +- Clean up test data via `@Sql` with `/sql/clean.sql` + +### Database +- Use MyBatis Plus, mappers extend `BaseMapper` +- Use `TenantBaseDO` for multi-tenant tables, `BaseDO` for single-tenant + +### Security +- Use `@PreAuthorize` with format `{module}:{resource}:{action}` (e.g., `system:user:create`) +- Inject current user: `SecurityFrameworkUtils.getLoginUserId()` + +### Date/Time +- Use `java.time`, `LocalDateTime` for timestamps +- Format: `DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND`, `@DateTimeFormat` + +### Logging +- Use `@Slf4j`, levels: `ERROR`, `WARN`, `INFO`, `DEBUG` +- Log meaningful context, avoid sensitive info (passwords, tokens, PII) + +### API Documentation +- Use OpenAPI 3.0: `@Tag`, `@Operation`, `@Parameter`, `@Schema` +- Chinese descriptions in `@Schema`, mark required with `requiredMode`, provide `example` values + +### Module Communication +- Use module APIs (`urbanops-module-api`) for cross-module communication +- Use `@Lazy` to avoid circular dependencies + +### Adding New Features +1. Create DO class in `dal/dataobject` +2. Create Mapper interface in `dal/mysql` +3. Create Service interface and implementation +4. Create VO classes in `controller/.../vo` +5. Create Controller class +6. Create Convert interface (MapStruct) +7. Write unit tests extending appropriate base class +8. Add error codes to `ErrorCodeConstants` + +### Common Pitfalls +- Forgetting `@Transactional` on modifying service methods +- Using wrong DO base class (`BaseDO` vs `TenantBaseDO`) +- Not using `@Valid` for request body validation +- Not handling exceptions appropriately +- Direct database access bypassing Service layer +- Using `System.out.println` instead of logger diff --git a/api_document.md b/api_document.md new file mode 100644 index 0000000..38f283a --- /dev/null +++ b/api_document.md @@ -0,0 +1,305 @@ +# API 接口文档 - 行道树巡检记录与安全风险评估 + +本文档用于指导微信小程序前端开发人员对接行道树巡检记录与安全风险评估接口。 +为完美契合小程序详情页中按部位折叠面板的设计,接口设计采用了**分层嵌套结构**。 + +--- + +## 接口基础信息 + +* **接口协议**:HTTP / HTTPS +* **请求格式**:`application/json;charset=utf-8` +* **响应格式**:`application/json;charset=utf-8` +* **接口基地址**:`/app-api/garden/tree-inspection` 或 `/app-api/business/tree-inspection` +* **认证方式**:请求头中需携带 `Authorization: Bearer ` + +--- + +## 1. 提交巡检记录并进行风险评估 + +小程序在巡检人员填写完成表单,点击提交按钮(图3)时调用该接口。 + +* **接口路径**:`POST /create` +* **请求方法**:`POST` + +### 请求 JSON 参数说明 (`TreeInspectionSaveReqVO`) + +| 一级属性名 | 二级字段名 | 类型 | 是否必填 | 枚举值 / 说明 | 字段描述 | +| :--- | :--- | :--- | :--- | :--- | :--- | +| **treeId** | - | Long | 是 | 关联的树木档案 ID,如 `1024` | 树木 ID | +| **inspectionTime** | - | String | 是 | 格式 `"yyyy-MM-dd HH:mm:ss"` | 巡检发生的时间 | +| **root** | - | Object | 是 | 对应“树根部位”折叠面板 | 树根评估指标组 | +| | **disease** | Integer | 是 | `0`(无真菌危害/腐朽), `8`(存在危害/腐朽) | 是否存在病害? | +| | **anchorage** | Integer | 是 | `0`(良好无盘根隆起), `7`(存在隆起或盘根) | 根系下扎情况 | +| | **cutting** | Integer | 是 | `0`(无工程切根), `5`(存在工程切根) | 是否存在工程切根 | +| **collar** | - | Object | 是 | 对应“根颈部位”折叠面板 | 根颈评估指标组 | +| | **woodDamage** | Integer | 是 | `0`(无), `5`(<10%), `15`(10%-30%), `25`(30%-50%), `70`(>=50% 一票否决) | 根颈木质部受损 | +| | **barkDamage** | Integer | 是 | `0`(<10%), `2`(10%-30%), `4`(30%-50%), `6`(>=50%) | 根颈树皮受损 | +| | **loosening** | Integer | 是 | `0`(不存在松动), `100`(存在松动 一票否决) | 根颈是否松动 | +| **trunk** | - | Object | 是 | 对应“主干部位”折叠面板 | 主干评估指标组 | +| | **woodDamage** | Integer | 是 | `0`(无), `5`(<10%), `12`(10%-30%), `20`(30%-50%), `70`(>=50% 一票否决) | 主干木质部受损 | +| | **tilt** | Integer | 是 | `0`(<10°), `3`(10°-20°), `8`(20°-30°), `70`(>=30° 一票否决) | 主干倾斜度 | +| | **barkDamage** | Integer | 是 | `0`(<10%), `1`(10%-30%), `3`(30%-50%), `5`(>=50%) | 主干树皮受损 | +| **crown** | - | Object | 是 | 对应“树冠部位”折叠面板 | 树冠评估指标组 | +| | **looseBranch** | Integer | 是 | `0`(无易落枝), `2`(占比<1/10), `3`(占比>=1/10) | 观察是否存在易落枝 | +| | **collarAbnormal** | Integer | 是 | `0`(无异常), `3`(龟裂/卷皮), `5`(腐烂尚未成洞), `70`(空洞/蛀干 一票否决) | 枝干结合部异常 | +| | **ventilationBalance**| Integer | 是 | `0`(好不偏冠), `1`(偏冠或透风差不偏冠), `2`(偏冠冠幅适中), `5`(透风差冠幅大不偏冠), `8`(透风差大且偏冠) | 树冠透风与平衡性 | +| **weight** | - | Object | 是 | 对应“权重因子评估”面板 | 生理与生境权重因子组 | +| | **treeSpeciesType** | String | 是 | `"深根性树种"`, `"浅根性树种"` | 树种类型 (深根性1.0 / 浅根性1.1) | +| | **plantingYears** | String | 是 | `"栽植 10 年以内"`, `"栽植 10-30 年"`, `"栽植 30 年以上"` | 栽植年限 (对应 1.0, 1.1, 1.2) | +| | **isWindCorridor** | Boolean | 是 | `true`(是), `false`(否) | 是否处于风口 (对应权重 2.0 / 1.0) | +| | **treePoolType** | String | 是 | `"联通树池"`, `"独立树池"`, `"树池硬化"` | 树池类型 (对应 1.0, 1.2, 1.5) | +| | **treePoolWidthDbhRatio**| String| 是 | `"7 倍及以上"`, `"5 倍-7 倍"`, `"3 倍-5 倍"`, `"3 倍以下"` | 树池宽胸径比 (对应 1.0-1.3) | +| **result** | - | Object | 是 | 对应“风险评估结果”面板 | 风险评估结果表单项 | +| | **isEmergency** | Boolean | 是 | `true`(是), `false`(否) | 是否展开应急评估 | +| | **windPower** | String | 否 | `"7 级及以下"`, `"8-9 级"`, `"10 级"`, `"10 级以上"` | 极端风力等级(`isEmergency`为`true`时必填) | +| | **defectScore** | Integer | 否 | 输入缺陷总分,如 `9` (若前端已计算则优先以前端为准) | 缺陷总得分 | +| | **normalResult** | Object | 否 | 常规风险评估计算结果对象 | 常规安全评估结果组 | +| | **normalResult.score**| Double | 否 | 常规安全评估得分,如 `10.89` | 常规安全得分 | +| | **normalResult.level**| String | 否 | 常规安全风险等级,如 `"II级 (轻度风险)"` | 常规风险等级 | +| | **emergencyResult**| Object | 否 | 应急风险评估计算结果对象 | 应急安全评估结果组 | +| | **emergencyResult.score**| Double| 否 | 应急安全评估得分,如 `16.34` | 应急安全得分 | +| | **emergencyResult.level**| String| 否 | 应急安全风险等级,如 `"II级 (轻度风险)"` | 应急风险等级 | +| **status** | - | Object | 是 | 对应“现状及处理措施”面板 | 现状及处理措施数据组 | +| | **photos** | List | 否 | 数组,最多 5 个图片 URL,超过 5 个会报错拦截 | 现场采集照片 (限制最多5张) | + +### 请求示例 JSON + +```json +{ + "treeId": 1024, + "inspectionTime": "2026-05-26 13:17:00", + "root": { + "disease": 0, + "anchorage": 0, + "cutting": 0 + }, + "collar": { + "woodDamage": 5, + "barkDamage": 0, + "loosening": 0 + }, + "trunk": { + "woodDamage": 0, + "tilt": 3, + "barkDamage": 0 + }, + "crown": { + "looseBranch": 0, + "collarAbnormal": 0, + "ventilationBalance": 1 + }, + "weight": { + "treeSpeciesType": "深根性树种", + "plantingYears": "栽植 10-30 年", + "isWindCorridor": false, + "treePoolType": "联通树池", + "treePoolWidthDbhRatio": "5 倍(含)-7 倍(不含)" + }, + "result": { + "isEmergency": true, + "windPower": "8-9 级", + "defectScore": 9, + "normalResult": { + "score": 10.89, + "level": "II级 (轻度风险)" + }, + "emergencyResult": { + "score": 16.34, + "level": "II级 (轻度风险)" + } + }, + "status": { + "photos": [ + "https://example.com/images/tree_whole.jpg", + "https://example.com/images/tree_detail1.jpg" + ] + } +} +``` + +### 响应示例 JSON + +```json +{ + "code": 0, + "data": 12, + "msg": "" +} +``` +*(注:返回的 `data` 值为新建的巡检评估记录 ID。)* + +--- + +## 2. 获得层级嵌套的巡检记录详情 + +进入巡检历史记录详情页(图2)时,获取该记录所有计算指标及打分结果。该接口返回完全**层次分明、与表单高度对称的结构**,并额外输出每项计算的权重常数、各分类得分与最终安全等级描述。 + +* **接口路径**:`GET /get` +* **请求方法**:`GET` +* **请求参数**: + +| 参数名 | 类型 | 是否必填 | 说明 | +| :--- | :--- | :--- | :--- | +| **id** | Long | 是 | 巡检记录 ID,例如 `12` | + +### 响应示例 JSON + +```json +{ + "code": 0, + "data": { + "id": 12, + "treeId": 1024, + "treenumber": "D0001-P1-0001", + "inspectionTime": "2026-05-26 13:17:00", + "inspectorId": 10001, + "inspectorName": "张三", + "root": { + "disease": 0, + "anchorage": 0, + "cutting": 0 + }, + "collar": { + "woodDamage": 5, + "barkDamage": 0, + "loosening": 0 + }, + "trunk": { + "woodDamage": 0, + "tilt": 3, + "barkDamage": 0 + }, + "crown": { + "looseBranch": 0, + "collarAbnormal": 0, + "ventilationBalance": 1 + }, + "weight": { + "treeSpeciesType": "深根性树种", + "treeSpeciesWeight": 1.0, + "plantingYears": "栽植 10-30 年", + "plantingYearsWeight": 1.1, + "isWindCorridor": false, + "windCorridorWeight": 1.0, + "treePoolType": "联通树池", + "treePoolWeight": 1.0, + "treePoolWidthDbhRatio": "5 倍(含)-7 倍(不含)", + "treePoolRatioWeight": 1.1 + }, + "result": { + "isEmergency": true, + "windPower": "8-9 级", + "windPowerWeight": 1.5, + "defectScore": 9, + "normalResult": { + "score": 10.89, + "level": "II级 (轻度风险)" + }, + "emergencyResult": { + "score": 16.34, + "level": "II级 (轻度风险)" + } + }, + "status": { + "photos": [ + "https://example.com/images/tree_whole.jpg", + "https://example.com/images/tree_detail1.jpg" + ] + } + }, + "msg": "" +} +``` + +--- + +## 3. 分页查询单株树木的历史巡检记录 + +对应“巡检记录列表”原型图(图1),按历史巡检时间倒序分页加载。 + +* **接口路径**:`GET /page` +* **请求方法**:`GET` +* **请求参数**: + +| 参数名 | 类型 | 是否必填 | 示例值 | 说明 | +| :--- | :--- | :--- | :--- | :--- | +| **treeId** | Long | 是 | `1024` | 对应树木档案 ID | +| **pageNo** | Integer | 否 | `1` | 页码,从 1 开始 | +| **pageSize** | Integer | 否 | `10` | 每页行数 | + +### 响应示例 JSON + +```json +{ + "code": 0, + "data": { + "list": [ + { + "id": 12, + "treeId": 1024, + "treenumber": "D0001-P1-0001", + "inspectionTime": "2026-05-26 13:17:00", + "inspectorName": "张三", + "normalLevel": "II级 (轻度风险)", + "emergencyLevel": "II级 (轻度风险)", + "isEmergency": true, + "isTreated": false + }, + { + "id": 2, + "treeId": 1024, + "treenumber": "D0001-P1-0001", + "inspectionTime": "2026-05-06 13:23:23", + "inspectorName": "李四", + "normalLevel": "I级 (基本无风险)", + "emergencyLevel": null, + "isEmergency": false, + "isTreated": true + } + ], + "total": 2 + }, + "msg": "" +} +``` + +--- + +## 3. 管理后台 - 行道树巡检与风险评估接口 + +管理后台的“巡检记录”页签(如图所示)包含“巡检历史列表(左侧)”和“单条巡检详情面板(右侧)”。这两个功能可以直接调用本组管理后台 API: + +* **接口基地址**:`/admin-api/garden/tree-inspection` +* **权限标识**:`garden:tree-inspection:query`(需在管理后台的角色权限中进行配置) + +### 3.1 获得单条巡检评估详情 + +对应管理后台点击左侧列表时,右侧展示的全部缺陷分、常规评估、应急评估以及现场照片(右侧详情面板)。 + +* **接口路径**:`GET /get` +* **请求方法**:`GET` +* **请求参数**:同小程序端,根据主键 `id` 检索。 +* **返回 JSON 结构**:与小程序端 `GET /get` 的响应示例格式**完全一致**。返回分层嵌套的 7 大模块,高度契合后台页面树根、根颈、主干、树冠、权重评估、风险评估结果、现状及照片的区块设计。 + +### 3.2 分页查询单株树木的历史巡检记录 + +对应管理后台中左侧用于折叠展示的多次巡检历史简要列表。 + +* **接口路径**:`GET /page` +* **请求方法**:`GET` +* **请求参数**:同小程序端 `GET /page`,按 `treeId` 进行分页拉取。 +* **返回 JSON 结构**:与小程序端 `GET /page` 的响应示例格式**完全一致**,按巡检时间由近及远倒序排列。 + +--- + +## 常见错误返回状态码 + +| HTTP状态码 / code 码 | 错误提示内容 | 触发原因 | +| :--- | :--- | :--- | +| 400 | `status.photos: 最多只能上传5张现场照片` | 现场采集照片 `photos` 数组长度大于 5 时级联强校验报错 | +| 400 | `treeId: 关联树木ID不能为空` | 请求体中缺失 `treeId` | +| 400 | `inspectionTime: 巡检时间不能为空` | 请求体中缺失 `inspectionTime` | +| 403 | `Forbidden` | 管理后台用户未被授权 `garden:tree-inspection:query` 权限 | +| 500 / `1-100-008-001` | `巡检评估记录不存在` | 查询详情时 ID 在数据库中被标记删除或不存在 | + diff --git a/guide.txt b/guide.txt new file mode 100644 index 0000000..d04003f --- /dev/null +++ b/guide.txt @@ -0,0 +1,1529 @@ +北京市园林绿化局 + +行道树安全风险评估指南 +(试行) +2024 年 11 月 + + 目 录 + +目 录 +前 言 .......................................................................................................1 +第一部分 概 述 ..................................................................................... 3 +1.1 基本概念 ............................................................................................................ 3 +1.2 目的及依据 ........................................................................................................ 3 +1.3 适用范围 ............................................................................................................ 3 +1.4 评估原则 ............................................................................................................ 4 +1.5 评估周期 ............................................................................................................ 5 +1.6 方法与工具 ........................................................................................................ 5 + +第二部分 评估体系 ............................................................................... 7 +2.1 评估指标体系 .................................................................................................... 7 +2.2 风险得分计算 .................................................................................................. 10 +2.3 风险等级判定 .................................................................................................. 11 + +第三部分 评估流程 ............................................................................. 13 +3.1 整体流程 .......................................................................................................... 13 +3.2 划分风险区域 .................................................................................................. 13 +3.3 道路初筛 .......................................................................................................... 14 +3.4 精细评估 .......................................................................................................... 17 +3.5 评估资料归档 .................................................................................................. 22 + +第四部分 风险控制 ............................................................................. 25 +4.1 风险防范原则 .................................................................................................. 25 +4.2 常规防范措施 .................................................................................................. 25 +4.3 风险处置措施 .................................................................................................. 26 +4.4 应急抢险 .......................................................................................................... 30 + +第五部分 附录 ..................................................................................... 31 +附录 1 风险指标判断示例 ..................................................................................... 31 + +i + + ii + + 前 言 +本指南由北京市园林绿化局组织实施。 +本指南起草单位:北京市园林绿化局城镇绿化处、北京 +民生智库科技信息咨询有限公司。 +本指南主要起草人:刘明星、周红英、朱永和、申明华、 +陈晓晶、姚士才、常广新、杨志华、吴斌、巢阳、张华 +伟、郭珺琪、张绮思、高天宇、胥心楠、池伯佳、胡嘉 +琪、陈季琴、刘丽婕、杨曦。 +本指南为首次发布。 + +1 + + 行道树安全风险评估指南(试行) + +2 + + 第一部分 概 述 + +第一部分 概 述 +1.1 基本概念 +行道树指种植于道路两侧及分车带、具有一定遮荫功能 +并构成街景的乔木。 +行道树安全风险是城市公共安全风险的一部分,本指南 +所指的行道树安全风险指行道树发生倒伏、断折、落枝等事 +件的可能性。 +行道树安全风险评估是识别、分析和评价行道树发生倒 +伏、断折、落枝可能性的过程。 +1.2 目的及依据 +为了科学评估行道树的潜在安全危险,及时采取处置措 +施,消除行道树安全隐患,依据《风险管理 + +风险评估技术》 + +(GBT27921-2023),参考《城市树木健康诊断技术规程》 +(DB11T 1692-2019)等规范文件,结合本市行道树实际情况, +编制本指南。 +1.3 适用范围 +本指南适用于本市范围内行道树(不含古树名木)的安 +全风险评估,具体评估对象包括种植于人行道、机非隔离带、 +主辅路隔离带、中央隔离带、其他道路两侧的乔木。 +行道树安全风险评估应由具有园林绿化中级及以上技 +术职称并有绿地养护管理经验的人员组织开展。 +3 + + 行道树安全风险评估指南(试行) + +1.4 评估原则 +北京市行道树数量庞大,在资源有限的情况下,应先聚 +焦重点区域,再聚焦单株树木开展安全风险评估。根据北京 +市气象特征,冬季大风和汛期为行道树安全事件多发时段, +在常规情况下定期开展安全风险评估的同时,还应在极端大 +风天气频发季节开展应急评估。 + +聚焦重点区域。根据行道树倒伏、断折、落枝等事件对 +公共安全影响的严重程度划分风险区域类别,其中,人员密 +集、交通流量大的主次干路、景区、公园周边道路、城市风 +口位置等区域为高风险区域,行道树安全风险评估应优先保 +障高风险区域。不同风险区域分类参考见表 1。 +表 1 行道树安全风险区域分类 +类别 + +解释 + +示例 +1.环路、主干路、次干路等重点道路; +2.环路、主干路、次干路以外人流量、车流量大的道路; + +高风险 + +频繁使用、人员密 + +区域 + +集的道路 + +3.位于政务活动场所、国际交往场所等重要区域的道路; +4.位于重点交通枢纽、商务区、会展区、商圈、重点公园等人 +员活动密集区域的道路; +5.河道两侧、十字路口、立交桥周边、城区高楼之间的狭窄地 +带等具有典型风口特征的位置。 + +中风险 +区域 + +使用频率中等、人 +员密集程度略低 +的道路 + +1.支路等车流量、人流量中等的道路; +2.位于一般公园、社区、普通医院等周边的道路。 + +低风险 + +使用频率较低的 + +1.街巷等非重点道路人流量、车流量较少的道路; + +区域 + +道路 + +2.位于开放区域、林地、边缘区域等远离人活动区域的道路。 + +聚焦单株树木精细评估。具体到每一条道路,首先应使 +用目视法或简单工具对行道树进行初筛,筛选出具有风险特 +征的单株行道树,对其进行精细评估。 +4 + + 第一部分 概 述 + +聚焦重要时段。冬季大风和汛期为行道树安全风险防范 +的重要时段,常规情况下的安全风险评估工作应在重要时段 +一个月前完成,重要时段应结合气象预警信息开展应急评估, +以便提前采取防范措施。 +1.5 评估周期 +常绿行道树安全风险评估全年均可进行,落叶行道树安 +全风险评估建议在生长期进行。评估开展频次根据实际管理 +需求确定,高风险区域建议每半年开展一次,且至少有一次 +于汛期前完成。 +1.6 方法与工具 +本指南以目视法为主,使用到的简易工具包括:胸径尺、 +卷尺、测高器、橡皮锤、量角器等。 + +5 + + 行道树安全风险评估指南(试行) + +6 + + 第二部分 评估体系 + +第二部分 评估体系 +2.1 评估指标体系 +行道树安全风险评估指标体系包括【常规情况下的评估 +指标】和【应急情况下的评估指标】。常规情况下的评估指 +标适用于非汛期常规气象条件下的评估,应急情况下的评估 +指标适用于汛期或极端天气来临之前的评估。 +评估指标体系的整体框架如图 1 所示。 + +图 1 行道树安全风险评估指标体系整体框架 + +7 + + 行道树安全风险评估指南(试行) + +2.1.1 常规情况下的评估指标体系 +常规情况下,行道树安全风险评估从【树木缺陷】【树 +木生理特性】和【树木生境】三个维度展开。 +【树木缺陷】维度,依据“根本末”对安全风险的影响 +力递减的原则,依次对“树根”“根颈”“主干”“树冠” +四个部位的 12 项指标进行赋分,分值越大,风险越高。满 +分为 100 分,其中,树根部位分值为 20 分,根颈部位分值 +为 31 分,主干部位分值为 33 分,树冠部位分值为 16 分, +各部位所占分值见图 2。 + +图 2 【树木缺陷】指标中各部位所占分值示意图 + +8 + + 第二部分 评估体系 + +其中,“树根”包括根部病害、根系下扎情况和工程切 +根 3 项指标;“根颈”包括木质部受损、树皮受损 、根颈松 +动 3 项指标;“主干”包括木质部受损、主干倾斜、树皮受 +损 3 项指标;“树冠”包括易落枝、枝干结合部异常、树冠 +透风情况及平衡性 3 项指标。 +将五种缺陷较为严重的情况设置为“一票否决”项,直 +接赋分为 100 分(判定为“极度风险”)或 70 分(判定为 +“重度风险”),详见表 2。 +表 2 【树木缺陷】指标中的“一票否决”项 +“一票否决”项 +根颈松动 + +赋分及对应的风险等级 +直接赋 100 分,归入极度风险 + +根颈木质部受损达到 50%及以上 + +直接赋 70 分,归入重度及以上风险 + +主干木质部受损达到 50%及以上 + +直接赋 70 分,归入重度及以上风险 + +主干倾斜≥30° + +直接赋 70 分,归入重度及以上风险 + +枝干结合部异常有明显空洞或蛀干痕迹 + +直接赋 70 分,归入重度及以上风险 + +【树木生理特性】维度包括“树种类型”和“栽植年限” +两项指标,均为权重指标,权重越高,对风险的影响越大。 +“树种类型”分为深根性树种和浅根性树种,后者权重较高; +“栽植年限”分为栽植 10 年以内、栽植 10-30 年、栽植 30 +年以上三档,年限越长,权重越高。 +【树木生境】维度包括“是否处于风口”“树池类型” +和“树池宽度与胸径比”三项指标,均为权重指标,权重越 +9 + + 行道树安全风险评估指南(试行) + +高,对风险的影响越大。处于风口位置的行道树在大风天气 +下承受了更大的风力,赋予较高权重;“树池类型”方面, +分联通树池、独立树池、树池硬化三种情况,赋予的权重依 +次增大;树池宽度与胸径的比值越大,赋予的权重越小。 +2.1.2 应急情况下的评估指标体系 +汛期或极端天气频发季节,应结合气象预警信息开展应 +急评估。应急情况下的评估指标体系包括“常规情况下安全 +风险评估的全部指标”和“极端天气指标”。极端天气指标 +中将“风力”作为权重因子进行赋权,风力越大,权重越高。 +其中,风力 7 级及以下、8-9 级、10 级和 10 级以上的权重 +值分别为 1.0、1.5、2.0、3.0。 +2.2 风险得分计算 +常规情况下,行道树安全风险评估得分计算公式为: +常规情况下安全风险得分 =【树木缺陷】各项指标得分相加 +×【树木生理特性】各因子权重 +×【树木生境】各因子权重 + +应急情况下,行道树安全风险评估得分计算公式为: +应急情况下安全风险得分 =【树木缺陷】各项指标得分相加 +×【树木生理特性】各因子权重 +×【树木生境】各因子权重 +×【极端天气】因子权重 + +10 + + 第二部分 评估体系 + +2.3 风险等级判定 +根据安全风险得分进行风险等级判定。行道树安全风险 +等级分 5 级,安全风险得分与风险等级对应关系见表 3。 +表 3 行道树安全风险等级划分 +安全风险得分 + +风险等级 + +风险等级描述 + +[0,10) + +I级 + +基本无风险 + +[10,30) + +II 级 + +轻度风险 + +[30,70) + +III 级 + +中度风险 + +[70,100) + +IV 级 + +重度风险 + +100+ + +V级 + +极度风险 + +说明:当风险得分为临界值时,归入下一个较高风险等级。 + +11 + + 行道树安全风险评估指南(试行) + +12 + + 第三部分 评估流程 + +第三部分 评估流程 +3.1 整体流程 +评估前应做好准备工作,确定每次评估实施范围,统筹 +安排人员和工具。由具有园林绿化中级及以上技术职称并有 +行道树养护管理经验的人员牵头,组织辖区内管养单位的骨 +干技术人员参与,并对参与人员进行充分培训。准备数量充 +足的工具和调查评估表格。 +具体实施时,先划分风险区域类别,对道路进行风险初 +筛,确定需要调查的道路后聚焦道路上“有风险”的单株树 +进行精细评估。评估完成后应将评估资料进行归档,建立风 +险评估台账。 + +图 3 行道树安全风险评估整体流程 + +3.2 划分风险区域 +综合考虑人员密集度、交通流量、道路等级等因素,将 +辖区内的道路划分为高风险区域、中风险区域和低风险区域, +划分示例参考《表 1 行道树安全风险区域分类》。有条件 +的应绘制不同风险区域的道路分布图,为评估工作提供可视 +化参考。风险区域分布图示例见图 4。 +13 + + 行道树安全风险评估指南(试行) + +图 4 不同风险区域的道路分布图 + +根据风险类别的优先级和现有人力物力条件,确定每次 +评估拟覆盖的范围,原则上高风险区域应先行实施,若一次 +评估无法覆盖所有道路,应制定分步评估计划,确保所有道 +路每年至少评估一次。 +在设计调查路线时,需兼顾科学性和合理性原则,以保 +证评估工作的效率。 +3.3 道路初筛 +优先对高风险区域道路开展全覆盖初筛,中风险和低风 +险区域根据安排适时开展。道路过长时,可分段进行。 +采用目视法结合简易工具,对道路上行道树进行观察, +快速筛选具有以下任意一种特征的行道树: +14 + + 第三部分 评估流程 + +(1)根颈松动; +(2)根颈或主干木质部受损 10%以上; +(3)主干倾斜 20 度以上; +(4)枝干结合部有明显异常; +(5)处于风口且树冠结构明显失衡。 +对筛选出的行道树涂抹标记,以便开展精细评估。道路 +初步筛查使用的调查表见表 4。 +表 4 行道树安全风险初步筛查表 +所在区:____________ +存在以下风险表征的行道树 +道路 + +道路 + +编号 + +名称 + +风险区域分类 根颈 +松动 + +根颈或主干 + +主干倾 + +枝干结合 处于风口且 + +木质部受损 斜 20 度 部有明显 树冠结构明 +10%以上 + +以上 + +异常 + +显失衡 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +评估时间: + +年 + +高风险区域 +中风险区域 +低风险区域 +高风险区域 +中风险区域 +低风险区域 +高风险区域 +中风险区域 +低风险区域 +高风险区域 +中风险区域 +低风险区域 +高风险区域 +中风险区域 +低风险区域 +高风险区域 +中风险区域 +低风险区域 + +是否纳 +入精细 +评估 + +是 +否 +是 +否 +是 +否 +是 +否 +是 +否 +是 +否 + +评估单位: +评估人: + +月 + +日 + +15 + + 行道树安全风险评估指南(试行) +填表说明: +(1)道路编号 +D+四位数字,从 D0001 开始,对辖区内的道路依次编号,确保每条道路有 +唯一编号。 +(2)风险区域分类 +参考本指南《表 1 行道树安全风险区域分类》,结合本区实际情况进行划 +分。 +(3)风口 +受狭管效应影响,气流由开阔地带流入风口时,风速会急剧增大,狭管效 +应示意图见图 5。 + +图 5 城市建筑群间的狭管效应示意图 +处于风口位置的行道树具有较高的安全风险,在行道树初筛时,准确判断 +“风口”位置至关重要。判断“风口”的一般性建议如下: +“风口”的判断 +风口一般位于受狭管效应影响的地带、水面附近等,包括但不限于以下地点: + +16 + + + +河道两侧; + + + +湖面周边的迎风面; + + + +十字路口各个方位第一株行道树,尤其是东南角; + + + +立交桥周边的各方位第一株行道树,尤其是东南角; + + + +城区内高楼大厦间瞬间风力加强的狭窄地带; + + + +开敞空间,周边无任何遮挡物。 + + 第三部分 评估流程 + +3.4 精细评估 +根据道路初筛结果,选择初步筛查标记出的风险树,开 +展精细评估,填写表 5。按照“树根-根颈-主干-树冠”的 +顺序,依次填写树木缺陷各项指标得分,记录树木生理特性 +和树木生境各项权重因子的权重值,根据各项得分或权重值 +计算风险得分,并判断对应的风险等级。 +表 5 行道树安全风险精细评估调查表 +风险树编号:D-P- +(一)基本信息表 +树种 + +栽植位置 + +树高(m) + + 人行道 + +胸径(cm) + +点位坐标 + +一级指标 二级指标 + +X: + +根系下扎情况 +工程切根 + +木质部受损 +树木缺陷 +根颈 +树皮受损 + +根颈松动 +主干 + +木质部受损 + + 路侧绿地 + +冠幅(m) +Y: + +(二)行道树缺陷评估 +三级级指标 +评分标准 +根部病害 + +树根 + + 分车带 + +赋分 + +无真菌危害或腐朽情况 + +0 + +存在真菌危害或腐朽情况 + +8 + +根部下扎良好,无盘根或隆起 + +0 + +存在根部隆起或盘根 + +7 + +无工程切根 + +0 + +存在工程切根 + +5 + +无受损情况 + +0 + +受损程度<10% + +5 + +受损程度介于 10%(含)-30%(不含) + +15 + +受损程度介于 30%(含)-50%(不含) + +25 + +受损程度≥50% + +70 + +受损程度<10% + +0 + +受损程度介于 10%-30% + +2 + +受损程度介于 30%-50% + +4 + +受损程度≥50% + +6 + +不存在根颈松动 + +0 + +存在根颈松动 + +100 + +无受损情况 + +0 + +受损程度<10% + +5 + +得分 + +17 + + 行道树安全风险评估指南(试行) +一级指标 二级指标 + +三级级指标 + +主干倾 + +树皮受损 + +易落枝 + +树冠 +(16) + +评分标准 + +赋分 + +受损程度介于 10%(含)-30%(不含) + +12 + +受损程度介于 30%(含)-50%(不含) + +20 + +受损程度≥50% + +70 + +倾斜度<10° + +0 + +倾斜度介于 10°(含)-20°(不含) + +3 + +倾斜度介于 20°(含)-30°(不含) + +8 + +倾斜度≥30° + +70 + +受损程度<10% + +0 + +受损程度介于 10%(含)-30%(不含) + +1 + +受损程度介于 30%(含)-50%(不含) + +3 + +受损程度≥50% + +5 + +未发现易落枝 + +0 + +易落枝占整个树冠枝条数量的比例< +1/10 + +2 + +易落枝占整个树冠枝条数量的比例≥ +1/10 + +3 + +无异常 + +0 + +枝干结合部异 有龟裂或卷皮情况 +常 +有腐烂现象但尚未形成明显空洞 + +3 +5 + +有明显空洞或蛀干痕迹 + +70 + +透风情况较好,不偏冠 + +0 + +【透风情况较好但有明显偏冠】或【透 +树冠透风情况 风性差但冠幅适中,不偏冠】 +透风性差且明显偏冠,但冠幅适中 +及平衡性 + +得分 + +1 +2 + +透风性差且冠幅较大,但不偏冠 + +5 + +透风性差、冠幅较大且明显偏冠 + +8 + +缺陷评估得分 +(三)权重因子评估 +一级权重因子 + +二级权重因子 +树种类型 + +树木生理特性 +栽植年限 + +树木生境 + +是否处于风口 +树池类型 + +18 + +权重标准 + +赋权 + +深根性树种 + +1.0 + +浅根性树种 + +1.1 + +栽植 10 年以内 + +1.0 + +栽植 10-30 年 + +1.1 + +栽植 30 年以上 + +1.2 + +否 + +1.0 + +是 + +2.0 + +联通树池 + +1.0 + +权重值 + + 第三部分 评估流程 +独立树池 + +1.2 + +树池硬化 + +1.5 + +7 倍及以上 + +1.0 + +树池宽度与胸径 5 倍(含)-7 倍(不含) +比 +3 倍(含)-5 倍(不含) + +1.1 + +3 倍以下 + +1.3 + +1.2 + +常规情况下的评估得分 +(四)应急评估增项指标 +一级权重因子 + +二级权重因子 + +极端天气 + +风力 + +权重标准 + +赋权 + +7 级及以下 + +1.0 + +8-9 级 + +1.5 + +10 级 + +2.0 + +10 级以上 + +3.0 + +权重值 + +(五)风险得分及等级判定 +计算安全风险得分 +风险得分所属区间 + +对应风险等级 + +□ 得分<10 + +□ 基本无安全风险 + +□ 10≤得分<30 + +□ 轻度安全风险 + +□ 30≤得分<70 + +□ 中度安全风险 + +□ 70≤得分<100 + +□ 重度安全风险 + +□ 得分≥100 + +□ 极度安全风险 + +选择安全风险等级 + +评估单位: +评估人: + +评估时间: + +年 + +月 + +日 + +填表说明: +(1)风险树编号 +按“道路编号-树群单元编号-行道树排号-顺序编号”的规则进行编号,即 +DXXXX-PX-XXXX。 +行道树排号编写规则:P+1 位数字,南北走向道路按照从东向西的顺序依 +次命名 P1、P2、P3… …,东西走向的道路按照从北向南的顺序依次命名 P1、 +P2、P3… …,示例见图 6。 + +19 + + 行道树安全风险评估指南(试行) + +图 6 行道树排号编写规则示例 +比如,道路编号为 D0601,排号为 P1,则该树群单元中的第一株风险树编号 +为 D0601-P1-0001。 +(2)冠幅(m) +东西方向和南北方向冠幅的平均值。 +(3)根部病害 +仔细检查树根部位是否存在大型真菌或有明显的腐朽症状,若有则此项指 +标得分为 8 分。 +(4)根系下扎情况 +观察根系是否存隆起或盘根现象,若有则此项指标得分为 7 分。 +(5)工程切根 +观察根部是否存在切口,调查人员也可以根据施工记录判定是否存在工程 +切根情况。 +(6)根颈木质部受损 +根颈部位木质部受损情形包括腐朽、空洞、木质部开裂、明显的蛀干害虫 +痕迹等,受损程度以最大受损截面占根颈截面面积的比例计算,根据测算的受 +损比例选择相应的分值填写,若木质部受损程度大于等于 50%,则达到“一票 +否决”标准,直接得分 70,归入“重度风险”。 +(7)根颈树皮受损 +根颈部树皮受损情形包括树皮脱落、木质部裸露、树皮机械损伤等,受损 +程度以受损最大弧长占根颈周长的比例计算,根据测算的受损比例选择相应的 +分值填写。 +(8)根颈松动 +用力推动树干,观察根颈部位是否有明显晃动,此项为“一票否决”项, +若存在根颈松动情况,直接得分 100,归入极度风险。 +20 + + 第三部分 评估流程 +(9)主干木质部受损 +主干部位木质部受损情形包括腐朽、空洞、木质部开裂、明显的蛀干害虫 +痕迹等。受损程度以最大受损截面占主干截面面积的比例计算,主干外观无异 +常,应使用橡皮锤敲击,如有不同于正常树干的声音,判断为存在空腐。根据 +测算的受损比例选择相应的分值填写。若木质部受损程度大于等于 50%,则达 +到“一票否决”标准,直接得分 70,归入“重度风险”。 +(10)主干倾斜 +使用量角器测量树干偏离竖直方向的度数,根据测量结果选择相应的分值 +填写,若主干倾斜程度大于等于 30°,则达到“一票否决”标准,直接得分 70, +归入“重度风险”。 +(11)主干树皮受损 +主干部树皮受损情形包括树皮脱落、木质部裸露、树皮机械损伤等,受损 +程度以受损最大弧长占主干周长的比例计算,根据测算的受损比例选择相应的 +分值填写。 +(12)易落枝 +观察是否存在易落枝,包括枯死枝、蛀干枝或已折断未掉落的枝条,估算 +易落枝数量占树冠枝条数量的比例。根据观察结果选择相应的分值填写。 +(13)枝干结合部异常 +观察枝干结合部位是否存在异常,包括是否有龟裂或卷皮、腐烂、空洞或 +蛀干痕迹。根据异常程度选择相应的分值填写,若发现“有明显空洞或蛀干痕 +迹”,则达到“一票否决”标准,直接得分 70,归入“重度风险”。 +(14)树冠透风情况及平衡性 +观察是否存在树冠密不透风、冠幅过大或偏冠的情况,根据观察结果选择 +相应的分值填写。 +(15)树种类型 +常见树种根系类型划分参见表 6。 +表 6 常见树种根系分类 +序号 + +树种 + +根系分类 + +序号 + +树种 + +根系分类 + +1 + +国槐 + +深根性 + +8 + +栾树 + +深根性 + +2 + +刺槐 + +浅根性 + +9 + +垂柳 + +浅根性 + +3 + +白蜡 + +深根性 + +10 + +旱柳 + +浅根性 + +4 + +银杏 + +深根性 + +11 + +千头椿 + +浅根性 + +5 + +悬铃木 + +浅根性 + +12 + +臭椿 + +浅根性 + +6 + +毛白杨 + +深根性 + +13 + +油松 + +深根性 + +7 + +加杨 + +深根性 + +14 + +毛泡桐 + +浅根性 + +21 + + 行道树安全风险评估指南(试行) + +3.5 评估资料归档 +3.5.1 照片的采集 +对开展精细评估的每株行道树,均需拍摄现状照片,照 +片数量不少于 3 张且包括以下角度取景: +整体取景:含周边参照物,以展示单株行道树的树体形 +态及具体位置; +树木生境照片:展示树池形态、周边设施、建筑等; +细节照片:从不同角度清晰展示树木存在的缺陷问题, +如行道树存在多处缺陷,则每种症状的特写均需采集。 +此外,如遇处于风口的行道树,则需拍摄周边风场环境 +的照片或视频。 +3.5.2 点位的采集 +采集完风险树的信息和照片后,采集风险树点位坐标, +以树木编号命名,并以矢量数据保存。 +3.5.3 资料归档 +将各项调查评估信息进行汇总整理,形成行道树风险管 +理档案,具体包括: + 风险区域划分图、道路初筛表、精细评估调查表; + 对调查评估表格进行电子化,形成风险台账(见表 7); + 照片文件夹以树木编号命名,形成照片库; + 点位以树木编号命名,以矢量格式储存,与调查信息 +关联,形成行道树风险点位矢量数据库。 +22 + + 第三部分 评估流程 + +表 7 行道树安全风险台账表 +基本信息 +风险树 + +所属 + +所属道 + +X坐 + +Y坐 + +编号 + +区 + +路 + +标 + +标 + +树种 + +风险评估结果 +树高 + +胸径 + +冠幅 + +栽植 + +(m) + +(cm) + +(m) + +位置 + +常规情况 + +应急情况 + +下的风险 + +下的风险 + +等级 + +等级 + +风险处置 +建议处置 +措施 + +是否已处 +置 + +评估记录 +评估 +时间 + +评估单位 + +评估人 + +23 + + 行道树安全风险评估指南(试行) + +24 + + 第四部分 风险控制 + +第四部分 风险控制 +4.1 风险防范原则 + +坚持保护优先的原则。行道树安全风险管理要严格坚持 +保护优先原则,重在找出问题,提出改善措施。 + +安全风险分级管控原则。重度及以上安全风险树木及早 +处置,中度、轻度安全风险树木密切监测,采取缓解和保护 +措施。 +4.2 常规防范措施 +行道树作为有生命的城市基础设施,其安全风险管理贯 +穿树种规划、设计、施工、管养的整个过程。 +行道树树种规划阶段,应因地制宜,适地适树,优选抗 +性强、耐修剪、易栽活的乡土树种;设计阶段宜考虑丰富树 +种多样性和合理布局与搭配,提高行道树抗风险能力;施工 +阶段应关注施工质量和树木保护,确保严格按照相关规范和 +标准进行,工程验收时将安全风险指标考虑在内;管养阶段, +除日常养护工作外,还应针对长势不良的行道树及时采取树 +池扩大、联通树池改造或土壤改良等复壮措施。 +建立常态化安全风险评估机制,实现行道树安全风险台 +账动态管理,并及时采取措施消除风险。注重收集的行道树 +安全事故数据,建立风险预警数据库,针对往年行道树安全 +事件频发的重点区域、位置设置警示标识,完善应急预案, +加强巡查监测,提升行道树安全风险管理的有效性。 + +25 + + 行道树安全风险评估指南(试行) + +4.3 风险处置措施 +行道树安全风险是行道树健康风险的重要组成部分,按 +照保护优先和分级管控的原则,对于不同风险等级的行道树 +采取不同措施进行管理。 +评估为“轻度风险”“中度风险”的行道树,应依托树 +木医开展健康管理,减缓风险发展速度。 +评估为“重度风险”和“极度风险”的行道树,应立即 +采取措施,消除风险隐患。 +表 8 行道树安全风险等级对应的处置措施 +风险等级 + +风险等级描述 + +I级 + +基本无风险 + +II 级 + +轻度风险 + +III 级 + +中度风险 + +IV 级 + +重度风险 + +V级 + +极度风险 + +处置建议 +正常养护,每年定期开展安全风险评估。 +根据道路实际情况,结合存在的风险隐患点,开展修剪、支撑、创 +面或空洞修复、病虫害防治等治理措施,每半年巡查 1 次。 +加强巡查频次,每季度巡查一次,适时采取风险减缓措施。 +在极端天气来临前,特别是处于人流量较大区域风险树,在完成评 +估后 4 周内采取措施。对此类树木密切监察,直至风险降级或解除。 +立即采取措施,进行树木移除、危险枝清理或其他减轻风险的措施。 + +4.3.1 针对特殊情形树木的移除 +一般情况下,能采取风险减缓措施的树木均不建议进行 +移除。 +针对风险等级达到重度安全风险及以上且不具备采取 +风险减缓措施条件的行道树,建议进行移除,并适时补植适 +宜规格的苗木,以保证景观效果。 + +26 + + 第四部分 风险控制 +建议进行树木移除的情形 + + +栽植 2 年以上且根颈出现松动时,应及时采取措施,进行树木移除; + + + +树根存在严重腐烂,无法起到支撑作用的行道树,应及时移除; + + + +木质部受损(空腐率)超过 50%且树势较弱的行道树; + + + +主干倾斜 30 度以上且无法通过缩冠减缓风险或不具备增加支撑条件的行道树; + + + +其他经过专家研讨一致认定应及时移除的行道树。 + +4.3.2 针对木质部受损的行道树进行空洞、腐朽处理 +根颈、主干及大枝的木质部受损,尤其是出现腐朽、空 +洞时,应及时采取处理措施。 +(1)木质部腐朽处理措施 +主干、主枝上有明显裸露腐朽木质部的,首先清除木质 +部表面的松软碎末等杂物,不损伤活组织的前提下,使用已 +消毒的工具修整至活组织,喷洒杀菌剂后,再均匀喷洒水溶 +性防腐剂,待自然风干后均匀涂抹纯熟桐油等天然环保的防 +腐材料。 +(2)木质部空洞处理措施 +主干或主枝上有明显树洞的,针对不同类型空洞,建议 +采取不同的处置措施。树体修复施工宜在树木休眠期、天气 +干燥时进行。 +建议采取的空洞修复方式 + + +不易积水、存水的树洞,做好防腐处理,不填充封堵。 + + + +易积水但不影响树体安全,可在适当位置设导流管(孔)顺利排出的树洞,做好防腐 +处理不填充封堵。 + + + +敞开式、贯通式树洞不填充封堵,做好导水、防腐及安全加固处理。 + + + +易进水、存水的树洞,应封堵洞口,做好排水、通风处理。 + +27 + + 行道树安全风险评估指南(试行) + +4.3.3 针对倾斜的行道树采取支撑措施 +针对倾斜且具备支撑、加固条件的行道树,在不妨碍车 +辆、行人通行的情况下进行支撑、加固。树体外观明显倾斜 +的行道树,宜采用“人字”硬支撑、拉纤等方法进行支撑、 +加固。主干有劈裂倾倒隐患或树冠上有断裂隐患的分枝间可 +采用抱箍和相互拉纤进行加固。 +4.3.4 针对偏冠、树冠过大的行道树开展缩冠修剪 +行道树的修剪以冬春季(休眠期)修剪为主,夏季(生 +长期)修剪为辅,结合大风汛期前的应急修剪进行。 +针对存在偏冠的行道树,适当进行缩冠修剪。修剪时, +应对生长势较弱一方的枝条适当长放或轻剪,对生长势较强 +一侧适当回缩,以此达到平衡生长势。 +树冠过大的行道树需进行缩冠修剪,尤其是针对处于风 +口树冠过大的行道树,必须进行缩冠修剪。按照“由外及里、 +由上到下”的顺序进行修剪,注意保留大部分枝条顶芽,避 +免出现截干、重修剪等现象。修剪时应保持树木冠幅及树冠 +高度与树干适当比例,冠幅宜占全树高度的 1/3~1/2,树冠 +高度宜占全树高度的 1/2~2/3。 +4.3.5 针对枝条过密的行道树进行疏枝 +过密枝条的疏枝按照“一知、二看、三剪、四拿、五处 +理、六保护”的程序进行操作: +一知:参加修剪的全体人员,应明确修剪原则,知道操 +作规程、技术规范及特殊要求; +28 + + 第四部分 风险控制 + +二看:修剪前先绕树观察,对树木的修剪方法做到心中 +有数; +三剪:根据因地制宜,因树修剪的原则,合理修剪; +四拿:修剪下来的枝条,及时清运,保证环境整洁; +五处理:剪下的枝条要及时处理,防止病虫害蔓延; +六保护:疏除大枝、粗枝时,应保护树体。 +4.3.6 针对枝干结合部异常的修剪 +针对枝干结合部存在异常,导致连接部位脆弱易发生断 +折的行道树,应及时去异常部位的枝条,避免产生大枝劈裂 +的风险。修剪时应注意避开枝领,降低剪切面受到病虫害侵 +染的几率,修剪后及时涂抹愈合剂。 +4.3.7 针对蛀干害虫的防治措施 +常见的蛀干害虫包括鞘翅目的吉丁虫、天牛、小蠹、象 +甲等,鳞翅目的木蠹蛾、小卷蛾、松梢螟、透翅蛾等,膜翅 +目的树蜂等。 +按照“预防为主,综合防治”的原则,做到安全、经济、 +及时、有效。及时采取物理防治手段,包括诱杀、阻止上树、 +人工捕捉、摘除网幕、剪除病虫枝等。宜采用生物防治手段, +保护和利用天敌。采用化学防治措施时,选择符合环保要求 +的低毒农药。交替使用不同的药剂,减少喷药次数。 + +29 + + 行道树安全风险评估指南(试行) + +4.4 应急抢险 +管理单位应建立组织全面、职能明确、运行有效的应急 +抢险组织架构(见图 7),确保行道树安全风险管理有效。 +组织架构设立应急抢险领导小组,下设应急抢险办公室、后 +勤保障组、现场调查组和应急抢险组。组织架构中相关人员 +各司其职,保障应急信息、应急指令的及时传递,并定期开 +展培训和演练。 + +图 7 应急抢险组织架构 + +行道树安全风险事故发生后,应根据险情不同及时启动 +相应的应急抢险,以保障抢险工作高效、有序进行。当树木 +完全倒伏至地面或高度较低,采用普通锯除,再将大枝、主 +枝、主干截段,进行运输处理;当树木倒伏或断枝的位置过 +高时,采用高空移除,先将中级枝以上分枝去除,再将大枝、 +主枝、主干截断,再截段运输;若发生倾斜或挤压建筑的树 +木过大过高,为了防止二次破坏,采用搭建脚手架辅助移除。 + +30 + + 第五部分 附录 + +第五部分 附录 +附录 1 风险指标判断示例 +5.1.1 根部病害示例 + +蜜环菌 + +多孔菌 + +鬼伞 + +鬼伞 + +图 8 根部病害示例 + +5.1.2 根系下扎不良示例 + +31 + + 行道树安全风险评估指南(试行) +根部隆起 + +根部隆起 + +盘根 + +盘根 + +图 9 根系下扎不良示例 + +5.1.3 工程切根示例 + +图 10 施工切根示例 + +32 + + 第五部分 附录 + +5.1.4 根颈木质部受损示例 + +图 11 根颈木质部受损示例 + +5.1.5 根颈树皮受损示例 + +图 12 根颈树皮受损示例 + +33 + + 行道树安全风险评估指南(试行) + +5.1.6 主干木质部受损示例 + +图 13 主干部位木质部受损示例 + +34 + + 第五部分 附录 + +5.1.7 主干倾斜示例 + +图 14 主干倾斜示例 + +5.1.8 主干树皮受损示例 + +图 15 主干部位树皮受损示例 + +5.1.9 易落枝示例 + +图 16 易落枝示例 + +35 + + 行道树安全风险评估指南(试行) + +5.1.10 枝干结合部异常示例 + +图 17 枝干结合部异常示例 + +5.1.11 树冠透风情况及平衡性示例 + +36 + + 第五部分 附录 + +树冠密不透风且冠幅大 + +偏冠 + +图 18 树冠透风情况及平衡性示例 + +37 + + 行道树安全风险评估指南(试行) + +38 + + \ No newline at end of file diff --git a/sql/mysql/garden_tree_inspection.sql b/sql/mysql/garden_tree_inspection.sql new file mode 100644 index 0000000..2847077 --- /dev/null +++ b/sql/mysql/garden_tree_inspection.sql @@ -0,0 +1,66 @@ +-- 行道树巡检与安全风险评估表 +CREATE TABLE IF NOT EXISTS `garden_tree_inspection` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `tree_id` bigint NOT NULL COMMENT '一树一档案ID', + `treenumber` varchar(64) DEFAULT NULL COMMENT '树木编号', + `inspection_time` datetime NOT NULL COMMENT '巡检时间', + `inspector_id` bigint DEFAULT NULL COMMENT '巡检人ID', + `inspector_name` varchar(64) DEFAULT NULL COMMENT '巡检人姓名', + `dept_id` bigint DEFAULT NULL COMMENT '部门ID', + + -- 缺陷评估指标得分 (数值直接存对应选项的分数) + `root_disease` int DEFAULT '0' COMMENT '根部病害:0-无真菌危害或腐朽,8-存在真菌危害或腐朽', + `root_anchorage` int DEFAULT '0' COMMENT '根系下扎情况:0-良好,7-存在隆起或盘根', + `root_cutting` int DEFAULT '0' COMMENT '工程切根:0-无,5-存在', + `collar_wood_damage` int DEFAULT '0' COMMENT '根颈木质部受损:0-无,5-<10%, 15-10%-30%, 25-30%-50%, 70->=50%', + `collar_bark_damage` int DEFAULT '0' COMMENT '根颈树皮受损:0-<10%, 2-10%-30%, 4-30%-50%, 6->=50%', + `collar_loosening` int DEFAULT '0' COMMENT '根颈松动:0-不存在,100-存在', + `trunk_wood_damage` int DEFAULT '0' COMMENT '主干木质部受损:0-无,5-<10%, 12-10%-30%, 20-30%-50%, 70->=50%', + `trunk_tilt` int DEFAULT '0' COMMENT '主干倾斜:0-<10度, 3-10-20度, 8-20-30度, 70->=30度', + `trunk_bark_damage` int DEFAULT '0' COMMENT '主干树皮受损:0-<10%, 1-10%-30%, 3-30%-50%, 5->=50%', + `crown_loose_branch` int DEFAULT '0' COMMENT '易落枝:0-未发现,2-<1/10, 3->=1/10', + `crown_collar_abnormal` int DEFAULT '0' COMMENT '枝干结合部异常:0-无,3-龟裂卷皮,5-腐烂未成洞,70-明显空洞或蛀干痕迹', + `crown_ventilation_balance` int DEFAULT '0' COMMENT '树冠透风及平衡性:0-较好不偏冠, 1-偏冠或透风差不偏冠, 2-透风差明显偏冠但冠幅适中, 5-透风差冠幅大不偏冠, 8-透风差冠幅大且明显偏冠', + + -- 权重因子指标值和赋权值 + `tree_species_type` varchar(32) DEFAULT NULL COMMENT '树种类型:深根性树种, 浅根性树种', + `tree_species_weight` double DEFAULT '1.0' COMMENT '树种类型权重:深根性-1.0, 浅根性-1.1', + `planting_years` varchar(32) DEFAULT NULL COMMENT '栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上', + `planting_years_weight` double DEFAULT '1.0' COMMENT '栽植年限权重:10年内-1.0, 10-30年-1.1, 30年以上-1.2', + `is_wind_corridor` tinyint DEFAULT '0' COMMENT '是否处于风口:0-否, 1-是', + `wind_corridor_weight` double DEFAULT '1.0' COMMENT '风口权重:否-1.0, 是-2.0', + `tree_pool_type` varchar(32) DEFAULT NULL COMMENT '树池类型:联通树池, 独立树池, 树池硬化', + `tree_pool_weight` double DEFAULT '1.0' COMMENT '树池类型权重:联通树池-1.0, 独立树池-1.2, 树池硬化-1.5', + `tree_pool_width_dbh_ratio` varchar(32) DEFAULT NULL COMMENT '树池宽度与胸径比:7 倍及以上, 5 倍-7 倍, 3 倍-5 倍, 3 倍以下', + `tree_pool_ratio_weight` double DEFAULT '1.0' COMMENT '树池比权重:7倍及以上-1.0, 5-7倍-1.1, 3-5倍-1.2, 3倍以下-1.3', + + -- 是否展开应急评估及应急权重 + `is_emergency` tinyint DEFAULT '0' COMMENT '是否进行应急评估:0-否, 1-是', + `wind_power` varchar(32) DEFAULT NULL COMMENT '应急评估极端天气风力:7 级及以下, 8-9 级, 10 级, 10 级以上', + `wind_power_weight` double DEFAULT '1.0' COMMENT '风力权重:7级及以下-1.0, 8-9级-1.5, 10级-2.0, 10级以上-3.0', + + -- 评估结果 + `defect_score` int DEFAULT '0' COMMENT '树木缺陷得分', + `normal_score` double DEFAULT '0' COMMENT '常规情况下安全风险得分', + `normal_level` varchar(64) DEFAULT NULL COMMENT '常规情况下风险等级', + `emergency_score` double DEFAULT NULL COMMENT '应急情况下安全风险得分', + `emergency_level` varchar(64) DEFAULT NULL COMMENT '应急情况下风险等级', + + -- 现状及处理措施 + `treatment_suggestion` varchar(512) DEFAULT NULL COMMENT '建议处置措施', + `is_treated` tinyint DEFAULT '0' COMMENT '是否已处置:0-未处置, 1-已处置', + `photos` varchar(1024) DEFAULT NULL COMMENT '现场照片链接(JSON 数组)', + `estimator_company` varchar(255) DEFAULT NULL COMMENT '评估单位(公司)', + `estimator` varchar(100) DEFAULT NULL COMMENT '评估人', + + + -- 基础审计与租户字段(继承 BaseDO / TenantBaseDO) + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` tinyint DEFAULT '0' COMMENT '是否删除:0-未删除, 1-已删除', + `tenant_id` bigint DEFAULT '0' COMMENT '租户ID', + PRIMARY KEY (`id`), + KEY `idx_tree_id` (`tree_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='行道树巡检与安全评估记录'; diff --git a/urbanops-module-api/urbanops-module-workorder-api/src/main/java/com/zteits/urbanops/module/workorder/dto/AppGardenWorkOrderInspectorReqVO.java b/urbanops-module-api/urbanops-module-workorder-api/src/main/java/com/zteits/urbanops/module/workorder/dto/AppGardenWorkOrderInspectorReqVO.java index 264026d..85de164 100644 --- a/urbanops-module-api/urbanops-module-workorder-api/src/main/java/com/zteits/urbanops/module/workorder/dto/AppGardenWorkOrderInspectorReqVO.java +++ b/urbanops-module-api/urbanops-module-workorder-api/src/main/java/com/zteits/urbanops/module/workorder/dto/AppGardenWorkOrderInspectorReqVO.java @@ -63,6 +63,12 @@ public class AppGardenWorkOrderInspectorReqVO { @Schema(description = "工单名称", example = "绿地卫生") private String orderName; + @Schema(description = "三级编码") + private String orderCode; + + @Schema(description = "业务类型(一级编码)") + private String busiType; + @Schema(description = "来源ID 工单来源 1、巡查,2、游客居民,3、12345,4、网格", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") //@NotNull(message = "来源ID不能为空") private Integer sourceId; diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treeinspection/TreeInspectionController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treeinspection/TreeInspectionController.java new file mode 100644 index 0000000..c22ebc0 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treeinspection/TreeInspectionController.java @@ -0,0 +1,50 @@ +package com.zteits.urbanops.module.garden.controller.admin.treeinspection; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.security.access.prepost.PreAuthorize; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import com.zteits.urbanops.framework.common.pojo.CommonResult; +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO; +import com.zteits.urbanops.module.garden.service.treeinspection.TreeInspectionService; +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*; +import com.zteits.urbanops.module.garden.convert.treeinspection.TreeInspectionConvert; + +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 行道树巡检记录与评估") +@RestController("adminTreeInspectionController") +@RequestMapping("/garden/tree-inspection") +@Validated +public class TreeInspectionController { + + @Resource + private TreeInspectionService treeInspectionService; + + @GetMapping("/get") + @Operation(summary = "获得巡检评估详情") + @Parameter(name = "id", description = "巡检记录编号", required = true, example = "12") + @PreAuthorize("@ss.hasPermission('garden:tree-inspection:query')") + public CommonResult getTreeInspection(@RequestParam("id") Long id) { + return success(treeInspectionService.getTreeInspection(id)); + } + + @GetMapping("/page") + @Operation(summary = "分页查询单株树木的历史巡检记录") + @PreAuthorize("@ss.hasPermission('garden:tree-inspection:query')") + public CommonResult> getTreeInspectionPage(@Valid TreeInspectionPageReqVO pageReqVO) { + PageResult pageResult = treeInspectionService.getTreeInspectionPage(pageReqVO); + PageResult respPageResult = new PageResult<>( + TreeInspectionConvert.INSTANCE.convertList(pageResult.getList()), + pageResult.getTotal() + ); + return success(respPageResult); + } + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java new file mode 100644 index 0000000..d783b1b --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java @@ -0,0 +1,38 @@ +package com.zteits.urbanops.module.garden.controller.app.problemtype; + +import com.zteits.urbanops.framework.common.pojo.CommonResult; +import com.zteits.urbanops.framework.common.util.object.BeanUtils; +import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypeRespVO; +import com.zteits.urbanops.module.garden.dal.dataobject.problemtype.GardenProblemTypeDO; +import com.zteits.urbanops.module.garden.service.problemtype.GardenProblemTypeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; + +@Tag(name = "APP - 问题类型") +@RestController +@RequestMapping("/app/problem-type") +@Validated +public class AppProblemTypeController { + + @Resource + private GardenProblemTypeService gardenProblemTypeService; + + @GetMapping("/list-by-parent") + @Operation(summary = "根据父级编码和层级查询子类型列表(级联下拉用)") + public CommonResult> getProblemTypeListByParent( + @RequestParam(value = "parentCode", required = false) String parentCode, + @RequestParam("level") Integer level) { + List list = gardenProblemTypeService.getProblemTypeByParentCode(parentCode, level); + return success(BeanUtils.toBean(list, ProblemTypeRespVO.class)); + } +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/AppTreeInspectionController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/AppTreeInspectionController.java new file mode 100644 index 0000000..af631d6 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/AppTreeInspectionController.java @@ -0,0 +1,53 @@ +package com.zteits.urbanops.module.garden.controller.app.treeinspection; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import com.zteits.urbanops.framework.common.pojo.CommonResult; +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO; +import com.zteits.urbanops.module.garden.service.treeinspection.TreeInspectionService; +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*; +import com.zteits.urbanops.module.garden.convert.treeinspection.TreeInspectionConvert; + +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; + +@Tag(name = "App - 行道树巡检与风险评估") +@RestController +@RequestMapping({"/garden/tree-inspection", "/business/tree-inspection"}) +@Validated +public class AppTreeInspectionController { + + @Resource + private TreeInspectionService treeInspectionService; + + @PostMapping("/create") + @Operation(summary = "提交巡检记录并评估") + public CommonResult createTreeInspection(@Valid @RequestBody TreeInspectionSaveReqVO createReqVO) { + return success(treeInspectionService.createTreeInspection(createReqVO)); + } + + @GetMapping("/get") + @Operation(summary = "获得巡检评估详情") + @Parameter(name = "id", description = "巡检记录编号", required = true, example = "12") + public CommonResult getTreeInspection(@RequestParam("id") Long id) { + return success(treeInspectionService.getTreeInspection(id)); + } + + @GetMapping("/page") + @Operation(summary = "分页查询单株树木的历史巡检记录") + public CommonResult> getTreeInspectionPage(@Valid TreeInspectionPageReqVO pageReqVO) { + PageResult pageResult = treeInspectionService.getTreeInspectionPage(pageReqVO); + PageResult respPageResult = new PageResult<>( + TreeInspectionConvert.INSTANCE.convertList(pageResult.getList()), + pageResult.getTotal() + ); + return success(respPageResult); + } + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionPageReqVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionPageReqVO.java new file mode 100644 index 0000000..30243bd --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionPageReqVO.java @@ -0,0 +1,20 @@ +package com.zteits.urbanops.module.garden.controller.app.treeinspection.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import jakarta.validation.constraints.NotNull; +import com.zteits.urbanops.framework.common.pojo.PageParam; + +@Schema(description = "小程序端 - 行道树巡检历史分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class TreeInspectionPageReqVO extends PageParam { + + @Schema(description = "一树一档案ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "关联树木ID不能为空") + private Long treeId; + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionRespVO.java new file mode 100644 index 0000000..ef97121 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionRespVO.java @@ -0,0 +1,187 @@ +package com.zteits.urbanops.module.garden.controller.app.treeinspection.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.zteits.urbanops.framework.common.util.json.databind.LocalDateTimeStringSerializer; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "小程序端 - 行道树巡检记录与评估详情 Response VO") +@Data +public class TreeInspectionRespVO { + + @Schema(description = "主键ID", example = "12") + private Long id; + + @Schema(description = "一树一档案ID", example = "1024") + private Long treeId; + + @Schema(description = "树木编号", example = "D0001-P1-0001") + private String treenumber; + + @Schema(description = "巡检时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @JsonSerialize(using = LocalDateTimeStringSerializer.class) + private LocalDateTime inspectionTime; + + @Schema(description = "巡检人ID", example = "10001") + private Long inspectorId; + + @Schema(description = "巡检人姓名", example = "张三") + private String inspectorName; + + @Schema(description = "树根部位指标组") + private RootRespDTO root; + + @Schema(description = "根颈部位指标组") + private CollarRespDTO collar; + + @Schema(description = "主干部位指标组") + private TrunkRespDTO trunk; + + @Schema(description = "树冠部位指标组") + private CrownRespDTO crown; + + @Schema(description = "权重因子评估组") + private WeightRespDTO weight; + + @Schema(description = "风险评估结果组 (折叠面板)") + private ResultRespDTO result; + + + @Schema(description = "现状及处理措施组") + private StatusRespDTO status; + + @Data + @Schema(description = "树根部位指标详情") + public static class RootRespDTO { + @Schema(description = "根部病害:0-无真菌危害或腐朽,8-存在真菌危害或腐朽", example = "0") + private Integer disease; + + @Schema(description = "根系下扎情况:0-良好,7-存在隆起或盘根", example = "0") + private Integer anchorage; + + @Schema(description = "工程切根:0-无,5-存在", example = "0") + private Integer cutting; + } + + @Data + @Schema(description = "根颈部位指标详情") + public static class CollarRespDTO { + @Schema(description = "根颈木质部受损:0-无,5-<10%, 15-10%-30%, 25-30%-50%, 70->=50%", example = "5") + private Integer woodDamage; + + @Schema(description = "根颈树皮受损:0-<10%, 2-10%-30%, 4-30%-50%, 6->=50%", example = "0") + private Integer barkDamage; + + @Schema(description = "根颈松动:0-不存在,100-存在", example = "0") + private Integer loosening; + } + + @Data + @Schema(description = "主干部位指标详情") + public static class TrunkRespDTO { + @Schema(description = "主干木质部受损:0-无,5-<10%, 12-10%-30%, 20-30%-50%, 70->=50%", example = "0") + private Integer woodDamage; + + @Schema(description = "主干倾斜:0-<10度, 3-10-20度, 8-20-30度, 70->=30度", example = "0") + private Integer tilt; + + @Schema(description = "主干树皮受损:0-<10%, 1-10%-30%, 3-30%-50%, 5->=50%", example = "0") + private Integer barkDamage; + } + + @Data + @Schema(description = "树冠部位指标详情") + public static class CrownRespDTO { + @Schema(description = "易落枝:0-未发现,2-<1/10, 3->=1/10", example = "0") + private Integer looseBranch; + + @Schema(description = "枝干结合部异常:0-无,3-龟裂卷皮,5-腐烂未成洞,70-明显空洞或蛀干痕迹", example = "0") + private Integer collarAbnormal; + + @Schema(description = "树冠透风及平衡性:0-较好不偏冠, 1-偏冠或透风差不偏冠, 2-透风差明显偏冠但冠幅适中, 5-透风差冠幅大不偏冠, 8-透风差冠幅大且明显偏冠", example = "0") + private Integer ventilationBalance; + } + + @Data + @Schema(description = "权重因子评估详情") + public static class WeightRespDTO { + @Schema(description = "树种类型:深根性树种, 浅根性树种", example = "深根性树种") + private String treeSpeciesType; + + @Schema(description = "树种类型权重常数", example = "1.0") + private Double treeSpeciesWeight; + + @Schema(description = "栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上", example = "栽植 10-30 年") + private String plantingYears; + + @Schema(description = "栽植年限权重常数", example = "1.1") + private Double plantingYearsWeight; + + @Schema(description = "是否处于风口", example = "false") + private Boolean isWindCorridor; + + @Schema(description = "风口权重常数", example = "1.0") + private Double windCorridorWeight; + + @Schema(description = "树池类型:联通树池, 独立树池, 树池硬化", example = "联通树池") + private String treePoolType; + + @Schema(description = "树池类型权重常数", example = "1.0") + private Double treePoolWeight; + + @Schema(description = "树池宽度与胸径比:7 倍及以上, 5 倍-7 倍, 3 倍-5 倍, 3 倍以下", example = "7 倍及以上") + private String treePoolWidthDbhRatio; + + @Schema(description = "树池比权重常数", example = "1.0") + private Double treePoolRatioWeight; + } + + @Data + @Schema(description = "风险评估结果详情 (折叠面板)") + public static class ResultRespDTO { + @Schema(description = "是否进行应急评估", example = "true") + private Boolean isEmergency; + + @Schema(description = "应急评估极端天气风力:7 级及以下, 8-9 级, 10 级, 10 级以上", example = "8-9 级") + private String windPower; + + @Schema(description = "风力权重常数", example = "1.5") + private Double windPowerWeight; + + @Schema(description = "树木缺陷得分", example = "9") + private Integer defectScore; + + @Schema(description = "常规安全风险评估结果") + private EvaluationResultDTO normalResult; + + @Schema(description = "应急安全风险评估结果") + private EvaluationResultDTO emergencyResult; + } + + @Data + @Schema(description = "安全风险评估得分与等级结果") + public static class EvaluationResultDTO { + @Schema(description = "风险得分", example = "87.12") + private Double score; + + @Schema(description = "风险等级", example = "重度风险") + private String level; + } + + + + @Data + @Schema(description = "现状及处理措施详情") + public static class StatusRespDTO { + @Schema(description = "现场照片(最多5张)") + private List photos; + } + + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionSaveReqVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionSaveReqVO.java new file mode 100644 index 0000000..fe973ed --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionSaveReqVO.java @@ -0,0 +1,194 @@ +package com.zteits.urbanops.module.garden.controller.app.treeinspection.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; +import org.springframework.format.annotation.DateTimeFormat; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "小程序端 - 行道树巡检记录与评估新增/修改 Request VO") +@Data +public class TreeInspectionSaveReqVO { + + @Schema(description = "一树一档案ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "关联树木ID不能为空") + private Long treeId; + + @Schema(description = "巡检时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "巡检时间不能为空") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime inspectionTime; + + @Schema(description = "树根部位指标组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "树根部位指标不能为空") + @Valid + private RootDTO root; + + @Schema(description = "根颈部位指标组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "根颈部位指标不能为空") + @Valid + private CollarDTO collar; + + @Schema(description = "主干部位指标组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "主干部位指标不能为空") + @Valid + private TrunkDTO trunk; + + @Schema(description = "树冠部位指标组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "树冠部位指标不能为空") + @Valid + private CrownDTO crown; + + @Schema(description = "权重因子评估组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "权重因子评估组不能为空") + @Valid + private WeightDTO weight; + + @Schema(description = "风险评估结果组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "风险评估结果组不能为空") + @Valid + private ResultDTO result; + + + @Schema(description = "现状及处理措施组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "现状及处理措施组不能为空") + @Valid + private StatusDTO status; + + @Data + @Schema(description = "树根部位指标 DTO") + public static class RootDTO { + @Schema(description = "根部病害:0-无真菌危害或腐朽,8-存在真菌危害或腐朽", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "根部病害指标不能为空") + private Integer disease; + + @Schema(description = "根系下扎情况:0-良好,7-存在隆起或盘根", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "根系下扎情况指标不能为空") + private Integer anchorage; + + @Schema(description = "工程切根:0-无,5-存在", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "工程切根指标不能为空") + private Integer cutting; + } + + @Data + @Schema(description = "根颈部位指标 DTO") + public static class CollarDTO { + @Schema(description = "根颈木质部受损:0-无,5-<10%, 15-10%-30%, 25-30%-50%, 70->=50%", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + @NotNull(message = "根颈木质部受损指标不能为空") + private Integer woodDamage; + + @Schema(description = "根颈树皮受损:0-<10%, 2-10%-30%, 4-30%-50%, 6->=50%", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "根颈树皮受损指标不能为空") + private Integer barkDamage; + + @Schema(description = "根颈松动:0-不存在,100-存在", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "根颈松动指标不能为空") + private Integer loosening; + } + + @Data + @Schema(description = "主干部位指标 DTO") + public static class TrunkDTO { + @Schema(description = "主干木质部受损:0-无,5-<10%, 12-10%-30%, 20-30%-50%, 70->=50%", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "主干木质部受损指标不能为空") + private Integer woodDamage; + + @Schema(description = "主干倾斜:0-<10度, 3-10-20度, 8-20-30度, 70->=30度", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "主干倾斜指标不能为空") + private Integer tilt; + + @Schema(description = "主干树皮受损:0-<10%, 1-10%-30%, 3-30%-50%, 5->=50%", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "主干树皮受损指标不能为空") + private Integer barkDamage; + } + + @Data + @Schema(description = "树冠部位指标 DTO") + public static class CrownDTO { + @Schema(description = "易落枝:0-未发现,2-<1/10, 3->=1/10", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "易落枝指标不能为空") + private Integer looseBranch; + + @Schema(description = "枝干结合部异常:0-无,3-龟裂卷皮,5-腐烂未成洞,70-明显空洞或蛀干痕迹", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "枝干结合部异常指标不能为空") + private Integer collarAbnormal; + + @Schema(description = "树冠透风及平衡性:0-较好不偏冠, 1-偏冠或透风差不偏冠, 2-透风差明显偏冠但冠幅适中, 5-透风差冠幅大不偏冠, 8-透风差冠幅大且明显偏冠", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "树冠透风及平衡性指标不能为空") + private Integer ventilationBalance; + } + + @Data + @Schema(description = "权重因子评估 DTO") + public static class WeightDTO { + @Schema(description = "树种类型:深根性树种, 浅根性树种", requiredMode = Schema.RequiredMode.REQUIRED, example = "深根性树种") + @NotBlank(message = "树种类型不能为空") + private String treeSpeciesType; + + @Schema(description = "栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上", requiredMode = Schema.RequiredMode.REQUIRED, example = "栽植 10-30 年") + @NotBlank(message = "栽植年限不能为空") + private String plantingYears; + + @Schema(description = "是否处于风口", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否处于风口标识不能为空") + private Boolean isWindCorridor; + + @Schema(description = "树池类型:联通树池, 独立树池, 树池硬化", requiredMode = Schema.RequiredMode.REQUIRED, example = "联通树池") + @NotBlank(message = "树池类型不能为空") + private String treePoolType; + + @Schema(description = "树池宽度与胸径比:7 倍及以上, 5 倍-7 倍, 3 倍-5 倍, 3 倍以下", requiredMode = Schema.RequiredMode.REQUIRED, example = "7 倍及以上") + @NotBlank(message = "树池宽度与胸径比不能为空") + private String treePoolWidthDbhRatio; + } + + @Data + @Schema(description = "风险评估结果 DTO (折叠面板)") + public static class ResultDTO { + @Schema(description = "是否进行应急评估", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否应急评估标识不能为空") + private Boolean isEmergency; + + @Schema(description = "应急评估极端天气风力:7 级及以下, 8-9 级, 10 级, 10 级以上", example = "8-9 级") + private String windPower; + + @Schema(description = "树木缺陷得分", example = "9") + private Integer defectScore; + + @Schema(description = "常规安全风险评估结果") + private EvaluationResultDTO normalResult; + + @Schema(description = "应急安全风险评估结果") + private EvaluationResultDTO emergencyResult; + } + + @Data + @Schema(description = "安全风险评估得分与等级结果") + public static class EvaluationResultDTO { + @Schema(description = "风险得分", example = "87.12") + private Double score; + + @Schema(description = "风险等级", example = "重度风险") + private String level; + } + + + + @Data + @Schema(description = "现状及处理措施 DTO") + public static class StatusDTO { + @Schema(description = "现场照片(最多5张)") + @Size(max = 5, message = "最多只能上传5张现场照片") + private List photos; + } + + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/convert/treeinspection/TreeInspectionConvert.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/convert/treeinspection/TreeInspectionConvert.java new file mode 100644 index 0000000..59d8f1b --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/convert/treeinspection/TreeInspectionConvert.java @@ -0,0 +1,84 @@ +package com.zteits.urbanops.module.garden.convert.treeinspection; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO; +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.TreeInspectionSaveReqVO; +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.TreeInspectionRespVO; +import java.util.List; + +@Mapper +public interface TreeInspectionConvert { + + TreeInspectionConvert INSTANCE = Mappers.getMapper(TreeInspectionConvert.class); + + @Mappings({ + @Mapping(source = "root.disease", target = "rootDisease"), + @Mapping(source = "root.anchorage", target = "rootAnchorage"), + @Mapping(source = "root.cutting", target = "rootCutting"), + @Mapping(source = "collar.woodDamage", target = "collarWoodDamage"), + @Mapping(source = "collar.barkDamage", target = "collarBarkDamage"), + @Mapping(source = "collar.loosening", target = "collarLoosening"), + @Mapping(source = "trunk.woodDamage", target = "trunkWoodDamage"), + @Mapping(source = "trunk.tilt", target = "trunkTilt"), + @Mapping(source = "trunk.barkDamage", target = "trunkBarkDamage"), + @Mapping(source = "crown.looseBranch", target = "crownLooseBranch"), + @Mapping(source = "crown.collarAbnormal", target = "crownCollarAbnormal"), + @Mapping(source = "crown.ventilationBalance", target = "crownVentilationBalance"), + @Mapping(source = "weight.treeSpeciesType", target = "treeSpeciesType"), + @Mapping(source = "weight.plantingYears", target = "plantingYears"), + @Mapping(source = "weight.isWindCorridor", target = "isWindCorridor"), + @Mapping(source = "weight.treePoolType", target = "treePoolType"), + @Mapping(source = "weight.treePoolWidthDbhRatio", target = "treePoolWidthDbhRatio"), + @Mapping(source = "result.isEmergency", target = "isEmergency"), + @Mapping(source = "result.windPower", target = "windPower"), + @Mapping(source = "status.photos", target = "photos"), + @Mapping(source = "result.defectScore", target = "defectScore"), + @Mapping(source = "result.normalResult.score", target = "normalScore"), + @Mapping(source = "result.normalResult.level", target = "normalLevel"), + @Mapping(source = "result.emergencyResult.score", target = "emergencyScore"), + @Mapping(source = "result.emergencyResult.level", target = "emergencyLevel") + }) + TreeInspectionDO convert(TreeInspectionSaveReqVO bean); + + @Mappings({ + @Mapping(source = "rootDisease", target = "root.disease"), + @Mapping(source = "rootAnchorage", target = "root.anchorage"), + @Mapping(source = "rootCutting", target = "root.cutting"), + @Mapping(source = "collarWoodDamage", target = "collar.woodDamage"), + @Mapping(source = "collarBarkDamage", target = "collar.barkDamage"), + @Mapping(source = "collarLoosening", target = "collar.loosening"), + @Mapping(source = "trunkWoodDamage", target = "trunk.woodDamage"), + @Mapping(source = "trunkTilt", target = "trunk.tilt"), + @Mapping(source = "trunkBarkDamage", target = "trunk.barkDamage"), + @Mapping(source = "crownLooseBranch", target = "crown.looseBranch"), + @Mapping(source = "crownCollarAbnormal", target = "crown.collarAbnormal"), + @Mapping(source = "crownVentilationBalance", target = "crown.ventilationBalance"), + @Mapping(source = "treeSpeciesType", target = "weight.treeSpeciesType"), + @Mapping(source = "treeSpeciesWeight", target = "weight.treeSpeciesWeight"), + @Mapping(source = "plantingYears", target = "weight.plantingYears"), + @Mapping(source = "plantingYearsWeight", target = "weight.plantingYearsWeight"), + @Mapping(source = "isWindCorridor", target = "weight.isWindCorridor"), + @Mapping(source = "windCorridorWeight", target = "weight.windCorridorWeight"), + @Mapping(source = "treePoolType", target = "weight.treePoolType"), + @Mapping(source = "treePoolWeight", target = "weight.treePoolWeight"), + @Mapping(source = "treePoolWidthDbhRatio", target = "weight.treePoolWidthDbhRatio"), + @Mapping(source = "treePoolRatioWeight", target = "weight.treePoolRatioWeight"), + @Mapping(source = "isEmergency", target = "result.isEmergency"), + @Mapping(source = "windPower", target = "result.windPower"), + @Mapping(source = "windPowerWeight", target = "result.windPowerWeight"), + @Mapping(source = "defectScore", target = "result.defectScore"), + @Mapping(source = "normalScore", target = "result.normalResult.score"), + @Mapping(source = "normalLevel", target = "result.normalResult.level"), + @Mapping(source = "emergencyScore", target = "result.emergencyResult.score"), + @Mapping(source = "emergencyLevel", target = "result.emergencyResult.level"), + @Mapping(source = "photos", target = "status.photos") + }) + TreeInspectionRespVO convert(TreeInspectionDO bean); + + + List convertList(List list); + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treeinspection/TreeInspectionDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treeinspection/TreeInspectionDO.java new file mode 100644 index 0000000..b24ac45 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treeinspection/TreeInspectionDO.java @@ -0,0 +1,248 @@ +package com.zteits.urbanops.module.garden.dal.dataobject.treeinspection; + +import lombok.*; +import java.time.LocalDateTime; +import java.util.List; +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.zteits.urbanops.framework.tenant.core.db.TenantBaseDO; + +/** + * 行道树巡检与安全风险评估记录 DO + * + * @author Antigravity + */ +@TableName(value = "garden_tree_inspection", autoResultMap = true) +@KeySequence("garden_tree_inspection_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TreeInspectionDO extends TenantBaseDO { + + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 一树一档案ID + */ + private Long treeId; + + /** + * 树木编号 + */ + private String treenumber; + + /** + * 巡检时间 + */ + private LocalDateTime inspectionTime; + + /** + * 巡检人ID + */ + private Long inspectorId; + + /** + * 巡检人姓名 + */ + private String inspectorName; + + /** + * 部门ID + */ + private Long deptId; + + // ========== 缺陷评估指标得分 ========== + + /** + * 根部病害:0-无真菌危害或腐朽,8-存在真菌危害或腐朽 + */ + private Integer rootDisease; + + /** + * 根系下扎情况:0-良好,7-存在隆起或盘根 + */ + private Integer rootAnchorage; + + /** + * 工程切根:0-无,5-存在 + */ + private Integer rootCutting; + + /** + * 根颈木质部受损:0-无,5-<10%, 15-10%-30%, 25-30%-50%, 70->=50% + */ + private Integer collarWoodDamage; + + /** + * 根颈树皮受损:0-<10%, 2-10%-30%, 4-30%-50%, 6->=50% + */ + private Integer collarBarkDamage; + + /** + * 根颈松动:0-不存在,100-存在 + */ + private Integer collarLoosening; + + /** + * 主干木质部受损:0-无,5-<10%, 12-10%-30%, 20-30%-50%, 70->=50% + */ + private Integer trunkWoodDamage; + + /** + * 主干倾斜:0-<10度, 3-10-20度, 8-20-30度, 70->=30度 + */ + private Integer trunkTilt; + + /** + * 主干树皮受损:0-<10%, 1-10%-30%, 3-30%-50%, 5->=50% + */ + private Integer trunkBarkDamage; + + /** + * 易落枝:0-未发现,2-<1/10, 3->=1/10 + */ + private Integer crownLooseBranch; + + /** + * 枝干结合部异常:0-无,3-龟裂卷皮,5-腐烂未成洞,70-明显空洞或蛀干痕迹 + */ + private Integer crownCollarAbnormal; + + /** + * 树冠透风及平衡性:0-较好不偏冠, 1-偏冠或透风差不偏冠, 2-透风差明显偏冠但冠幅适中, 5-透风差冠幅大不偏冠, 8-透风差冠幅大且明显偏冠 + */ + private Integer crownVentilationBalance; + + // ========== 权重因子指标值和赋权值 ========== + + /** + * 树种类型:深根性树种, 浅根性树种 + */ + private String treeSpeciesType; + + /** + * 树种类型权重:深根性-1.0, 浅根性-1.1 + */ + private Double treeSpeciesWeight; + + /** + * 栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上 + */ + private String plantingYears; + + /** + * 栽植年限权重:10年内-1.0, 10-30年-1.1, 30年以上-1.2 + */ + private Double plantingYearsWeight; + + /** + * 是否处于风口:0-否, 1-是 + */ + private Boolean isWindCorridor; + + /** + * 风口权重:否-1.0, 是-2.0 + */ + private Double windCorridorWeight; + + /** + * 树池类型:联通树池, 独立树池, 树池硬化 + */ + private String treePoolType; + + /** + * 树池类型权重:联通树池-1.0, 独立树池-1.2, 树池硬化-1.5 + */ + private Double treePoolWeight; + + /** + * 树池宽度与胸径比:7 倍及以上, 5 倍-7 倍, 3 倍-5 倍, 3 倍以下 + */ + private String treePoolWidthDbhRatio; + + /** + * 树池比权重:7倍及以上-1.0, 5-7倍-1.1, 3-5倍-1.2, 3倍以下-1.3 + */ + private Double treePoolRatioWeight; + + // ========== 应急评估及加权 ========== + + /** + * 是否进行应急评估:0-否, 1-是 + */ + private Boolean isEmergency; + + /** + * 应急评估极端天气风力:7 级及以下, 8-9 级, 10 级, 10 级以上 + */ + private String windPower; + + /** + * 风力权重:7级及以下-1.0, 8-9级-1.5, 10级-2.0, 10级以上-3.0 + */ + private Double windPowerWeight; + + // ========== 评估计算结果 ========== + + /** + * 树木缺陷得分 + */ + private Integer defectScore; + + /** + * 常规情况下安全风险得分 + */ + private Double normalScore; + + /** + * 常规情况下风险等级 + */ + private String normalLevel; + + /** + * 应急情况下安全风险得分 + */ + private Double emergencyScore; + + /** + * 应急情况下风险等级 + */ + private String emergencyLevel; + + // ========== 现状及处理措施 ========== + + /** + * 建议处置措施 + */ + private String treatmentSuggestion; + + /** + * 是否已处置:0-未处置, 1-已处置 + */ + private Boolean isTreated; + + /** + * 现场照片链接(JSON 数组) + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List photos; + + /** + * 评估单位(公司) + */ + private String estimatorCompany; + + /** + * 评估人 + */ + private String estimator; + + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treeinspection/TreeInspectionMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treeinspection/TreeInspectionMapper.java new file mode 100644 index 0000000..576664b --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treeinspection/TreeInspectionMapper.java @@ -0,0 +1,26 @@ +package com.zteits.urbanops.module.garden.dal.mysql.treeinspection; + +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX; +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO; +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.TreeInspectionPageReqVO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 行道树巡检与安全风险评估记录 Mapper + * + * @author Antigravity + */ +@Mapper +public interface TreeInspectionMapper extends BaseMapperX { + + default PageResult selectPage(TreeInspectionPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(TreeInspectionDO::getTreeId, reqVO.getTreeId()) + .orderByDesc(TreeInspectionDO::getInspectionTime) + .orderByDesc(TreeInspectionDO::getId) + ); + } + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/ErrorCodeConstants.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/ErrorCodeConstants.java index 5cb7aa7..e03d6ba 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/ErrorCodeConstants.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/ErrorCodeConstants.java @@ -151,4 +151,7 @@ public interface ErrorCodeConstants { ErrorCode PROBLEM_TYPE_NOT_EXISTS = new ErrorCode(1100009000, "问题类型不存在"); ErrorCode PROBLEM_TYPE_CODE_EXISTS = new ErrorCode(1100009001, "问题类型编码已存在"); + // ========== 一树一档案巡检与风险评估 1-100-008-000 ========== + ErrorCode TREE_INSPECTION_NOT_EXISTS = new ErrorCode(1100008001, "巡检评估记录不存在"); + } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionService.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionService.java new file mode 100644 index 0000000..a786c9b --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionService.java @@ -0,0 +1,39 @@ +package com.zteits.urbanops.module.garden.service.treeinspection; + +import jakarta.validation.Valid; +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO; +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*; + +/** + * 行道树巡检与安全风险评估业务 Service 接口 + * + * @author Antigravity + */ +public interface TreeInspectionService { + + /** + * 创建行道树巡检与评估记录 + * + * @param createReqVO 创建信息 + * @return 记录ID + */ + Long createTreeInspection(@Valid TreeInspectionSaveReqVO createReqVO); + + /** + * 获得行道树巡检与评估详情 + * + * @param id 编号 + * @return 嵌套结构的详情 VO + */ + TreeInspectionRespVO getTreeInspection(Long id); + + /** + * 分页查询单株树木的历史巡检记录 + * + * @param pageReqVO 分页查询请求 + * @return 巡检历史记录分页 + */ + PageResult getTreeInspectionPage(TreeInspectionPageReqVO pageReqVO); + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImpl.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImpl.java new file mode 100644 index 0000000..178b73b --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImpl.java @@ -0,0 +1,212 @@ +package com.zteits.urbanops.module.garden.service.treeinspection; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; +import jakarta.annotation.Resource; + +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil; +import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO; +import com.zteits.urbanops.module.garden.dal.dataobject.tree.TreeDO; +import com.zteits.urbanops.module.garden.dal.mysql.treeinspection.TreeInspectionMapper; +import com.zteits.urbanops.module.garden.dal.mysql.tree.TreeMapper; +import com.zteits.urbanops.module.garden.enums.ErrorCodeConstants; +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*; +import com.zteits.urbanops.module.garden.convert.treeinspection.TreeInspectionConvert; + +/** + * 行道树巡检与安全风险评估业务 Service 实现类 + * + * @author Antigravity + */ +@Service +@Validated +@Slf4j +public class TreeInspectionServiceImpl implements TreeInspectionService { + + @Resource + private TreeInspectionMapper treeInspectionMapper; + + @Resource + private TreeMapper treeMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createTreeInspection(TreeInspectionSaveReqVO createReqVO) { + // 1. 校验树木档案是否存在 + TreeDO tree = treeMapper.selectById(createReqVO.getTreeId()); + if (tree == null) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.TREE_NOT_EXISTS); + } + + // 2. 转换数据并组装 DO 实体 + TreeInspectionDO entity = TreeInspectionConvert.INSTANCE.convert(createReqVO); + entity.setTreenumber(tree.getTreenumber()); + + // 3. 填充用户信息与部门信息 + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + String nickname = SecurityFrameworkUtils.getLoginUserNickname(); + Long deptId = SecurityFrameworkUtils.getLoginUserDeptId(); + + entity.setInspectorId(loginUserId); + entity.setInspectorName(nickname != null ? nickname : "巡检员"); + if (entity.getDeptId() == null) { + entity.setDeptId(deptId != null ? deptId : tree.getDeptId()); + } + + // 4. 补齐各因子的权重指标(即使由前端打分,后端依然补齐数据库里各个细节因子权重以支持底层的统计与分析) + double treeSpeciesWeight = "浅根性树种".equals(entity.getTreeSpeciesType()) ? 1.1 : 1.0; + entity.setTreeSpeciesWeight(treeSpeciesWeight); + + double plantingYearsWeight = getPlantingYearsWeight(entity.getPlantingYears()); + entity.setPlantingYearsWeight(plantingYearsWeight); + + double windCorridorWeight = Boolean.TRUE.equals(entity.getIsWindCorridor()) ? 2.0 : 1.0; + entity.setWindCorridorWeight(windCorridorWeight); + + double treePoolWeight = getTreePoolWeight(entity.getTreePoolType()); + entity.setTreePoolWeight(treePoolWeight); + + double treePoolRatioWeight = getTreePoolRatioWeight(entity.getTreePoolWidthDbhRatio()); + entity.setTreePoolRatioWeight(treePoolRatioWeight); + + if (Boolean.TRUE.equals(entity.getIsEmergency())) { + double windPowerWeight = getWindPowerWeight(entity.getWindPower()); + entity.setWindPowerWeight(windPowerWeight); + } else { + entity.setWindPowerWeight(1.0); + } + + // 5. 风险打分计算(前端计算结果存储优先,后端自动补齐防空兜底) + if (entity.getDefectScore() == null + || entity.getNormalScore() == null + || entity.getNormalLevel() == null) { + + // 后端兜底自动缺陷打分 + int calculatedSum = entity.getRootDisease() + + entity.getRootAnchorage() + + entity.getRootCutting() + + entity.getCollarWoodDamage() + + entity.getCollarBarkDamage() + + entity.getCollarLoosening() + + entity.getTrunkWoodDamage() + + entity.getTrunkTilt() + + entity.getTrunkBarkDamage() + + entity.getCrownLooseBranch() + + entity.getCrownCollarAbnormal() + + entity.getCrownVentilationBalance(); + + int defectScore = calculatedSum; + // 一票否决规则 + if (entity.getCollarLoosening() == 100) { + defectScore = 100; + } else if (entity.getCollarWoodDamage() == 70 + || entity.getTrunkWoodDamage() == 70 + || entity.getTrunkTilt() == 70 + || entity.getCrownCollarAbnormal() == 70) { + defectScore = Math.max(calculatedSum, 70); + } + entity.setDefectScore(defectScore); + + // 计算常规安全得分 + double normalScore = defectScore * treeSpeciesWeight * plantingYearsWeight * windCorridorWeight * treePoolWeight * treePoolRatioWeight; + normalScore = Math.round(normalScore * 100.0) / 100.0; + entity.setNormalScore(normalScore); + entity.setNormalLevel(getRiskLevelName(normalScore)); + } + + // 6. 应急打分补齐(若前端已计算则信任前端结果,否则后端自动补齐应急兜底) + if (Boolean.TRUE.equals(entity.getIsEmergency())) { + if (entity.getEmergencyScore() == null || entity.getEmergencyLevel() == null) { + double windPowerWeight = entity.getWindPowerWeight(); + double emergencyScore = entity.getNormalScore() * windPowerWeight; + emergencyScore = Math.round(emergencyScore * 100.0) / 100.0; + entity.setEmergencyScore(emergencyScore); + entity.setEmergencyLevel(getRiskLevelName(emergencyScore)); + } + } else { + entity.setEmergencyScore(null); + entity.setEmergencyLevel(null); + } + + // 7. 保存到数据库 + treeInspectionMapper.insert(entity); + return entity.getId(); + } + + @Override + public TreeInspectionRespVO getTreeInspection(Long id) { + TreeInspectionDO entity = treeInspectionMapper.selectById(id); + if (entity == null) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.TREE_INSPECTION_NOT_EXISTS); + } + return TreeInspectionConvert.INSTANCE.convert(entity); + } + + @Override + public PageResult getTreeInspectionPage(TreeInspectionPageReqVO pageReqVO) { + return treeInspectionMapper.selectPage(pageReqVO); + } + + // ========== 权重因子规则辅助类 ========== + + private double getPlantingYearsWeight(String years) { + if (years == null) return 1.0; + if (years.contains("30年")) return 1.2; + if (years.contains("10-30")) return 1.1; + if (years.contains("10年")) return 1.0; + return 1.0; + } + + private double getTreePoolWeight(String type) { + if (type == null) return 1.0; + if (type.contains("硬化")) return 1.5; + if (type.contains("独立")) return 1.2; + if (type.contains("联通")) return 1.0; + return 1.0; + } + + private double getTreePoolRatioWeight(String ratio) { + if (ratio == null) return 1.0; + if (ratio.contains("3倍以下") || ratio.contains("3 倍以下") || ratio.contains("小于3")) { + return 1.3; + } + if (ratio.contains("3-5") || ratio.contains("3至5") || ratio.contains("3倍") || ratio.contains("3 倍")) { + return 1.2; + } + if (ratio.contains("5-7") || ratio.contains("5至7") || ratio.contains("5倍") || ratio.contains("5 倍")) { + return 1.1; + } + if (ratio.contains("7倍") || ratio.contains("7 倍") || ratio.contains("大于7")) { + return 1.0; + } + return 1.0; + } + + private double getWindPowerWeight(String power) { + if (power == null) return 1.0; + if (power.contains("10级以上") || power.contains("10 级以上")) return 3.0; + if (power.contains("10级") || power.contains("10 级")) return 2.0; + if (power.contains("8-9")) return 1.5; + if (power.contains("7级") || power.contains("7 级")) return 1.0; + return 1.0; + } + + private String getRiskLevelName(double score) { + if (score < 10.0) { + return "I级 (基本无风险)"; + } else if (score < 30.0) { + return "II级 (轻度风险)"; + } else if (score < 70.0) { + return "III级 (中度风险)"; + } else if (score < 100.0) { + return "IV级 (重度风险)"; + } else { + return "V级 (极度风险)"; + } + } + +} diff --git a/urbanops-module-garden/src/test/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImplTest.java b/urbanops-module-garden/src/test/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImplTest.java new file mode 100644 index 0000000..759ad9f --- /dev/null +++ b/urbanops-module-garden/src/test/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImplTest.java @@ -0,0 +1,246 @@ +package com.zteits.urbanops.module.garden.service.treeinspection; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import java.time.LocalDateTime; +import java.util.Collections; + +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.framework.test.core.ut.BaseMockitoUnitTest; +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO; +import com.zteits.urbanops.module.garden.dal.dataobject.tree.TreeDO; +import com.zteits.urbanops.module.garden.dal.mysql.treeinspection.TreeInspectionMapper; +import com.zteits.urbanops.module.garden.dal.mysql.tree.TreeMapper; +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*; +import com.zteits.urbanops.module.garden.enums.ErrorCodeConstants; + +import static com.zteits.urbanops.framework.test.core.util.AssertUtils.assertServiceException; +import static com.zteits.urbanops.framework.test.core.util.RandomUtils.randomLongId; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class TreeInspectionServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private TreeInspectionServiceImpl treeInspectionService; + + @Mock + private TreeInspectionMapper treeInspectionMapper; + + @Mock + private TreeMapper treeMapper; + + private TreeInspectionSaveReqVO createReqVOBase() { + TreeInspectionSaveReqVO req = new TreeInspectionSaveReqVO(); + req.setTreeId(1024L); + req.setInspectionTime(LocalDateTime.now()); + + // 树根 + TreeInspectionSaveReqVO.RootDTO root = new TreeInspectionSaveReqVO.RootDTO(); + root.setDisease(0); + root.setAnchorage(0); + root.setCutting(0); + req.setRoot(root); + + // 根颈 + TreeInspectionSaveReqVO.CollarDTO collar = new TreeInspectionSaveReqVO.CollarDTO(); + collar.setWoodDamage(5); // 扣 5 分 + collar.setBarkDamage(0); + collar.setLoosening(0); + req.setCollar(collar); + + // 主干 + TreeInspectionSaveReqVO.TrunkDTO trunk = new TreeInspectionSaveReqVO.TrunkDTO(); + trunk.setWoodDamage(0); + trunk.setTilt(3); // 扣 3 分 + trunk.setBarkDamage(0); + req.setTrunk(trunk); + + // 树冠 + TreeInspectionSaveReqVO.CrownDTO crown = new TreeInspectionSaveReqVO.CrownDTO(); + crown.setLooseBranch(0); + crown.setCollarAbnormal(0); + crown.setVentilationBalance(1); // 扣 1 分 + req.setCrown(crown); + + // 权重 + TreeInspectionSaveReqVO.WeightDTO weight = new TreeInspectionSaveReqVO.WeightDTO(); + weight.setTreeSpeciesType("深根性树种"); // 1.0 + weight.setPlantingYears("栽植 10-30 年"); // 1.1 + weight.setIsWindCorridor(false); // 1.0 + weight.setTreePoolType("联通树池"); // 1.0 + weight.setTreePoolWidthDbhRatio("5 倍(含)-7 倍(不含)"); // 1.1 + req.setWeight(weight); + + // 风险评估结果 (展开应急评估) + TreeInspectionSaveReqVO.ResultDTO result = new TreeInspectionSaveReqVO.ResultDTO(); + result.setIsEmergency(true); + result.setWindPower("8-9 级"); // 1.5 + req.setResult(result); + + // 现状及建议 + TreeInspectionSaveReqVO.StatusDTO status = new TreeInspectionSaveReqVO.StatusDTO(); + status.setPhotos(Collections.singletonList("http://example.com/tree.jpg")); + req.setStatus(status); + + return req; + } + + @Test + public void testCreateTreeInspection_success_normal() { + // 准备参数 + TreeInspectionSaveReqVO req = createReqVOBase(); + // 模拟一树一档案数据 + TreeDO tree = new TreeDO(); + tree.setId(1024L); + tree.setTreenumber("D0001-P1-0001"); + tree.setDeptId(200L); + when(treeMapper.selectById(eq(1024L))).thenReturn(tree); + + // 调用 + Long resultId = treeInspectionService.createTreeInspection(req); + + // 验证数据库插入 + verify(treeInspectionMapper, times(1)).insert(argThat((TreeInspectionDO entity) -> { + assertNotNull(entity); + assertEquals("D0001-P1-0001", entity.getTreenumber()); + assertEquals(1024L, entity.getTreeId()); + // 基础累加得分: collar.woodDamage (5) + trunk.tilt (3) + crown.ventilationBalance (1) = 9 分 + assertEquals(9, entity.getDefectScore()); + // 权重乘积: 9 * 1.0 (深根) * 1.1 (10-30年) * 1.0 (非风口) * 1.0 (联通树池) * 1.1 (5-7倍比) = 10.89 分 + assertEquals(10.89, entity.getNormalScore()); + assertEquals("II级 (轻度风险)", entity.getNormalLevel()); + // 应急乘积 (isEmergency = true, windPower = 8-9级 即 1.5): 10.89 * 1.5 = 16.34 分 + assertEquals(16.34, entity.getEmergencyScore()); + assertEquals("II级 (轻度风险)", entity.getEmergencyLevel()); + assertEquals(1, entity.getPhotos().size()); + assertEquals("http://example.com/tree.jpg", entity.getPhotos().get(0)); + return true; + })); + } + + @Test + public void testCreateTreeInspection_success_oneVoteVetoLoosening() { + // 根颈松动一票否决 -> 直接归为 V级极度风险 + TreeInspectionSaveReqVO req = createReqVOBase(); + req.getCollar().setLoosening(100); // 存在根颈松动 + + TreeDO tree = new TreeDO(); + tree.setId(1024L); + tree.setTreenumber("D0001-P1-0001"); + when(treeMapper.selectById(eq(1024L))).thenReturn(tree); + + treeInspectionService.createTreeInspection(req); + + verify(treeInspectionMapper, times(1)).insert(argThat((TreeInspectionDO entity) -> { + assertEquals(100, entity.getDefectScore()); + // 权重乘积: 100 * 1.0 * 1.1 * 1.0 * 1.0 * 1.1 = 121.0 + assertEquals(121.0, entity.getNormalScore()); + assertEquals("V级 (极度风险)", entity.getNormalLevel()); + return true; + })); + } + + @Test + public void testCreateTreeInspection_success_oneVoteVetoTilt() { + // 主干倾斜>=30度一票否决 -> 缺陷分基准为 70,归为 IV级重度风险及以上 + TreeInspectionSaveReqVO req = createReqVOBase(); + req.getTrunk().setTilt(70); // 主干倾斜度>=30度 + + TreeDO tree = new TreeDO(); + tree.setId(1024L); + tree.setTreenumber("D0001-P1-0001"); + when(treeMapper.selectById(eq(1024L))).thenReturn(tree); + + treeInspectionService.createTreeInspection(req); + + verify(treeInspectionMapper, times(1)).insert(argThat((TreeInspectionDO entity) -> { + // 70 (一票否决) + 5 (collar) + 1 (crown) = 76 分 + assertEquals(76, entity.getDefectScore()); + // 权重乘积: 76 * 1.0 * 1.1 * 1.0 * 1.0 * 1.1 = 91.96 + assertEquals(91.96, entity.getNormalScore()); + assertEquals("IV级 (重度风险)", entity.getNormalLevel()); + return true; + })); + } + + @Test + public void testCreateTreeInspection_treeNotExists() { + TreeInspectionSaveReqVO req = createReqVOBase(); + when(treeMapper.selectById(eq(1024L))).thenReturn(null); + + assertServiceException(() -> treeInspectionService.createTreeInspection(req), + ErrorCodeConstants.TREE_NOT_EXISTS); + } + + @Test + public void testGetTreeInspection_success() { + Long id = randomLongId(); + LocalDateTime now = LocalDateTime.now(); + TreeInspectionDO entity = new TreeInspectionDO(); + entity.setId(id); + entity.setTreeId(1024L); + entity.setTreenumber("D0001-P1-0001"); + entity.setInspectionTime(now); + entity.setRootDisease(0); + entity.setRootAnchorage(0); + entity.setRootCutting(0); + entity.setCollarWoodDamage(5); + entity.setCollarBarkDamage(0); + entity.setCollarLoosening(0); + entity.setTrunkWoodDamage(0); + entity.setTrunkTilt(3); + entity.setTrunkBarkDamage(0); + entity.setCrownLooseBranch(0); + entity.setCrownCollarAbnormal(0); + entity.setCrownVentilationBalance(1); + entity.setTreeSpeciesType("深根性树种"); + entity.setTreeSpeciesWeight(1.0); + entity.setPlantingYears("栽植 10-30 年"); + entity.setPlantingYearsWeight(1.1); + entity.setIsWindCorridor(false); + entity.setWindCorridorWeight(1.0); + entity.setTreePoolType("联通树池"); + entity.setTreePoolWeight(1.0); + entity.setTreePoolWidthDbhRatio("5 倍(含)-7 倍(不含)"); + entity.setTreePoolRatioWeight(1.1); + entity.setIsEmergency(true); + entity.setWindPower("8-9 级"); + entity.setWindPowerWeight(1.5); + entity.setDefectScore(9); + entity.setNormalScore(10.89); + entity.setNormalLevel("II级 (轻度风险)"); + entity.setEmergencyScore(16.34); + entity.setEmergencyLevel("II级 (轻度风险)"); + entity.setPhotos(Collections.singletonList("http://example.com/tree.jpg")); + + when(treeInspectionMapper.selectById(eq(id))).thenReturn(entity); + + TreeInspectionRespVO resp = treeInspectionService.getTreeInspection(id); + + assertNotNull(resp); + assertEquals(id, resp.getId()); + assertEquals("D0001-P1-0001", resp.getTreenumber()); + assertEquals(now, resp.getInspectionTime()); + assertEquals(5, resp.getCollar().getWoodDamage()); + assertEquals(3, resp.getTrunk().getTilt()); + assertEquals(1, resp.getCrown().getVentilationBalance()); + assertEquals(1.1, resp.getWeight().getPlantingYearsWeight()); + assertEquals(1.5, resp.getResult().getWindPowerWeight()); + assertEquals(10.89, resp.getResult().getNormalResult().getScore()); + assertEquals("II级 (轻度风险)", resp.getResult().getNormalResult().getLevel()); + assertEquals(1, resp.getStatus().getPhotos().size()); + } + + @Test + public void testGetTreeInspection_notExists() { + Long id = randomLongId(); + when(treeInspectionMapper.selectById(eq(id))).thenReturn(null); + + assertServiceException(() -> treeInspectionService.getTreeInspection(id), + ErrorCodeConstants.TREE_INSPECTION_NOT_EXISTS); + } + +} diff --git a/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/garden/AppGardenWorkOrderInspectorController.java b/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/garden/AppGardenWorkOrderInspectorController.java index 6d2dcdd..78c3c18 100644 --- a/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/garden/AppGardenWorkOrderInspectorController.java +++ b/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/garden/AppGardenWorkOrderInspectorController.java @@ -6,7 +6,6 @@ import com.zteits.urbanops.framework.idempotent.core.annotation.Idempotent; import com.zteits.urbanops.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver; import com.zteits.urbanops.module.garden.api.road.RoadApi; import com.zteits.urbanops.module.workorder.annotation.AutoUpdateWorkerOrderInspectorBuzStatus; -import com.zteits.urbanops.module.workorder.annotation.AutoUpdateWorkerOrderUniversalBuzStatus; import com.zteits.urbanops.module.workorder.controller.app.garden.vo.common.AppUniversalApprovalReqVO; import com.zteits.urbanops.module.workorder.controller.app.garden.vo.operator.AppGardenTaskReturnExtReqVO; import com.zteits.urbanops.module.workorder.controller.app.garden.vo.operator.AppGardenTaskTeamLeaderAssignReqVO; @@ -16,12 +15,9 @@ import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGar import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskRegionMgrApproveReqVO; import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderInspectorReqVO; import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderReqVO; -import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderUniversalReqVO; -import com.zteits.urbanops.module.workorder.enums.InspectorOperateTypeEnum; import com.zteits.urbanops.module.workorder.enums.InspectorOperateTypeEnum; import com.zteits.urbanops.module.workorder.service.garden.BpmGardenService; import com.zteits.urbanops.module.workorder.service.garden.BpmInspectorService; -import com.zteits.urbanops.module.workorder.service.garden.BpmUniversalService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; @@ -76,6 +72,15 @@ public class AppGardenWorkOrderInspectorController { return success(inspectorService.createWorkOrder(createReqVO)); } + @PostMapping("/createBatch") + //@PreAuthorize("@ss.hasPermission('bpm:garden-workorder:create')") + @Operation(summary = "app-全域巡查员创建工单信息") + public CommonResult> createMainInfoBatch(@Valid @RequestBody List createReqVOList) { + return success(inspectorService.batchCreateWorkOrder(createReqVOList)); + } + + + @Operation(summary = "app-统一审批入口") @PostMapping("/universalApproval") @AutoUpdateWorkerOrderInspectorBuzStatus diff --git a/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/dal/dataobject/maininfo/MainInfoDO.java b/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/dal/dataobject/maininfo/MainInfoDO.java index 4eec0fc..aa2ab0c 100644 --- a/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/dal/dataobject/maininfo/MainInfoDO.java +++ b/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/dal/dataobject/maininfo/MainInfoDO.java @@ -1,17 +1,16 @@ package com.zteits.urbanops.module.workorder.dal.dataobject.maininfo; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import io.swagger.v3.oas.annotations.media.Schema; +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO; import lombok.*; -import java.util.*; -import java.time.LocalDateTime; -import java.math.BigDecimal; + import java.math.BigDecimal; import java.time.LocalDateTime; -import java.time.LocalDateTime; -import com.baomidou.mybatisplus.annotation.*; -import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO; -import org.hibernate.validator.constraints.Length; +import java.util.Set; /** * 工单信息 DO @@ -46,6 +45,14 @@ public class MainInfoDO extends BaseDO { */ private String orderName; /** + * 三级编码 + */ + private String orderCode; + /** + * 业务类型 + */ + private String busiType; + /** * 工单类型 Q:快速工单,C:普通工单,O:其他工单 */ private String orderType; diff --git a/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorService.java b/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorService.java index fc42531..e8ec5f7 100644 --- a/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorService.java +++ b/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorService.java @@ -1,12 +1,12 @@ package com.zteits.urbanops.module.workorder.service.garden; -import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskApproveReqVO; import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskInspectorApproveReqVO; import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskRegionMgrApproveReqVO; import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderInspectorReqVO; -import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderUniversalReqVO; import jakarta.validation.Valid; +import java.util.List; + /** * 类描述:全域督察员工单处理server * 创建人:yanhuiqing @@ -27,6 +27,14 @@ public interface BpmInspectorService { */ Long createWorkOrder(@Valid AppGardenWorkOrderInspectorReqVO createRequestVo); + + /** + * 批量创建工单 + * @param reqVOList 工单列表 + * @return 工单ID集合 + */ + List batchCreateWorkOrder(List reqVOList); + /** * 全域督察员工单分配 * @param regionPassReqVO diff --git a/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorServiceImpl.java b/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorServiceImpl.java index 6ad467a..96220a8 100644 --- a/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorServiceImpl.java +++ b/urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorServiceImpl.java @@ -11,6 +11,7 @@ import com.zteits.urbanops.module.bpm.controller.admin.task.vo.task.BpmTaskAppro import com.zteits.urbanops.module.bpm.enums.task.BpmTaskStatusEnum; import com.zteits.urbanops.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import com.zteits.urbanops.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zteits.urbanops.module.bpm.service.task.BpmProcessInstanceCopyService; import com.zteits.urbanops.module.bpm.service.task.BpmProcessInstanceService; import com.zteits.urbanops.module.bpm.service.task.BpmTaskService; import com.zteits.urbanops.module.garden.api.road.RoadApi; @@ -21,7 +22,6 @@ import com.zteits.urbanops.module.system.api.user.dto.AdminUserRespDTO; import com.zteits.urbanops.module.system.controller.admin.permission.vo.role.RoleRespVO; import com.zteits.urbanops.module.workorder.api.WorkOrderApi; import com.zteits.urbanops.module.workorder.api.constant.BpmCommonConstant; -import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskApproveReqVO; import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskInspectorApproveReqVO; import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskRegionMgrApproveReqVO; import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoDO; @@ -29,7 +29,6 @@ import com.zteits.urbanops.module.workorder.dal.mysql.attachment.AttachmentMappe import com.zteits.urbanops.module.workorder.dal.mysql.maininfo.MainInfoExtMapper; import com.zteits.urbanops.module.workorder.dal.mysql.maininfo.MainInfoMapper; import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderInspectorReqVO; -import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderUniversalReqVO; import com.zteits.urbanops.module.workorder.enums.BusiLineTeamLeaderRoleCodeEnum; import com.zteits.urbanops.module.workorder.enums.EventSourceEnum; import com.zteits.urbanops.module.workorder.util.RoleListUtils; @@ -78,6 +77,8 @@ public class BpmInspectorServiceImpl implements BpmInspectorService{ private BpmProcessInstanceApi processInstanceApi; @Resource + private BpmProcessInstanceCopyService copyService; + @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private WorkOrderApi workOrderApi; @@ -213,6 +214,11 @@ public class BpmInspectorServiceImpl implements BpmInspectorService{ String processInstanceId = processInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(BpmCommonConstant.BPM_UNIVERSE_INSPECTOR_WO) .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(workOrder.getId()))); + List userIds = roleApi.getUserIdsByRoleCode("Inspector_global_leader"); + //抄送 + if(null != userIds && !userIds.isEmpty()){ + copyService.createProcessInstanceCopy(userIds,"APP巡查工单创建,自动抄送",processInstanceId,"StartEvent","发起流程",null); + } // 将工作流的编号,更新到 工单中 workOrderMapper.updateById(new MainInfoDO().setId(workOrder.getId()).setProcessInstanceId(processInstanceId)); @@ -220,6 +226,18 @@ public class BpmInspectorServiceImpl implements BpmInspectorService{ } @Override + @Transactional(rollbackFor = Exception.class) + public List batchCreateWorkOrder(List reqVOList) { + List idList = new ArrayList<>(); + for (AppGardenWorkOrderInspectorReqVO reqVO : reqVOList) { + // 调用你原来的单个创建方法 + Long id = createWorkOrder(reqVO); + idList.add(id); + } + return idList; + } + + @Override public void approveTask2TeamLeader(AppGardenTaskRegionMgrApproveReqVO reqVO) { BpmTaskApproveReqVO targetVO = BeanUtils.toBean(reqVO,BpmTaskApproveReqVO.class); Map variables = new HashMap<>(); diff --git a/北京市行道树安全风险评估指南 2024.11.15.pdf b/北京市行道树安全风险评估指南 2024.11.15.pdf new file mode 100644 index 0000000..c702f3e --- /dev/null +++ b/北京市行道树安全风险评估指南 2024.11.15.pdf