2026-06-02 a217dbfc7378ff9cf5b6d3ca8b229a2b9d932e51
feat(device): 添加设备保养验收功能和年度定时任务支持

- 在DeviceMaintenance实体类中新增验收人、验收时间和验收备注字段
- 修改保养状态枚举,添加待验收状态(3)
- 实现保养确认功能,将状态变更为待验收
- 实现保养验收审批功能,完成验收流程
- 添加设备保养任务启用/禁用功能
- 支持年度频率类型的定时任务调度
- 新增质量检验模板导出功能,支持伟龙、百事、达利三种模板
- 优化数据表结构,添加验收相关字段和启用状态字段
- 完善定时任务的时间计算逻辑,支持年度执行计划
已添加10个文件
已修改18个文件
1966 ■■■■■ 文件已修改
classpath.txt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260602_device_maintenance_acceptance.sql 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/前端联调文档-设备保养验收人功能.md 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/前端联调文档-设备报修验收审批功能.md 361 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/前端联调文档-过程检验模板导出功能.md 275 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/maintenance_task_api.md 317 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/MaintenanceTaskController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceRepairDto.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/MaintenanceTaskService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskScheduler.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduleUtils.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/IQualityInspectService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/utils/QualityInspectTemplateExportHelper.java 489 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/伟龙模版.doc 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/百事模版.doc 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/达利模版.doc 补丁 | 查看 | 原始文档 | blame | 历史
classpath.txt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
D:\Maven3.6.3\myRepository\com\github\xiaoymin\knife4j-openapi3-jakarta-spring-boot-starter\4.5.0\knife4j-openapi3-jakarta-spring-boot-starter-4.5.0.jar;D:\Maven3.6.3\myRepository\com\github\xiaoymin\knife4j-core\4.5.0\knife4j-core-4.5.0.jar;D:\Maven3.6.3\myRepository\com\github\xiaoymin\knife4j-openapi3-ui\4.5.0\knife4j-openapi3-ui-4.5.0.jar;D:\Maven3.6.3\myRepository\org\springdoc\springdoc-openapi-starter-webmvc-ui\2.8.17\springdoc-openapi-starter-webmvc-ui-2.8.17.jar;D:\Maven3.6.3\myRepository\org\springdoc\springdoc-openapi-starter-webmvc-api\2.8.17\springdoc-openapi-starter-webmvc-api-2.8.17.jar;D:\Maven3.6.3\myRepository\org\springdoc\springdoc-openapi-starter-common\2.8.17\springdoc-openapi-starter-common-2.8.17.jar;D:\Maven3.6.3\myRepository\io\swagger\core\v3\swagger-core-jakarta\2.2.47\swagger-core-jakarta-2.2.47.jar;D:\Maven3.6.3\myRepository\io\swagger\core\v3\swagger-annotations-jakarta\2.2.47\swagger-annotations-jakarta-2.2.47.jar;D:\Maven3.6.3\myRepository\io\swagger\core\v3\swagger-models-jakarta\2.2.47\swagger-models-jakarta-2.2.47.jar;D:\Maven3.6.3\myRepository\com\fasterxml\jackson\dataformat\jackson-dataformat-yaml\2.18.3\jackson-dataformat-yaml-2.18.3.jar;D:\Maven3.6.3\myRepository\org\webjars\swagger-ui\5.32.2\swagger-ui-5.32.2.jar;D:\Maven3.6.3\myRepository\org\webjars\webjars-locator-lite\1.1.3\webjars-locator-lite-1.1.3.jar;D:\Maven3.6.3\myRepository\org\jspecify\jspecify\1.0.0\jspecify-1.0.0.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter\3.5.13\spring-boot-starter-3.5.13.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot\3.5.13\spring-boot-3.5.13.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-autoconfigure\3.5.13\spring-boot-autoconfigure-3.5.13.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-logging\3.5.13\spring-boot-starter-logging-3.5.13.jar;D:\Maven3.6.3\myRepository\org\apache\logging\log4j\log4j-to-slf4j\2.24.3\log4j-to-slf4j-2.24.3.jar;D:\Maven3.6.3\myRepository\org\slf4j\jul-to-slf4j\2.0.17\jul-to-slf4j-2.0.17.jar;D:\Maven3.6.3\myRepository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-core\6.2.17\spring-core-6.2.17.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-jcl\6.2.17\spring-jcl-6.2.17.jar;D:\Maven3.6.3\myRepository\org\yaml\snakeyaml\2.4\snakeyaml-2.4.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-aop\3.5.13\spring-boot-starter-aop-3.5.13.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-aop\6.2.17\spring-aop-6.2.17.jar;D:\Maven3.6.3\myRepository\org\aspectj\aspectjweaver\1.9.25.1\aspectjweaver-1.9.25.1.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-web\3.5.13\spring-boot-starter-web-3.5.13.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-json\3.5.13\spring-boot-starter-json-3.5.13.jar;D:\Maven3.6.3\myRepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.18.3\jackson-datatype-jdk8-2.18.3.jar;D:\Maven3.6.3\myRepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.18.3\jackson-module-parameter-names-2.18.3.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-tomcat\3.5.13\spring-boot-starter-tomcat-3.5.13.jar;D:\Maven3.6.3\myRepository\org\apache\tomcat\embed\tomcat-embed-core\10.1.53\tomcat-embed-core-10.1.53.jar;D:\Maven3.6.3\myRepository\org\apache\tomcat\embed\tomcat-embed-websocket\10.1.53\tomcat-embed-websocket-10.1.53.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-web\6.2.17\spring-web-6.2.17.jar;D:\Maven3.6.3\myRepository\io\micrometer\micrometer-observation\1.15.10\micrometer-observation-1.15.10.jar;D:\Maven3.6.3\myRepository\io\micrometer\micrometer-commons\1.15.10\micrometer-commons-1.15.10.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-webmvc\6.2.17\spring-webmvc-6.2.17.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-expression\6.2.17\spring-expression-6.2.17.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-devtools\3.5.13\spring-boot-devtools-3.5.13.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-security\3.5.13\spring-boot-starter-security-3.5.13.jar;D:\Maven3.6.3\myRepository\org\springframework\security\spring-security-config\6.5.9\spring-security-config-6.5.9.jar;D:\Maven3.6.3\myRepository\org\springframework\security\spring-security-core\6.5.9\spring-security-core-6.5.9.jar;D:\Maven3.6.3\myRepository\org\springframework\security\spring-security-crypto\6.5.9\spring-security-crypto-6.5.9.jar;D:\Maven3.6.3\myRepository\org\springframework\security\spring-security-web\6.5.9\spring-security-web-6.5.9.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-data-redis\3.5.13\spring-boot-starter-data-redis-3.5.13.jar;D:\Maven3.6.3\myRepository\io\lettuce\lettuce-core\6.6.0.RELEASE\lettuce-core-6.6.0.RELEASE.jar;D:\Maven3.6.3\myRepository\redis\clients\authentication\redis-authx-core\0.1.1-beta2\redis-authx-core-0.1.1-beta2.jar;D:\Maven3.6.3\myRepository\io\netty\netty-common\4.1.119.Final\netty-common-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\io\netty\netty-handler\4.1.119.Final\netty-handler-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\io\netty\netty-resolver\4.1.119.Final\netty-resolver-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\io\netty\netty-buffer\4.1.119.Final\netty-buffer-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\io\netty\netty-transport-native-unix-common\4.1.119.Final\netty-transport-native-unix-common-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\io\netty\netty-codec\4.1.119.Final\netty-codec-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\io\netty\netty-transport\4.1.119.Final\netty-transport-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\org\springframework\data\spring-data-redis\3.5.10\spring-data-redis-3.5.10.jar;D:\Maven3.6.3\myRepository\org\springframework\data\spring-data-keyvalue\3.5.10\spring-data-keyvalue-3.5.10.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-tx\6.2.17\spring-tx-6.2.17.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-oxm\6.2.17\spring-oxm-6.2.17.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-data-mongodb\3.5.13\spring-boot-starter-data-mongodb-3.5.13.jar;D:\Maven3.6.3\myRepository\org\mongodb\mongodb-driver-sync\5.5.2\mongodb-driver-sync-5.5.2.jar;D:\Maven3.6.3\myRepository\org\mongodb\bson\5.5.2\bson-5.5.2.jar;D:\Maven3.6.3\myRepository\org\mongodb\mongodb-driver-core\5.5.2\mongodb-driver-core-5.5.2.jar;D:\Maven3.6.3\myRepository\org\springframework\data\spring-data-mongodb\4.5.10\spring-data-mongodb-4.5.10.jar;D:\Maven3.6.3\myRepository\org\springframework\data\spring-data-commons\3.5.10\spring-data-commons-3.5.10.jar;D:\Maven3.6.3\myRepository\org\apache\commons\commons-pool2\2.12.1\commons-pool2-2.12.1.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-spring-boot-starter\1.0.0-beta3\langchain4j-spring-boot-starter-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j\1.0.0-beta3\langchain4j-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\org\apache\opennlp\opennlp-tools\1.9.4\opennlp-tools-1.9.4.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-pinecone\1.0.0-beta3\langchain4j-pinecone-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-core\1.0.0-beta3\langchain4j-core-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\io\pinecone\pinecone-client\3.1.0\pinecone-client-3.1.0.jar;D:\Maven3.6.3\myRepository\io\grpc\grpc-protobuf\1.60.2\grpc-protobuf-1.60.2.jar;D:\Maven3.6.3\myRepository\io\grpc\grpc-api\1.60.2\grpc-api-1.60.2.jar;D:\Maven3.6.3\myRepository\io\grpc\grpc-protobuf-lite\1.60.2\grpc-protobuf-lite-1.60.2.jar;D:\Maven3.6.3\myRepository\io\grpc\grpc-stub\1.60.2\grpc-stub-1.60.2.jar;D:\Maven3.6.3\myRepository\io\grpc\grpc-netty\1.60.2\grpc-netty-1.60.2.jar;D:\Maven3.6.3\myRepository\io\grpc\grpc-core\1.60.2\grpc-core-1.60.2.jar;D:\Maven3.6.3\myRepository\io\netty\netty-codec-http2\4.1.119.Final\netty-codec-http2-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\io\netty\netty-codec-http\4.1.119.Final\netty-codec-http-4.1.119.Final.jar;D:\Maven3.6.3\myRepository\com\google\api\grpc\proto-google-common-protos\2.14.3\proto-google-common-protos-2.14.3.jar;D:\Maven3.6.3\myRepository\com\squareup\okhttp3\logging-interceptor\4.12.0\logging-interceptor-4.12.0.jar;D:\Maven3.6.3\myRepository\com\google\protobuf\protobuf-java\3.25.2\protobuf-java-3.25.2.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-open-ai-spring-boot-starter\1.0.0-beta3\langchain4j-open-ai-spring-boot-starter-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-open-ai\1.0.0-beta3\langchain4j-open-ai-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\com\knuddels\jtokkit\1.1.0\jtokkit-1.1.0.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-http-client-spring-restclient\1.0.0-beta3\langchain4j-http-client-spring-restclient-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-http-client\1.0.0-beta3\langchain4j-http-client-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-reactor\1.0.0-beta3\langchain4j-reactor-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\io\projectreactor\reactor-core\3.7.3\reactor-core-3.7.3.jar;D:\Maven3.6.3\myRepository\org\reactivestreams\reactive-streams\1.0.4\reactive-streams-1.0.4.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-ollama-spring-boot-starter\1.0.0-beta3\langchain4j-ollama-spring-boot-starter-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-ollama\1.0.0-beta3\langchain4j-ollama-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-community-dashscope-spring-boot-starter\1.0.0-beta3\langchain4j-community-dashscope-spring-boot-starter-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-community-dashscope\1.0.0-beta3\langchain4j-community-dashscope-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\com\alibaba\dashscope-sdk-java\2.18.5\dashscope-sdk-java-2.18.5.jar;D:\Maven3.6.3\myRepository\io\reactivex\rxjava2\rxjava\2.2.21\rxjava-2.2.21.jar;D:\Maven3.6.3\myRepository\com\github\victools\jsonschema-generator\4.31.1\jsonschema-generator-4.31.1.jar;D:\Maven3.6.3\myRepository\ch\qos\logback\logback-classic\1.5.32\logback-classic-1.5.32.jar;D:\Maven3.6.3\myRepository\ch\qos\logback\logback-core\1.5.32\logback-core-1.5.32.jar;D:\Maven3.6.3\myRepository\dev\langchain4j\langchain4j-mcp\1.0.0-beta3\langchain4j-mcp-1.0.0-beta3.jar;D:\Maven3.6.3\myRepository\com\fasterxml\jackson\core\jackson-databind\2.18.3\jackson-databind-2.18.3.jar;D:\Maven3.6.3\myRepository\com\squareup\okhttp3\okhttp-sse\4.12.0\okhttp-sse-4.12.0.jar;D:\Maven3.6.3\myRepository\org\jetbrains\kotlin\kotlin-stdlib-jdk8\1.9.25\kotlin-stdlib-jdk8-1.9.25.jar;D:\Maven3.6.3\myRepository\org\jetbrains\kotlin\kotlin-stdlib-jdk7\1.9.25\kotlin-stdlib-jdk7-1.9.25.jar;D:\Maven3.6.3\myRepository\org\freemarker\freemarker\2.3.33\freemarker-2.3.33.jar;D:\Maven3.6.3\myRepository\com\github\pagehelper\pagehelper-spring-boot-starter\2.1.1\pagehelper-spring-boot-starter-2.1.1.jar;D:\Maven3.6.3\myRepository\org\mybatis\spring\boot\mybatis-spring-boot-starter\2.3.2\mybatis-spring-boot-starter-2.3.2.jar;D:\Maven3.6.3\myRepository\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\2.3.2\mybatis-spring-boot-autoconfigure-2.3.2.jar;D:\Maven3.6.3\myRepository\com\github\pagehelper\pagehelper-spring-boot-autoconfigure\2.1.1\pagehelper-spring-boot-autoconfigure-2.1.1.jar;D:\Maven3.6.3\myRepository\com\github\pagehelper\pagehelper\6.1.1\pagehelper-6.1.1.jar;D:\Maven3.6.3\myRepository\com\alibaba\druid-spring-boot-3-starter\1.2.23\druid-spring-boot-3-starter-1.2.23.jar;D:\Maven3.6.3\myRepository\com\alibaba\druid\1.2.23\druid-1.2.23.jar;D:\Maven3.6.3\myRepository\org\slf4j\slf4j-api\2.0.17\slf4j-api-2.0.17.jar;D:\Maven3.6.3\myRepository\org\mybatis\mybatis\3.5.16\mybatis-3.5.16.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-spring-boot3-starter\3.5.16\mybatis-plus-spring-boot3-starter-3.5.16.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus\3.5.16\mybatis-plus-3.5.16.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-core\3.5.16\mybatis-plus-core-3.5.16.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-annotation\3.5.16\mybatis-plus-annotation-3.5.16.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-spring\3.5.16\mybatis-plus-spring-3.5.16.jar;D:\Maven3.6.3\myRepository\org\mybatis\mybatis-spring\3.0.5\mybatis-spring-3.0.5.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-spring-boot-autoconfigure\3.5.16\mybatis-plus-spring-boot-autoconfigure-3.5.16.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-jdbc\3.5.13\spring-boot-starter-jdbc-3.5.13.jar;D:\Maven3.6.3\myRepository\com\zaxxer\HikariCP\6.3.3\HikariCP-6.3.3.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-jdbc\6.2.17\spring-jdbc-6.2.17.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-jsqlparser\3.5.16\mybatis-plus-jsqlparser-3.5.16.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-jsqlparser-common\3.5.16\mybatis-plus-jsqlparser-common-3.5.16.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-extension\3.5.16\mybatis-plus-extension-3.5.16.jar;D:\Maven3.6.3\myRepository\com\baomidou\mybatis-plus-generator\3.5.16\mybatis-plus-generator-3.5.16.jar;D:\Maven3.6.3\myRepository\com\github\jsqlparser\jsqlparser\4.9\jsqlparser-4.9.jar;D:\Maven3.6.3\myRepository\org\springframework\boot\spring-boot-starter-validation\3.5.13\spring-boot-starter-validation-3.5.13.jar;D:\Maven3.6.3\myRepository\org\apache\tomcat\embed\tomcat-embed-el\10.1.53\tomcat-embed-el-10.1.53.jar;D:\Maven3.6.3\myRepository\org\hibernate\validator\hibernate-validator\8.0.3.Final\hibernate-validator-8.0.3.Final.jar;D:\Maven3.6.3\myRepository\jakarta\validation\jakarta.validation-api\3.0.2\jakarta.validation-api-3.0.2.jar;D:\Maven3.6.3\myRepository\org\jboss\logging\jboss-logging\3.6.3.Final\jboss-logging-3.6.3.Final.jar;D:\Maven3.6.3\myRepository\com\fasterxml\classmate\1.7.3\classmate-1.7.3.jar;D:\Maven3.6.3\myRepository\org\apache\commons\commons-lang3\3.17.0\commons-lang3-3.17.0.jar;D:\Maven3.6.3\myRepository\commons-io\commons-io\2.13.0\commons-io-2.13.0.jar;D:\Maven3.6.3\myRepository\eu\bitwalker\UserAgentUtils\1.21\UserAgentUtils-1.21.jar;D:\Maven3.6.3\myRepository\com\alibaba\fastjson2\fastjson2\2.0.53\fastjson2-2.0.53.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-context-support\6.2.17\spring-context-support-6.2.17.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-beans\6.2.17\spring-beans-6.2.17.jar;D:\Maven3.6.3\myRepository\org\springframework\spring-context\6.2.17\spring-context-6.2.17.jar;D:\Maven3.6.3\myRepository\io\jsonwebtoken\jjwt-api\0.13.0\jjwt-api-0.13.0.jar;D:\Maven3.6.3\myRepository\jakarta\xml\bind\jakarta.xml.bind-api\4.0.4\jakarta.xml.bind-api-4.0.4.jar;D:\Maven3.6.3\myRepository\jakarta\activation\jakarta.activation-api\2.1.4\jakarta.activation-api-2.1.4.jar;D:\Maven3.6.3\myRepository\io\swagger\swagger-annotations\1.6.15\swagger-annotations-1.6.15.jar;D:\Maven3.6.3\myRepository\com\github\oshi\oshi-core\6.6.5\oshi-core-6.6.5.jar;D:\Maven3.6.3\myRepository\net\java\dev\jna\jna\5.15.0\jna-5.15.0.jar;D:\Maven3.6.3\myRepository\net\java\dev\jna\jna-platform\5.15.0\jna-platform-5.15.0.jar;D:\Maven3.6.3\myRepository\org\apache\poi\poi-ooxml\5.2.3\poi-ooxml-5.2.3.jar;D:\Maven3.6.3\myRepository\org\apache\poi\poi\5.2.3\poi-5.2.3.jar;D:\Maven3.6.3\myRepository\commons-codec\commons-codec\1.18.0\commons-codec-1.18.0.jar;D:\Maven3.6.3\myRepository\org\apache\commons\commons-math3\3.6.1\commons-math3-3.6.1.jar;D:\Maven3.6.3\myRepository\com\zaxxer\SparseBitSet\1.2\SparseBitSet-1.2.jar;D:\Maven3.6.3\myRepository\org\apache\poi\poi-ooxml-lite\5.2.3\poi-ooxml-lite-5.2.3.jar;D:\Maven3.6.3\myRepository\org\apache\xmlbeans\xmlbeans\5.1.1\xmlbeans-5.1.1.jar;D:\Maven3.6.3\myRepository\org\apache\commons\commons-compress\1.21\commons-compress-1.21.jar;D:\Maven3.6.3\myRepository\com\github\virtuald\curvesapi\1.07\curvesapi-1.07.jar;D:\Maven3.6.3\myRepository\org\apache\logging\log4j\log4j-api\2.24.3\log4j-api-2.24.3.jar;D:\Maven3.6.3\myRepository\org\apache\commons\commons-collections4\4.4\commons-collections4-4.4.jar;D:\Maven3.6.3\myRepository\org\apache\velocity\velocity-engine-core\2.3\velocity-engine-core-2.3.jar;D:\Maven3.6.3\myRepository\org\quartz-scheduler\quartz\2.5.2\quartz-2.5.2.jar;D:\Maven3.6.3\myRepository\pro\fessional\kaptcha\2.3.3\kaptcha-2.3.3.jar;D:\Maven3.6.3\myRepository\com\jhlabs\filters\2.0.235-1\filters-2.0.235-1.jar;D:\Maven3.6.3\myRepository\org\projectlombok\lombok\1.18.44\lombok-1.18.44.jar;D:\Maven3.6.3\myRepository\io\minio\minio\8.4.3\minio-8.4.3.jar;D:\Maven3.6.3\myRepository\com\carrotsearch\thirdparty\simple-xml-safe\2.7.1\simple-xml-safe-2.7.1.jar;D:\Maven3.6.3\myRepository\com\google\guava\guava\30.1.1-jre\guava-30.1.1-jre.jar;D:\Maven3.6.3\myRepository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;D:\Maven3.6.3\myRepository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;D:\Maven3.6.3\myRepository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;D:\Maven3.6.3\myRepository\org\checkerframework\checker-qual\3.8.0\checker-qual-3.8.0.jar;D:\Maven3.6.3\myRepository\com\google\errorprone\error_prone_annotations\2.5.1\error_prone_annotations-2.5.1.jar;D:\Maven3.6.3\myRepository\com\google\j2objc\j2objc-annotations\1.3\j2objc-annotations-1.3.jar;D:\Maven3.6.3\myRepository\com\fasterxml\jackson\core\jackson-annotations\2.18.3\jackson-annotations-2.18.3.jar;D:\Maven3.6.3\myRepository\com\fasterxml\jackson\core\jackson-core\2.18.3\jackson-core-2.18.3.jar;D:\Maven3.6.3\myRepository\org\bouncycastle\bcprov-jdk15on\1.69\bcprov-jdk15on-1.69.jar;D:\Maven3.6.3\myRepository\org\xerial\snappy\snappy-java\1.1.8.4\snappy-java-1.1.8.4.jar;D:\Maven3.6.3\myRepository\com\squareup\okhttp3\okhttp\4.9.0\okhttp-4.9.0.jar;D:\Maven3.6.3\myRepository\com\squareup\okio\okio\2.8.0\okio-2.8.0.jar;D:\Maven3.6.3\myRepository\org\jetbrains\kotlin\kotlin-stdlib-common\1.9.25\kotlin-stdlib-common-1.9.25.jar;D:\Maven3.6.3\myRepository\org\jetbrains\kotlin\kotlin-stdlib\1.9.25\kotlin-stdlib-1.9.25.jar;D:\Maven3.6.3\myRepository\org\jetbrains\annotations\13.0\annotations-13.0.jar;D:\Maven3.6.3\myRepository\com\deepoove\poi-tl\1.12.2\poi-tl-1.12.2.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-transcoder\1.17\batik-transcoder-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-anim\1.17\batik-anim-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-css\1.17\batik-css-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-ext\1.17\batik-ext-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-parser\1.17\batik-parser-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-svg-dom\1.17\batik-svg-dom-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-awt-util\1.17\batik-awt-util-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\xmlgraphics-commons\2.9\xmlgraphics-commons-2.9.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-bridge\1.17\batik-bridge-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-script\1.17\batik-script-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-dom\1.17\batik-dom-1.17.jar;D:\Maven3.6.3\myRepository\xml-apis\xml-apis\1.4.01\xml-apis-1.4.01.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-gvt\1.17\batik-gvt-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-shared-resources\1.17\batik-shared-resources-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-svggen\1.17\batik-svggen-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-util\1.17\batik-util-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-constants\1.17\batik-constants-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-i18n\1.17\batik-i18n-1.17.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-xml\1.17\batik-xml-1.17.jar;D:\Maven3.6.3\myRepository\xml-apis\xml-apis-ext\1.3.04\xml-apis-ext-1.3.04.jar;D:\Maven3.6.3\myRepository\org\apache\xmlgraphics\batik-codec\1.17\batik-codec-1.17.jar;D:\Maven3.6.3\myRepository\com\alibaba\easyexcel\4.0.3\easyexcel-4.0.3.jar;D:\Maven3.6.3\myRepository\com\alibaba\easyexcel-core\4.0.3\easyexcel-core-4.0.3.jar;D:\Maven3.6.3\myRepository\com\alibaba\easyexcel-support\3.3.4\easyexcel-support-3.3.4.jar;D:\Maven3.6.3\myRepository\org\apache\commons\commons-csv\1.11.0\commons-csv-1.11.0.jar;D:\Maven3.6.3\myRepository\org\ehcache\ehcache\3.10.9\ehcache-3.10.9.jar;D:\Maven3.6.3\myRepository\javax\cache\cache-api\1.1.1\cache-api-1.1.1.jar;D:\Maven3.6.3\myRepository\com\google\zxing\core\3.3.3\core-3.3.3.jar;D:\Maven3.6.3\myRepository\com\getui\push\restful-sdk\1.0.7.0\restful-sdk-1.0.7.0.jar;D:\Maven3.6.3\myRepository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;D:\Maven3.6.3\myRepository\org\apache\httpcomponents\httpcore\4.4.16\httpcore-4.4.16.jar;D:\Maven3.6.3\myRepository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\Maven3.6.3\myRepository\com\google\code\gson\gson\2.13.2\gson-2.13.2.jar;D:\Maven3.6.3\myRepository\cn\hutool\hutool-all\5.8.43\hutool-all-5.8.43.jar;D:\Maven3.6.3\myRepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.18.3\jackson-datatype-jsr310-2.18.3.jar;D:\Maven3.6.3\myRepository\net\coobird\thumbnailator\0.4.20\thumbnailator-0.4.20.jar
doc/20260602_device_maintenance_acceptance.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
-- è®¾å¤‡ä¿å…»è¡¨æ–°å¢žéªŒæ”¶äººç›¸å…³å­—段
ALTER TABLE `device_maintenance`
    ADD COLUMN `acceptance_name` VARCHAR(100) NULL COMMENT '验收人' AFTER `spare_parts_ids`,
    ADD COLUMN `acceptance_time` DATETIME NULL COMMENT '验收时间' AFTER `acceptance_name`,
    ADD COLUMN `acceptance_remark` VARCHAR(500) NULL COMMENT '验收备注' AFTER `acceptance_time`;
-- è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡è¡¨æ–°å¢žæ˜¯å¦å¯ç”¨å­—æ®µ
ALTER TABLE `maintenance_task`
    ADD COLUMN `is_enabled` TINYINT(1) NULL DEFAULT 1 COMMENT '是否启用 0-禁用 1-启用' AFTER `is_active`;
doc/ǰ¶ËÁªµ÷Îĵµ-É豸±£ÑøÑéÊÕÈ˹¦ÄÜ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,232 @@
# å‰ç«¯è”调文档 - è®¾å¤‡ä¿å…»éªŒæ”¶äººåŠŸèƒ½
## 1. å˜æ›´æ¦‚è¿°
本次变更涉及设备保养模块新增验收审批流程,与设备报修验收流程保持一致:
- è®¾å¤‡ä¿å…»ç¡®è®¤åŽæ–°å¢žéªŒæ”¶å®¡æ‰¹çŽ¯èŠ‚
- éªŒæ”¶é€šè¿‡åŽæ‰ç®—完结
- æ–°å¢žéªŒæ”¶äººã€éªŒæ”¶æ—¶é—´ã€éªŒæ”¶å¤‡æ³¨å­—段
---
## 2. æŽ¥å£æ¸…单
### 2.1 è®¾å¤‡ä¿å…»
基础路径:`/device/maintenance`
#### çŠ¶æ€å®šä¹‰
| çŠ¶æ€å€¼ | å«ä¹‰ |
|---|---|
| `0` | å¾…保养 |
| `3` | å¾…验收 |
| `1` | å®Œç»“ |
| `2` | å¤±è´¥ |
---
### 2.2 ç¡®è®¤ä¿å…»ï¼ˆæ–°å¢žï¼‰
- **POST** `/device/maintenance/maintenance`
- è¯´æ˜Žï¼šæäº¤åŽçŠ¶æ€ä»Ž `待保养(0)` è¿›å…¥ `待验收(3)`,不再直接完结。
#### è¯·æ±‚参数(Body)
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| `id` | long | æ˜¯ | ä¿å…»è®°å½•ID |
| `maintenanceActuallyName` | string | å¦ | å®žé™…保养人 |
| `maintenanceActuallyTime` | string | å¦ | å®žé™…保养时间,格式 `yyyy-MM-dd HH:mm:ss` |
| `maintenanceResult` | string | å¦ | ä¿å…»ç»“æžœ 0-ç»´ä¿® 1-完好 |
| `sparePartsUseList` | array | å¦ | ä½¿ç”¨å¤‡ä»¶åˆ—表 |
#### è¯·æ±‚示例
```json
{
  "id": 10001,
  "maintenanceActuallyName": "张三",
  "maintenanceActuallyTime": "2026-06-02 10:30:00",
  "maintenanceResult": "1",
  "sparePartsUseList": [
    {
      "id": 501,
      "quantity": 2
    }
  ]
}
```
#### å¸¸è§å¤±è´¥æç¤º
- `保养记录不存在`
- `该保养已完结,不能重复确认保养`
- `该保养已提交验收审批`
- `备件 xxx æ•°é‡ä¸è¶³`
---
### 2.3 éªŒæ”¶å®¡æ‰¹ï¼ˆæ–°å¢žï¼‰
- **POST** `/device/maintenance/acceptance`
- è¯´æ˜Žï¼šä»… `待验收(3)` å¯å®¡æ‰¹ï¼›å®¡æ‰¹é€šè¿‡åŽçŠ¶æ€æ”¹ä¸º `完结(1)`。
#### è¯·æ±‚参数(Body)
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| `id` | long | æ˜¯ | ä¿å…»è®°å½•ID |
| `acceptanceName` | string | æ˜¯ | éªŒæ”¶äºº |
| `acceptanceTime` | string | æ˜¯ | éªŒæ”¶æ—¶é—´ï¼Œæ ¼å¼ `yyyy-MM-dd HH:mm:ss` |
| `acceptanceRemark` | string | æ˜¯ | éªŒæ”¶å¤‡æ³¨ |
#### è¯·æ±‚示例
```json
{
  "id": 10001,
  "acceptanceName": "李四",
  "acceptanceTime": "2026-06-02 11:00:00",
  "acceptanceRemark": "保养项核验通过,设备运行正常"
}
```
#### å¸¸è§å¤±è´¥æç¤º
- `保养记录id不能为空`
- `保养记录不存在`
- `该保养未进入待验收状态,不能审批`
- `验收人不能为空`
- `验收时间不能为空`
- `验收备注不能为空`
---
### 2.4 æ™®é€šæ›´æ–°æŽ¥å£é™åˆ¶
- **PUT** `/device/maintenance`
- é™åˆ¶ï¼šä¸èƒ½é€šè¿‡æ™®é€šæ›´æ–°ç›´æŽ¥æŠŠçŠ¶æ€æ”¹æˆ `完结(1)`(必须走验收审批接口)。
- å¤±è´¥æç¤ºï¼š`请先提交验收审批,验收通过后才可完结`
---
## 3. è¿”回字段变更
以下接口返回已新增验收字段:
- **GET** `/device/maintenance/page`
- **GET** `/device/maintenance/{id}`
### æ–°å¢žè¿”回字段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| `acceptanceName` | string | éªŒæ”¶äºº |
| `acceptanceTime` | string | éªŒæ”¶æ—¶é—´ï¼Œæ ¼å¼ `yyyy-MM-dd HH:mm:ss` |
| `acceptanceRemark` | string | éªŒæ”¶å¤‡æ³¨ |
---
## 4. è®¾å¤‡æŠ¥ä¿®éªŒæ”¶æµç¨‹ï¼ˆå‚考)
设备报修模块已有相同逻辑,可参考实现:
| çŠ¶æ€å€¼ | å«ä¹‰ |
|---|---|
| `0` | å¾…ç»´ä¿® |
| `3` | å¾…验收 |
| `1` | å®Œç»“ |
| `2` | å¤±è´¥ |
### æŠ¥ä¿®ç›¸å…³æŽ¥å£
| æŽ¥å£ | è¯´æ˜Ž |
|---|---|
| **POST** `/device/repair/repair` | ç¡®è®¤ç»´ä¿®ï¼ŒçŠ¶æ€ 0→3 |
| **POST** `/device/repair/acceptance` | éªŒæ”¶å®¡æ‰¹ï¼ŒçŠ¶æ€ 3→1 |
### æŠ¥ä¿®éªŒæ”¶è¯·æ±‚示例
```json
{
  "id": 10001,
  "acceptanceName": "王五",
  "acceptanceTime": "2026-05-14 11:00:00",
  "acceptanceRemark": "维修项核验通过,设备运行正常"
}
```
---
## 5. å‰ç«¯æ”¹é€ å»ºè®®
### 5.1 è®¾å¤‡ä¿å…»é¡µé¢
1. åˆ—表增加状态值 `3=待验收` çš„展示文案与筛选项。
2. "确认保养"按钮调用 `/device/maintenance/maintenance`,成功后刷新为待验收状态。
3. æ–°å¢ž"验收审批"弹窗,必填:
   - éªŒæ”¶äºº
   - éªŒæ”¶æ—¶é—´
   - éªŒæ”¶å¤‡æ³¨
4. ç¦æ­¢åœ¨æ™®é€šç¼–辑页直接将状态置为完结。
### 5.2 è®¾å¤‡æŠ¥ä¿®é¡µé¢
已有验收流程,确认逻辑与保养一致:
- çŠ¶æ€æµè½¬ï¼š`0待维修 -> 3待验收 -> 1完结`
- éªŒæ”¶æŽ¥å£ï¼š`/device/repair/acceptance`
---
## 6. è”调检查清单
### è®¾å¤‡ä¿å…»
1. ä¿å…»å•流程:`0待保养 -> 3待验收 -> 1完结`。
2. å¾…验收单据未填验收人/验收时间/验收备注时,后端返回对应错误提示。
3. å°è¯•通过 `PUT /device/maintenance` ç›´æŽ¥è®¾ä¸ºå®Œç»“时,后端返回拦截提示。
4. åˆ—表/详情接口能正确返回 `acceptanceName`、`acceptanceTime`、`acceptanceRemark`。
### è®¾å¤‡æŠ¥ä¿®
1. æŠ¥ä¿®å•流程:`0待维修 -> 3待验收 -> 1完结`。
2. å¾…验收单据未填验收人/验收时间/验收备注时,后端返回对应错误提示。
3. å°è¯•通过 `PUT /device/repair` ç›´æŽ¥è®¾ä¸ºå®Œç»“时,后端返回拦截提示。
---
## 7. æ•°æ®åº“变更
执行以下SQL前请备份数据:
```sql
-- è®¾å¤‡ä¿å…»è¡¨æ–°å¢žéªŒæ”¶äººç›¸å…³å­—段
ALTER TABLE `device_maintenance`
    ADD COLUMN `acceptance_name` VARCHAR(100) NULL COMMENT '验收人' AFTER `spare_parts_ids`,
    ADD COLUMN `acceptance_time` DATETIME NULL COMMENT '验收时间' AFTER `acceptance_name`,
    ADD COLUMN `acceptance_remark` VARCHAR(500) NULL COMMENT '验收备注' AFTER `acceptance_time`;
```
> è‹¥æœªæ‰§è¡Œä»¥ä¸ŠSQL,相关接口会出现字段不存在异常。
---
## 8. å­—段对照表
### è®¾å¤‡ä¿å…»ï¼ˆdevice_maintenance)
| æ•°æ®åº“字段 | Java字段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|---|
| `acceptance_name` | `acceptanceName` | String | éªŒæ”¶äºº |
| `acceptance_time` | `acceptanceTime` | LocalDateTime | éªŒæ”¶æ—¶é—´ |
| `acceptance_remark` | `acceptanceRemark` | String | éªŒæ”¶å¤‡æ³¨ |
### è®¾å¤‡æŠ¥ä¿®ï¼ˆdevice_repair)
| æ•°æ®åº“字段 | Java字段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|---|
| `acceptance_name` | `acceptanceName` | String | éªŒæ”¶äºº |
| `acceptance_time` | `acceptanceTime` | LocalDateTime | éªŒæ”¶æ—¶é—´ |
| `acceptance_remark` | `acceptanceRemark` | String | éªŒæ”¶å¤‡æ³¨ |
doc/ǰ¶ËÁªµ÷Îĵµ-É豸±¨ÐÞÑéÊÕÉóÅú¹¦ÄÜ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,361 @@
# å‰ç«¯è”调文档 - è®¾å¤‡æŠ¥ä¿®éªŒæ”¶å®¡æ‰¹åŠŸèƒ½
## 1. åŠŸèƒ½æ¦‚è¿°
设备报修模块实现了完整的验收审批流程:
- ç»´ä¿®ç¡®è®¤åŽè¿›å…¥å¾…验收状态
- éªŒæ”¶å®¡æ‰¹é€šè¿‡åŽæ‰ç®—完结
- è®°å½•验收人、验收时间、验收备注
---
## 2. çŠ¶æ€å®šä¹‰
| çŠ¶æ€å€¼ | å«ä¹‰ | è¯´æ˜Ž |
|---|---|---|
| `0` | å¾…ç»´ä¿® | æŠ¥ä¿®å•初始状态 |
| `3` | å¾…验收 | ç»´ä¿®ç¡®è®¤åŽç­‰å¾…验收 |
| `1` | å®Œç»“ | éªŒæ”¶é€šè¿‡ |
| `2` | å¤±è´¥ | ç»´ä¿®å¤±è´¥ |
### çŠ¶æ€æµè½¬å›¾
```
待维修(0) --> å¾…验收(3) --> å®Œç»“(1)
                |
                +--> å¤±è´¥(2)
```
---
## 3. æŽ¥å£æ¸…单
### 3.1 ç»´ä¿®ç¡®è®¤
- **POST** `/device/repair/repair`
- è¯´æ˜Žï¼šç»´ä¿®äººå‘˜å®Œæˆç»´ä¿®åŽæäº¤ï¼ŒçŠ¶æ€ä»Ž `待维修(0)` è¿›å…¥ `待验收(3)`
#### è¯·æ±‚参数(Body)
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| `id` | Long | æ˜¯ | æŠ¥ä¿®è®°å½•ID |
| `maintenanceName` | String | å¦ | ç»´ä¿®äºº |
| `maintenanceTime` | String | å¦ | ç»´ä¿®æ—¶é—´ï¼Œæ ¼å¼ `yyyy-MM-dd HH:mm:ss` |
| `maintenanceResult` | String | å¦ | ç»´ä¿®ç»“果描述 |
| `sparePartsUseList` | Array | å¦ | ä½¿ç”¨å¤‡ä»¶åˆ—表 |
| `sparePartsUseList[].id` | Long | æ˜¯ | å¤‡ä»¶ID |
| `sparePartsUseList[].quantity` | Integer | æ˜¯ | ä½¿ç”¨æ•°é‡ |
#### è¯·æ±‚示例
```json
{
  "id": 10001,
  "maintenanceName": "李四",
  "maintenanceTime": "2026-06-02 10:30:00",
  "maintenanceResult": "更换轴承并试运行正常",
  "sparePartsUseList": [
    {
      "id": 501,
      "quantity": 2
    },
    {
      "id": 502,
      "quantity": 1
    }
  ]
}
```
#### è¿”回示例
```json
{
  "code": 200,
  "msg": "操作成功"
}
```
#### é”™è¯¯æç¤º
| é”™è¯¯ä¿¡æ¯ | åœºæ™¯ |
|---|---|
| `报修记录不存在` | ä¼ å…¥çš„ID无效 |
| `该报修已完结,不能重复确认维修` | çŠ¶æ€å·²ä¸ºå®Œç»“(1) |
| `该报修已提交验收审批` | çŠ¶æ€å·²ä¸ºå¾…éªŒæ”¶(3) |
| `备件 xxx æ•°é‡ä¸è¶³` | å¤‡ä»¶åº“存不足 |
---
### 3.2 éªŒæ”¶å®¡æ‰¹
- **POST** `/device/repair/acceptance`
- è¯´æ˜Žï¼šéªŒæ”¶äººå‘˜å¯¹å¾…验收单据进行审批,审批通过后状态改为 `完结(1)`
#### è¯·æ±‚参数(Body)
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| `id` | Long | æ˜¯ | æŠ¥ä¿®è®°å½•ID |
| `acceptanceName` | String | æ˜¯ | éªŒæ”¶äººå§“名 |
| `acceptanceTime` | String | æ˜¯ | éªŒæ”¶æ—¶é—´ï¼Œæ ¼å¼ `yyyy-MM-dd HH:mm:ss` |
| `acceptanceRemark` | String | æ˜¯ | éªŒæ”¶å¤‡æ³¨ |
#### è¯·æ±‚示例
```json
{
  "id": 10001,
  "acceptanceName": "王五",
  "acceptanceTime": "2026-06-02 11:00:00",
  "acceptanceRemark": "维修项核验通过,设备运行正常"
}
```
#### è¿”回示例
```json
{
  "code": 200,
  "msg": "操作成功"
}
```
#### é”™è¯¯æç¤º
| é”™è¯¯ä¿¡æ¯ | åœºæ™¯ |
|---|---|
| `报修记录id不能为空` | æœªä¼ å…¥id参数 |
| `报修记录不存在` | ä¼ å…¥çš„ID无效 |
| `该报修未进入待验收状态,不能审批` | çŠ¶æ€ä¸æ˜¯å¾…éªŒæ”¶(3) |
| `验收人不能为空` | acceptanceName为空 |
| `验收时间不能为空` | acceptanceTime为空 |
| `验收备注不能为空` | acceptanceRemark为空 |
| `验收审批失败` | æ•°æ®åº“更新失败 |
---
### 3.3 æ™®é€šæ›´æ–°æŽ¥å£é™åˆ¶
- **PUT** `/device/repair`
- è¯´æ˜Žï¼šæ™®é€šçš„æ›´æ–°æŽ¥å£ï¼Œç”¨äºŽä¿®æ”¹æŠ¥ä¿®åŸºæœ¬ä¿¡æ¯
#### é™åˆ¶è§„则
- ä¸èƒ½é€šè¿‡æ­¤æŽ¥å£ç›´æŽ¥å°†çŠ¶æ€æ”¹ä¸º `完结(1)`
- å¿…须走 `POST /device/repair/repair` -> `POST /device/repair/acceptance` æµç¨‹
#### é”™è¯¯æç¤º
| é”™è¯¯ä¿¡æ¯ | åœºæ™¯ |
|---|---|
| `请先提交验收审批,验收通过后才可完结` | å°è¯•直接设置status=1 |
---
### 3.4 æŸ¥è¯¢æŽ¥å£è¿”回字段
以下接口返回数据中包含验收相关字段:
- **GET** `/device/repair/page` - åˆ†é¡µåˆ—表
- **GET** `/device/repair/{id}` - è¯¦æƒ…
#### éªŒæ”¶ç›¸å…³è¿”回字段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| `acceptanceName` | String | éªŒæ”¶äºº |
| `acceptanceTime` | String | éªŒæ”¶æ—¶é—´ï¼Œæ ¼å¼ `yyyy-MM-dd HH:mm:ss` |
| `acceptanceRemark` | String | éªŒæ”¶å¤‡æ³¨ |
#### åˆ—表返回示例
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 10001,
        "deviceLedgerId": 100,
        "deviceName": "空压机A",
        "deviceModel": "KR-500",
        "areaId": 1,
        "areaName": "生产车间",
        "repairTime": "2026-06-01 09:00:00",
        "repairName": "张三",
        "remark": "设备运行异响",
        "machineryCategory": "动力设备",
        "maintenanceName": "李四",
        "maintenanceTime": "2026-06-02 10:30:00",
        "maintenanceResult": "更换轴承并试运行正常",
        "acceptanceName": "王五",
        "acceptanceTime": "2026-06-02 11:00:00",
        "acceptanceRemark": "维修项核验通过,设备运行正常",
        "status": 1,
        "createTime": "2026-06-01 08:30:00",
        "updateTime": "2026-06-02 11:00:00"
      }
    ],
    "total": 1,
    "size": 10,
    "current": 1
  }
}
```
---
## 4. å‰ç«¯å®žçŽ°å»ºè®®
### 4.1 åˆ—表页面
1. **状态筛选**:增加 `待验收(3)` çŠ¶æ€çš„ç­›é€‰é¡¹
2. **状态展示**:根据status值显示对应文案和颜色
| çŠ¶æ€å€¼ | æ–‡æ¡ˆ | å»ºè®®é¢œè‰² |
|---|---|---|
| 0 | å¾…ç»´ä¿® | æ©™è‰²/警告 |
| 3 | å¾…验收 | è“è‰²/信息 |
| 1 | å®Œç»“ | ç»¿è‰²/成功 |
| 2 | å¤±è´¥ | çº¢è‰²/错误 |
### 4.2 è¯¦æƒ…页面
1. **待维修状态(0)**:
   - æ˜¾ç¤º"确认维修"按钮
   - å¯ç¼–辑维修人、维修时间、维修结果
   - å¯é€‰æ‹©ä½¿ç”¨çš„备件
2. **待验收状态(3)**:
   - æ˜¾ç¤º"验收审批"按钮
   - å¼¹çª—填写:验收人、验收时间、验收备注
3. **完结状态(1)**:
   - æ˜¾ç¤ºéªŒæ”¶ä¿¡æ¯ï¼ˆéªŒæ”¶äººã€éªŒæ”¶æ—¶é—´ã€éªŒæ”¶å¤‡æ³¨ï¼‰
   - åªè¯»å±•示
### 4.3 éªŒæ”¶å®¡æ‰¹å¼¹çª—
```html
<!-- ç¤ºä¾‹è¡¨å• -->
<el-form :model="acceptanceForm" :rules="rules">
  <el-form-item label="验收人" prop="acceptanceName">
    <el-input v-model="acceptanceForm.acceptanceName" placeholder="请输入验收人" />
  </el-form-item>
  <el-form-item label="验收时间" prop="acceptanceTime">
    <el-date-picker
      v-model="acceptanceForm.acceptanceTime"
      type="datetime"
      placeholder="选择验收时间"
      format="yyyy-MM-dd HH:mm:ss"
      value-format="yyyy-MM-dd HH:mm:ss"
    />
  </el-form-item>
  <el-form-item label="验收备注" prop="acceptanceRemark">
    <el-input
      v-model="acceptanceForm.acceptanceRemark"
      type="textarea"
      placeholder="请输入验收备注"
      :rows="3"
    />
  </el-form-item>
</el-form>
```
### 4.4 è¡¨å•验证规则
```javascript
const rules = {
  acceptanceName: [
    { required: true, message: '验收人不能为空', trigger: 'blur' }
  ],
  acceptanceTime: [
    { required: true, message: '验收时间不能为空', trigger: 'change' }
  ],
  acceptanceRemark: [
    { required: true, message: '验收备注不能为空', trigger: 'blur' }
  ]
}
```
---
## 5. æ“ä½œæŒ‰é’®æŽ§åˆ¶
### 5.1 æŒ‰é’®æ˜¾ç¤ºé€»è¾‘
| çŠ¶æ€ | ç¡®è®¤ç»´ä¿® | éªŒæ”¶å®¡æ‰¹ | ç¼–辑 | åˆ é™¤ |
|---|---|---|---|---|
| å¾…ç»´ä¿®(0) | âœ… æ˜¾ç¤º | âŒ éšè— | âœ… æ˜¾ç¤º | âœ… æ˜¾ç¤º |
| å¾…验收(3) | âŒ éšè— | âœ… æ˜¾ç¤º | âœ… æ˜¾ç¤º | âœ… æ˜¾ç¤º |
| å®Œç»“(1) | âŒ éšè— | âŒ éšè— | âŒ ç¦ç”¨ | âŒ ç¦ç”¨ |
| å¤±è´¥(2) | âŒ éšè— | âŒ éšè— | âœ… æ˜¾ç¤º | âœ… æ˜¾ç¤º |
### 5.2 çŠ¶æ€æµè½¬æŽ§åˆ¶
```javascript
// ç¡®è®¤ç»´ä¿®
async function handleRepair(row) {
  const res = await post('/device/repair/repair', {
    id: row.id,
    maintenanceName: form.maintenanceName,
    maintenanceTime: form.maintenanceTime,
    maintenanceResult: form.maintenanceResult,
    sparePartsUseList: form.sparePartsUseList
  })
  if (res.code === 200) {
    // çŠ¶æ€å˜ä¸ºå¾…éªŒæ”¶(3),刷新列表
    refreshList()
  }
}
// éªŒæ”¶å®¡æ‰¹
async function handleAcceptance(row) {
  const res = await post('/device/repair/acceptance', {
    id: row.id,
    acceptanceName: form.acceptanceName,
    acceptanceTime: form.acceptanceTime,
    acceptanceRemark: form.acceptanceRemark
  })
  if (res.code === 200) {
    // çŠ¶æ€å˜ä¸ºå®Œç»“(1),刷新列表
    refreshList()
  }
}
```
---
## 6. æ•°æ®åº“字段
### device_repair è¡¨
| å­—段名 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| `acceptance_name` | VARCHAR(100) | éªŒæ”¶äºº |
| `acceptance_time` | DATETIME | éªŒæ”¶æ—¶é—´ |
| `acceptance_remark` | VARCHAR(500) | éªŒæ”¶å¤‡æ³¨ |
### SQL(已执行)
```sql
ALTER TABLE device_repair
  ADD COLUMN acceptance_name VARCHAR(100) NULL COMMENT '验收人',
  ADD COLUMN acceptance_time DATETIME NULL COMMENT '验收时间',
  ADD COLUMN acceptance_remark VARCHAR(500) NULL COMMENT '验收备注';
```
---
## 7. è”调检查清单
- [ ] å¾…维修状态的报修单,点击"确认维修"后状态变为待验收(3)
- [ ] å¾…验收状态的报修单,点击"验收审批"后状态变为完结(1)
- [ ] éªŒæ”¶å®¡æ‰¹æ—¶ï¼ŒéªŒæ”¶äºº/验收时间/验收备注为空,后端返回对应错误提示
- [ ] å°è¯•通过普通编辑接口直接将状态改为完结,后端返回拦截提示
- [ ] åˆ—表/详情接口正确返回验收相关字段
- [ ] å®Œç»“状态的报修单,详情页展示验收信息
doc/ǰ¶ËÁªµ÷Îĵµ-¹ý³Ì¼ìÑéÄ£°åµ¼³ö¹¦ÄÜ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,275 @@
# å‰ç«¯è”调文档 - è¿‡ç¨‹æ£€éªŒæ¨¡æ¿å¯¼å‡ºåŠŸèƒ½
## 1. åŠŸèƒ½æ¦‚è¿°
过程检验模块支持三种客户模板的检验结果导出:
- **伟龙模板** - ä¼Ÿé¾™å®¢æˆ·ä¸“用检验报告格式
- **百事模板** - ç™¾äº‹å®¢æˆ·ä¸“用检验报告格式
- **达利模板** - è¾¾åˆ©å®¢æˆ·ä¸“用检验报告格式
导出功能会自动填充模板中的"结果"列,根据检验参数中的"项目列"进行匹配。
---
## 2. æŽ¥å£æ¸…单
### 2.1 å¯¼å‡ºä¼Ÿé¾™æ¨¡æ¿æ£€éªŒç»“æžœ
- **GET** `/quality/qualityInspect/export/weilong/{id}`
- è¯´æ˜Žï¼šå¯¼å‡ºæŒ‡å®šè¿‡ç¨‹æ£€éªŒå•的伟龙格式检验报告
#### è¯·æ±‚参数
| å‚数名 | ç±»åž‹ | ä½ç½® | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|------|
| `id` | Long | Path | æ˜¯ | è¿‡ç¨‹æ£€éªŒå•ID |
#### è¯·æ±‚示例
```
GET /quality/qualityInspect/export/weilong/123
```
#### è¿”回结果
返回Word文档文件流,文件名:`伟龙模版检验结果.doc`
---
### 2.2 å¯¼å‡ºç™¾äº‹æ¨¡æ¿æ£€éªŒç»“æžœ
- **GET** `/quality/qualityInspect/export/baishi/{id}`
- è¯´æ˜Žï¼šå¯¼å‡ºæŒ‡å®šè¿‡ç¨‹æ£€éªŒå•的百事格式检验报告
#### è¯·æ±‚参数
| å‚数名 | ç±»åž‹ | ä½ç½® | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|------|
| `id` | Long | Path | æ˜¯ | è¿‡ç¨‹æ£€éªŒå•ID |
#### è¯·æ±‚示例
```
GET /quality/qualityInspect/export/baishi/123
```
#### è¿”回结果
返回Word文档文件流,文件名:`百事模版检验结果.doc`
---
### 2.3 å¯¼å‡ºè¾¾åˆ©æ¨¡æ¿æ£€éªŒç»“æžœ
- **GET** `/quality/qualityInspect/export/dali/{id}`
- è¯´æ˜Žï¼šå¯¼å‡ºæŒ‡å®šè¿‡ç¨‹æ£€éªŒå•的达利格式检验报告
#### è¯·æ±‚参数
| å‚数名 | ç±»åž‹ | ä½ç½® | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|------|
| `id` | Long | Path | æ˜¯ | è¿‡ç¨‹æ£€éªŒå•ID |
#### è¯·æ±‚示例
```
GET /quality/qualityInspect/export/dali/123
```
#### è¿”回结果
返回Word文档文件流,文件名:`达利模版检验结果.doc`
---
## 3. æ•°æ®åŒ¹é…è§„则
### 3.1 æ¨¡æ¿å¡«å……逻辑
导出时系统会自动匹配检验参数与模板中的检验项目:
1. **项目匹配**:根据检验参数表(`quality_inspect_param`)中的`parameterItem`(检验项目)字段与模板表格左侧的项目列进行匹配
2. **结果填充**:匹配成功后,将`testValue`(检验值)填充到模板对应行的"结果"列
3. **检验结论**:特殊行(如"质量评定"、"检验结果"、"检验结论")会填充检验单的`checkResult`字段
### 3.2 åŒ¹é…ä¼˜å…ˆçº§
| ä¼˜å…ˆçº§ | åŒ¹é…è§„则 | è¯´æ˜Ž |
|--------|----------|------|
| 1 | å®Œå…¨åŒ¹é… | é¡¹ç›®åç§°å®Œå…¨ä¸€è‡´ |
| 2 | åŒ…含匹配 | é¡¹ç›®åç§°åŒ…含关系 |
| 3 | ç»„合匹配 | ç¬¬ä¸€åˆ—+第二列组合匹配 |
| 4 | åˆ†ç»„匹配 | åˆ†ç»„标签+项目名称组合匹配 |
### 3.3 ç‰¹æ®Šå¤„理
以下行会自动填充检验结论(`checkResult`):
- è´¨é‡è¯„定
- æ£€éªŒç»“æžœ
- æ£€éªŒç»“论
- Grade estimation
- Test Results
---
## 4. å‰ç«¯å®žçŽ°ç¤ºä¾‹
### 4.1 Vue + Axios å¯¼å‡ºç¤ºä¾‹
```javascript
// å¯¼å‡ºä¼Ÿé¾™æ¨¡æ¿
exportWeiLong(row) {
  const id = row.id;
  axios({
    method: 'get',
    url: `/quality/qualityInspect/export/weilong/${id}`,
    responseType: 'blob'
  }).then(response => {
    const blob = new Blob([response.data], { type: 'application/msword' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = '伟龙模版检验结果.doc';
    link.click();
    URL.revokeObjectURL(link.href);
  }).catch(error => {
    console.error('导出失败', error);
  });
}
// å¯¼å‡ºç™¾äº‹æ¨¡æ¿
exportBaiShi(row) {
  const id = row.id;
  axios({
    method: 'get',
    url: `/quality/qualityInspect/export/baishi/${id}`,
    responseType: 'blob'
  }).then(response => {
    const blob = new Blob([response.data], { type: 'application/msword' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = '百事模版检验结果.doc';
    link.click();
    URL.revokeObjectURL(link.href);
  }).catch(error => {
    console.error('导出失败', error);
  });
}
// å¯¼å‡ºè¾¾åˆ©æ¨¡æ¿
exportDaLi(row) {
  const id = row.id;
  axios({
    method: 'get',
    url: `/quality/qualityInspect/export/dali/${id}`,
    responseType: 'blob'
  }).then(response => {
    const blob = new Blob([response.data], { type: 'application/msword' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = '达利模版检验结果.doc';
    link.click();
    URL.revokeObjectURL(link.href);
  }).catch(error => {
    console.error('导出失败', error);
  });
}
```
### 4.2 æŒ‰é’®é…ç½®ç¤ºä¾‹
```html
<el-table-column label="操作" width="200">
  <template #default="{ row }">
    <!-- åªæœ‰è¿‡ç¨‹æ£€éªŒ(inspectType=1)才显示模板导出按钮 -->
    <el-button v-if="row.inspectType === 1" size="small" @click="exportWeiLong(row)">
      ä¼Ÿé¾™æ¨¡æ¿
    </el-button>
    <el-button v-if="row.inspectType === 1" size="small" @click="exportBaiShi(row)">
      ç™¾äº‹æ¨¡æ¿
    </el-button>
    <el-button v-if="row.inspectType === 1" size="small" @click="exportDaLi(row)">
      è¾¾åˆ©æ¨¡æ¿
    </el-button>
  </template>
</el-table-column>
```
---
## 5. é”™è¯¯å¤„理
### 5.1 é”™è¯¯å“åº”
| é”™è¯¯ä¿¡æ¯ | åœºæ™¯ |
|----------|------|
| `检验单ID不能为空` | æœªä¼ å…¥id参数 |
| `检验单不存在` | ä¼ å…¥çš„ID无效 |
| `模板文件不存在` | æ¨¡æ¿æ–‡ä»¶ç¼ºå¤± |
| `导出失败` | æ–‡ä»¶å¤„理异常 |
### 5.2 å‰ç«¯é”™è¯¯å¤„理
```javascript
.catch(error => {
  if (error.response) {
    // è¯»å–错误信息
    const reader = new FileReader();
    reader.onload = () => {
      const message = JSON.parse(reader.result).msg || '导出失败';
      this.$message.error(message);
    };
    reader.readAsText(error.response.data);
  } else {
    this.$message.error('导出失败,请稍后重试');
  }
});
```
---
## 6. ä¸šåŠ¡è¯´æ˜Ž
### 6.1 æ£€éªŒç±»åž‹åŒºåˆ†
| inspectType | ç±»åž‹ | è¯´æ˜Ž |
|-------------|------|------|
| 0 | åŽŸææ–™æ£€éªŒ | è¿›æ–™æ£€éªŒ |
| 1 | è¿‡ç¨‹æ£€éªŒ | ç”Ÿäº§è¿‡ç¨‹æ£€éªŒï¼ˆæ”¯æŒæ¨¡æ¿å¯¼å‡ºï¼‰ |
| 2 | å‡ºåŽ‚æ£€éªŒ | æˆå“å‡ºåŽ‚æ£€éªŒ |
### 6.2 æ¨¡æ¿å¯¼å‡ºé€‚用范围
模板导出功能主要用于**过程检验**(`inspectType=1`),前端应根据检验类型判断是否显示模板导出按钮。
### 6.3 æ£€éªŒå‚数数据结构
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| `parameterItem` | String | æ£€éªŒé¡¹ç›®åç§°ï¼ˆç”¨äºŽåŒ¹é…æ¨¡æ¿ï¼‰ |
| `testValue` | String | æ£€éªŒå€¼ï¼ˆå¡«å……到结果列) |
| `standardValue` | String | æ ‡å‡†å€¼ |
| `controlValue` | String | å†…控值 |
| `unit` | String | å•位 |
---
## 7. è”调检查清单
- [ ] è¿‡ç¨‹æ£€éªŒå•导出伟龙模板成功,文件可正常打开
- [ ] è¿‡ç¨‹æ£€éªŒå•导出百事模板成功,文件可正常打开
- [ ] è¿‡ç¨‹æ£€éªŒå•导出达利模板成功,文件可正常打开
- [ ] å¯¼å‡ºæ–‡ä»¶ä¸­æ£€éªŒç»“果列正确填充
- [ ] æ£€éªŒç»“论行正确显示`checkResult`值
- [ ] ä¼ å…¥ä¸å­˜åœ¨çš„ID时返回错误提示
- [ ] åŽŸææ–™æ£€éªŒ/出厂检验不显示模板导出按钮
- [ ] æ–‡ä»¶åç¼–码正确,中文无乱码
---
## 8. æ¨¡æ¿æ–‡ä»¶ä½ç½®
| æ¨¡æ¿ | è·¯å¾„ |
|------|------|
| ä¼Ÿé¾™æ¨¡ç‰ˆ.doc | `/static/伟龙模版.doc` |
| ç™¾äº‹æ¨¡ç‰ˆ.doc | `/static/百事模版.doc` |
| è¾¾åˆ©æ¨¡ç‰ˆ.doc | `/static/达利模版.doc` |
docs/maintenance_task_api.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,317 @@
# è®¾å¤‡ä¿å…»ä»»åŠ¡æŽ¥å£æ–‡æ¡£
## æ¦‚è¿°
设备保养任务模块用于管理设备的定时保养计划,支持多种频率类型(每日、每周、每月、每季度、每年),并可启用或禁用任务。
---
## æŽ¥å£åˆ—表
### 1. åˆ†é¡µæŸ¥è¯¢ä¿å…»ä»»åŠ¡åˆ—è¡¨
**请求地址**: `GET /deviceMaintenanceTask/listPage`
**请求参数**:
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| current | Long | å¦ | å½“前页码,默认1 |
| size | Long | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
| taskName | String | å¦ | ä»»åŠ¡åç§°ï¼ˆæ¨¡ç³ŠæŸ¥è¯¢ï¼‰ |
| areaId | Long | å¦ | è®¾å¤‡åŒºåŸŸID |
| isEnabled | Integer | å¦ | æ˜¯å¦å¯ç”¨ï¼ˆ1=启用,0=禁用) |
**响应示例**:
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 1,
        "taskName": "设备A,设备B",
        "taskId": 100,
        "areaId": 10,
        "areaName": "生产车间",
        "deviceModel": "型号A,型号B",
        "deviceLedgerIdsStr": "100,101",
        "frequencyType": "WEEKLY",
        "frequencyDetail": "MON,14:30",
        "nextExecutionTime": "2026-06-08T14:30:00",
        "lastExecutionTime": "2026-06-01T14:30:00",
        "isEnabled": 1,
        "remarks": "备注信息",
        "registrantId": 1,
        "registrant": "张三",
        "registrationDate": "2026-06-01",
        "createTime": "2026-06-01T10:00:00",
        "updateTime": "2026-06-01T10:00:00"
      }
    ],
    "total": 100,
    "current": 1,
    "size": 10
  }
}
```
---
### 2. æ–°å¢žä¿å…»ä»»åŠ¡
**请求地址**: `POST /deviceMaintenanceTask/add`
**请求头**: `Content-Type: application/json`
**请求参数**:
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| taskName | String | å¦ | ä»»åŠ¡åç§°ï¼ˆå¦‚ä¸ä¼ åˆ™æ ¹æ®è®¾å¤‡åç§°è‡ªåŠ¨ç”Ÿæˆï¼‰ |
| taskId | Long | å¦ | è®¾å¤‡ID(单个设备时使用) |
| deviceLedgerIds | Long[] | å¦ | è®¾å¤‡ID数组(多个设备时使用) |
| deviceLedgerIdsStr | String | å¦ | è®¾å¤‡ID字符串,逗号分隔 |
| areaId | Long | å¦ | è®¾å¤‡åŒºåŸŸID |
| frequencyType | String | æ˜¯ | é¢‘次类型,见下方说明 |
| frequencyDetail | String | æ˜¯ | é¢‘次详情,格式见下方说明 |
| isEnabled | Integer | å¦ | æ˜¯å¦å¯ç”¨ï¼ˆ1=启用,0=禁用),默认1 |
| remarks | String | å¦ | å¤‡æ³¨ |
**响应示例**:
```json
{
  "code": 200,
  "msg": "添加成功"
}
```
---
### 3. æ›´æ–°ä¿å…»ä»»åŠ¡
**请求地址**: `POST /deviceMaintenanceTask/update`
**请求头**: `Content-Type: application/json`
**请求参数**: åŒæ–°å¢žæŽ¥å£ï¼Œéœ€é¢å¤–ä¼ å…¥ `id` å­—段
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| id | Long | æ˜¯ | ä»»åŠ¡ID |
| ... | ... | ... | å…¶ä»–字段同新增接口 |
**响应示例**:
```json
{
  "code": 200,
  "msg": "更新成功"
}
```
---
### 4. å¯ç”¨/禁用保养任务
**请求地址**: `POST /deviceMaintenanceTask/changeEnable`
**请求头**: `Content-Type: application/json`
**请求参数**:
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| id | Long | æ˜¯ | ä»»åŠ¡ID |
| isEnabled | Integer | æ˜¯ | æ˜¯å¦å¯ç”¨ï¼ˆ1=启用,0=禁用) |
**响应示例**:
```json
{
  "code": 200,
  "msg": "更新成功"
}
```
**业务逻辑说明**:
- å¯ç”¨ä»»åŠ¡æ—¶ä¼šè‡ªåŠ¨æ¢å¤å®šæ—¶è°ƒåº¦
- ç¦ç”¨ä»»åŠ¡æ—¶ä¼šæš‚åœå®šæ—¶è°ƒåº¦
- å¦‚果任务没有下次执行时间,会自动计算
---
### 5. åˆ é™¤ä¿å…»ä»»åŠ¡
**请求地址**: `DELETE /deviceMaintenanceTask/delete`
**请求头**: `Content-Type: application/json`
**请求参数**:
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| - | Long[] | æ˜¯ | ä»»åŠ¡ID数组(请求体直接传数组) |
**请求示例**:
```json
[1, 2, 3]
```
**响应示例**:
```json
{
  "code": 200,
  "msg": "删除成功"
}
```
---
## é¢‘率类型说明
### frequencyType å¯é€‰å€¼
| å€¼ | è¯´æ˜Ž | frequencyDetail æ ¼å¼ |
|----|------|---------------------|
| DAILY | æ¯æ—¥ | `HH:mm` |
| WEEKLY | æ¯å‘¨ | `星期,HH:mm` |
| MONTHLY | æ¯æœˆ | `日,HH:mm` |
| QUARTERLY | æ¯å­£åº¦ | `起始月份,日,HH:mm` |
| YEARLY | æ¯å¹´ | `月份,日,HH:mm` |
### frequencyDetail æ ¼å¼è¯¦è§£
#### 1. æ¯æ—¥ï¼ˆDAILY)
格式:`HH:mm`
示例:
- `14:30` - æ¯å¤© 14:30 æ‰§è¡Œ
#### 2. æ¯å‘¨ï¼ˆWEEKLY)
格式:`星期,HH:mm`
星期可选值:`MON`, `TUE`, `WED`, `THU`, `FRI`, `SAT`, `SUN`
示例:
- `MON,14:30` - æ¯å‘¨ä¸€ 14:30 æ‰§è¡Œ
- `FRI,09:00` - æ¯å‘¨äº” 09:00 æ‰§è¡Œ
#### 3. æ¯æœˆï¼ˆMONTHLY)
格式:`日,HH:mm`
示例:
- `15,14:30` - æ¯æœˆ15日 14:30 æ‰§è¡Œ
- `01,09:00` - æ¯æœˆ1日 09:00 æ‰§è¡Œ
- `31,18:00` - æ¯æœˆæœ€åŽä¸€å¤©ï¼ˆå¦‚月份不足31天则取最后一天)
#### 4. æ¯å­£åº¦ï¼ˆQUARTERLY)
格式:`起始月份,日,HH:mm`
说明:从指定月份开始,每隔3个月执行一次
示例:
- `1,15,14:30` - 1月、4月、7月、10月的15日 14:30 æ‰§è¡Œ
- `3,10,09:00` - 3月、6月、9月、12月的10日 09:00 æ‰§è¡Œ
#### 5. æ¯å¹´ï¼ˆYEARLY)
格式:`月份,日,HH:mm`
示例:
- `6,15,14:30` - æ¯å¹´6月15日 14:30 æ‰§è¡Œ
- `12,31,23:59` - æ¯å¹´12月31日 23:59 æ‰§è¡Œ
---
## å‰ç«¯ä½¿ç”¨ç¤ºä¾‹
### æ–°å¢žæ¯å‘¨ä¿å…»ä»»åŠ¡
```javascript
const task = {
  deviceLedgerIds: [100, 101],  // è®¾å¤‡ID数组
  areaId: 10,                   // åŒºåŸŸID
  frequencyType: 'WEEKLY',      // æ¯å‘¨
  frequencyDetail: 'MON,14:30', // æ¯å‘¨ä¸€14:30
  isEnabled: 1,                 // å¯ç”¨
  remarks: '每周例行保养'
};
fetch('/deviceMaintenanceTask/add', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(task)
});
```
### å¯ç”¨/禁用任务
```javascript
// å¯ç”¨ä»»åŠ¡
fetch('/deviceMaintenanceTask/changeEnable', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: 1, isEnabled: 1 })
});
// ç¦ç”¨ä»»åŠ¡
fetch('/deviceMaintenanceTask/changeEnable', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: 1, isEnabled: 0 })
});
```
---
## æ•°æ®å­—å…¸
### isEnabled å­—段
| å€¼ | è¯´æ˜Ž |
|----|------|
| 0 | ç¦ç”¨ |
| 1 | å¯ç”¨ |
### è¿”回字段说明
| å­—段名 | ç±»åž‹ | è¯´æ˜Ž |
|--------|------|------|
| id | Long | ä»»åŠ¡ID |
| taskName | String | ä»»åŠ¡åç§°ï¼ˆå¤šä¸ªè®¾å¤‡ç”¨é€—å·åˆ†éš”ï¼‰ |
| taskId | Long | ä¸»è®¾å¤‡ID |
| areaId | Long | è®¾å¤‡åŒºåŸŸID |
| areaName | String | è®¾å¤‡åŒºåŸŸåç§° |
| deviceModel | String | è®¾å¤‡åž‹å·ï¼ˆå¤šä¸ªç”¨é€—号分隔) |
| deviceLedgerIdsStr | String | è®¾å¤‡ID字符串(逗号分隔) |
| frequencyType | String | é¢‘次类型 |
| frequencyDetail | String | é¢‘次详情 |
| nextExecutionTime | DateTime | ä¸‹æ¬¡æ‰§è¡Œæ—¶é—´ |
| lastExecutionTime | DateTime | æœ€åŽæ‰§è¡Œæ—¶é—´ |
| isEnabled | Integer | æ˜¯å¦å¯ç”¨ |
| remarks | String | å¤‡æ³¨ |
| registrantId | Long | å½•入人ID |
| registrant | String | å½•入人姓名 |
| registrationDate | Date | å½•入日期 |
| createTime | DateTime | åˆ›å»ºæ—¶é—´ |
| updateTime | DateTime | æ›´æ–°æ—¶é—´ |
---
## æ³¨æ„äº‹é¡¹
1. **设备选择**:可以传入 `deviceLedgerIds` æ•°ç»„或 `deviceLedgerIdsStr` å­—符串指定多个设备
2. **任务名称**:如不传 `taskName`,系统会根据设备名称自动生成
3. **下次执行时间**:新增或更新时会自动计算下次执行时间
4. **启用/禁用**:禁用任务不会删除定时器,只是暂停执行;启用后会恢复
5. **删除任务**:删除任务会同时移除相关的定时调度
pom.xml
@@ -326,6 +326,13 @@
            <version>${poi.version}</version>
        </dependency>
        <!-- word doc格式处理(HWPF) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>${poi.version}</version>
        </dependency>
        <!-- velocity代码生成使用模板 -->
        <dependency>
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
@@ -57,9 +57,15 @@
    }
    @PostMapping ("maintenance")
    @Operation(summary = "修改设备保养")
    @Operation(summary = "确认设备保养")
    public AjaxResult maintenance(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
        return deviceMaintenanceService.updateDeviceDeviceMaintenance(deviceMaintenance);
        return deviceMaintenanceService.confirmMaintenance(deviceMaintenance);
    }
    @PostMapping ("/acceptance")
    @Operation(summary = "设备保养验收审批")
    public AjaxResult acceptance(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
        return deviceMaintenanceService.approveMaintenanceAcceptance(deviceMaintenance);
    }
src/main/java/com/ruoyi/device/controller/MaintenanceTaskController.java
@@ -57,5 +57,12 @@
        return maintenanceTaskService.delete(ids);
    }
    @PostMapping("/changeEnable")
    @Operation(summary = "启用/禁用设备保养定时任务")
    @Log(title = "设备保养定时任务", businessType = BusinessType.UPDATE)
    public AjaxResult changeEnable(@RequestBody MaintenanceTask maintenanceTask) {
        return maintenanceTaskService.changeEnable(maintenanceTask.getId(), maintenanceTask.getIsEnabled());
    }
}
src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
@@ -54,7 +54,7 @@
    private String createUserName;
    @Schema(description = "保养图片")
    private List<StorageBlobDTO> storageBlobDTOs;
    private List<StorageBlobDTO> storageBlobDTOs = new java.util.ArrayList<>();
}
src/main/java/com/ruoyi/device/dto/DeviceRepairDto.java
@@ -5,6 +5,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@@ -16,5 +17,5 @@
    @Schema(description = "维修时间字符串")
    private String maintenanceTimeStr;
    private List<StorageBlobDTO> storageBlobDTOs;
    private List<StorageBlobDTO> storageBlobDTOs = new ArrayList<>();
}
src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java
@@ -64,9 +64,20 @@
    @Schema(description = "保养结果 0 ç»´ä¿® 1 å®Œå¥½")
    private String maintenanceResult;
    @Schema(description = "状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥")
    @Schema(description = "状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥ 3 å¾…验收")
    private Integer status;
    @Schema(description = "验收人")
    private String acceptanceName;
    @Schema(description = "验收时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime acceptanceTime;
    @Schema(description = "验收备注")
    private String acceptanceRemark;
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
@@ -68,6 +68,9 @@
    @Schema(description = "是否激活")
    private boolean isActive;
    @Schema(description = "是否启用 0-禁用 1-启用")
    private Integer isEnabled;
    @Schema(description = "备注")
    @Excel(name = "备注")
    private String remarks;
src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java
@@ -18,6 +18,10 @@
    AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenanceDto deviceMaintenance);
    AjaxResult confirmMaintenance(DeviceMaintenanceDto deviceMaintenanceDto);
    AjaxResult approveMaintenanceAcceptance(DeviceMaintenanceDto deviceMaintenanceDto);
    void export(HttpServletResponse response, Long[] ids);
    DeviceMaintenanceVo detailById(Long id);
src/main/java/com/ruoyi/device/service/MaintenanceTaskService.java
@@ -19,4 +19,6 @@
    AjaxResult updateByMaintenanceTaskId(MaintenanceTask maintenanceTask);
    AjaxResult delete(List<Long> ids);
    AjaxResult changeEnable(Long id, Integer isEnabled);
}
src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
@@ -45,6 +45,11 @@
    private final FileUtil fileUtil;
    private final ISysNoticeService sysNoticeService;
    private static final int STATUS_PENDING_MAINTENANCE = 0;
    private static final int STATUS_COMPLETED = 1;
    private static final int STATUS_FAILED = 2;
    private static final int STATUS_PENDING_ACCEPTANCE = 3;
    @Override
    public IPage<DeviceMaintenanceDto> queryPage(Page page, DeviceMaintenanceDto deviceMaintenanceDto) {
@@ -81,6 +86,12 @@
            deviceMaintenance.setDeviceName(byId.getDeviceName());
            deviceMaintenance.setDeviceModel(byId.getDeviceModel());
            deviceMaintenance.setAreaId(byId.getAreaId());
        }
        if (deviceMaintenance.getStatus() != null
                && deviceMaintenance.getStatus() == STATUS_COMPLETED
                && (oldDeviceMaintenance.getStatus() == null
                || oldDeviceMaintenance.getStatus() != STATUS_COMPLETED)) {
            return AjaxResult.error("请先提交验收审批,验收通过后才可完结");
        }
        // å¤„理备件使用情况
        if (com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(deviceMaintenance.getSparePartsUseList())) {
@@ -156,4 +167,56 @@
        vo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.DEVICE_MAINTENANCE, id));
        return vo;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult confirmMaintenance(DeviceMaintenanceDto deviceMaintenanceDto) {
        DeviceMaintenance oldDeviceMaintenance = this.getById(deviceMaintenanceDto.getId());
        if (oldDeviceMaintenance == null) {
            return AjaxResult.error("保养记录不存在");
        }
        if (oldDeviceMaintenance.getStatus() != null && oldDeviceMaintenance.getStatus() == STATUS_COMPLETED) {
            return AjaxResult.error("该保养已完结,不能重复确认保养");
        }
        if (oldDeviceMaintenance.getStatus() != null && oldDeviceMaintenance.getStatus() == STATUS_PENDING_ACCEPTANCE) {
            return AjaxResult.error("该保养已提交验收审批");
        }
        deviceMaintenanceDto.setStatus(STATUS_PENDING_ACCEPTANCE);
        return updateDeviceDeviceMaintenance(deviceMaintenanceDto);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult approveMaintenanceAcceptance(DeviceMaintenanceDto deviceMaintenanceDto) {
        if (deviceMaintenanceDto.getId() == null) {
            return AjaxResult.error("保养记录id不能为空");
        }
        DeviceMaintenance oldDeviceMaintenance = this.getById(deviceMaintenanceDto.getId());
        if (oldDeviceMaintenance == null) {
            return AjaxResult.error("保养记录不存在");
        }
        if (oldDeviceMaintenance.getStatus() == null || oldDeviceMaintenance.getStatus() != STATUS_PENDING_ACCEPTANCE) {
            return AjaxResult.error("该保养未进入待验收状态,不能审批");
        }
        if (StringUtils.isBlank(deviceMaintenanceDto.getAcceptanceName())) {
            return AjaxResult.error("验收人不能为空");
        }
        if (deviceMaintenanceDto.getAcceptanceTime() == null) {
            return AjaxResult.error("验收时间不能为空");
        }
        if (StringUtils.isBlank(deviceMaintenanceDto.getAcceptanceRemark())) {
            return AjaxResult.error("验收备注不能为空");
        }
        DeviceMaintenance update = new DeviceMaintenance();
        update.setId(deviceMaintenanceDto.getId());
        update.setAcceptanceName(deviceMaintenanceDto.getAcceptanceName());
        update.setAcceptanceTime(deviceMaintenanceDto.getAcceptanceTime());
        update.setAcceptanceRemark(deviceMaintenanceDto.getAcceptanceRemark());
        update.setStatus(STATUS_COMPLETED);
        if (this.updateById(update)) {
            return AjaxResult.success();
        }
        return AjaxResult.error("验收审批失败");
    }
}
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskScheduler.java
@@ -160,6 +160,8 @@
                return convertMonthlyToCron(task.getFrequencyDetail());
            case "QUARTERLY":
                return convertQuarterlyToCron(task.getFrequencyDetail());
            case "YEARLY":
                return convertYearlyToCron(task.getFrequencyDetail());
            default:
                throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
        }
@@ -204,6 +206,22 @@
                quarterStartMonth);
    }
    // æ¯å¹´ä»»åŠ¡è½¬æ¢
    private String convertYearlyToCron(String frequencyDetail) {
        String[] parts = validateAndSplit(frequencyDetail, ",", 3);
        int month = validateMonth(parts[0]);  // éªŒè¯æœˆä»½(1-12)
        int day = validateDayOfMonth(parts[1]);  // éªŒè¯æ—¥æœŸ
        LocalTime time = parseTime(parts[2]);  // è§£æžæ—¶é—´
        // Cron表达式: ç§’ åˆ† æ—¶ æ—¥ æœˆ å‘¨
        // æ¯å¹´æŒ‡å®šæœˆä»½çš„æŒ‡å®šæ—¥æ‰§è¡Œ
        return String.format("0 %d %d %d %d ?",
                time.getMinute(),
                time.getHour(),
                day,
                month);
    }
    // æ–°å¢žéªŒè¯æœˆä»½çš„æ–¹æ³•(1-12)
    private int validateMonth(String monthStr) {
        try {
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
@@ -147,4 +147,47 @@
        }
        return AjaxResult.success("删除成功");
    }
    @Override
    public AjaxResult changeEnable(Long id, Integer isEnabled) {
        MaintenanceTask oldTask = maintenanceTaskMapper.selectById(id);
        if (oldTask == null) {
            return AjaxResult.error("保养任务不存在");
        }
        MaintenanceTask update = new MaintenanceTask();
        update.setId(id);
        update.setIsEnabled(isEnabled);
        update.setActive(isEnabled != null && isEnabled == 1);
        int result = maintenanceTaskMapper.updateById(update);
        if (result > 0) {
            try {
                if (isEnabled != null && isEnabled == 1) {
                    // å¯ç”¨ï¼šæ¢å¤æˆ–重新调度
                    if (oldTask.getIsEnabled() != null && oldTask.getIsEnabled() == 0) {
                        // ä»Žç¦ç”¨æ”¹ä¸ºå¯ç”¨ï¼Œé‡æ–°è®¡ç®—下次执行时间(如果没有)
                        if (oldTask.getNextExecutionTime() == null || oldTask.getNextExecutionTime().isBefore(LocalDateTime.now())) {
                            TimingTask tempTask = new TimingTask();
                            tempTask.setFrequencyType(oldTask.getFrequencyType());
                            tempTask.setFrequencyDetail(oldTask.getFrequencyDetail());
                            LocalDateTime nextTime = timingTaskService.calculateFirstExecutionTime(tempTask);
                            update.setNextExecutionTime(nextTime);
                            maintenanceTaskMapper.updateById(update);
                        }
                        maintenanceTaskScheduler.scheduleMaintenanceTask(oldTask);
                    } else {
                        maintenanceTaskScheduler.resumeMaintenanceTask(id);
                    }
                } else {
                    // ç¦ç”¨ï¼šæš‚停调度
                    maintenanceTaskScheduler.pauseMaintenanceTask(id);
                }
            } catch (Exception e) {
                log.error("调整保养任务调度状态失败", e);
            }
            return AjaxResult.success("更新成功");
        }
        return AjaxResult.error("更新失败");
    }
}
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduleUtils.java
@@ -33,6 +33,8 @@
                return calculateMonthlyNextTime(frequencyDetail, currentTime);
            case "QUARTERLY":
                return calculateQuarterlyNextTime(frequencyDetail, currentTime);
            case "YEARLY":
                return calculateYearlyNextTime(frequencyDetail, currentTime);
            default:
                throw new IllegalArgumentException("不支持的频率类型: " + frequencyType);
        }
@@ -105,6 +107,30 @@
        throw new IllegalArgumentException("无法找到下一次执行时间");
    }
    private static LocalDateTime calculateYearlyNextTime(String detail, LocalDateTime current) {
        String[] parts = validateAndSplit(detail, ",", 3);
        int month = validateMonth(parts[0]);
        int dayOfMonth = validateDayOfMonth(parts[1]);
        LocalTime time = parseTime(parts[2]);
        for (int i = 0; i < 5; i++) {
            int year = current.getYear() + i;
            YearMonth targetYearMonth = YearMonth.of(year, month);
            int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
            LocalDateTime target = LocalDateTime.of(
                    year,
                    month,
                    adjustedDay,
                    time.getHour(),
                    time.getMinute()
            );
            if (target.isAfter(current)) {
                return target;
            }
        }
        throw new IllegalArgumentException("无法找到下一次执行时间");
    }
    private static LocalTime parseTime(String timeStr) {
        try {
            return LocalTime.parse(timeStr, TIME_FORMATTER);
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -247,8 +247,11 @@
            // å¦‚果是每月执行,计算下个月的具体日期
            return calculateMonthlyFirstExecution(task.getFrequencyDetail());
        } else if ("QUARTERLY".equals(frequencyType)) {
            // è‡ªå®šä¹‰é¢‘率,如每小时、每30分钟等
            return calculateCustomFirstExecution(task.getFrequencyDetail());
            // æ¯å­£åº¦æ‰§è¡Œ
            return TimingTaskScheduleUtils.calculateFirstExecutionTime("QUARTERLY", task.getFrequencyDetail());
        } else if ("YEARLY".equals(frequencyType)) {
            // æ¯å¹´æ‰§è¡Œ
            return TimingTaskScheduleUtils.calculateFirstExecutionTime("YEARLY", task.getFrequencyDetail());
        } else {
            throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
        }
@@ -409,7 +412,9 @@
                case "MONTHLY":
                    return calculateMonthlyNextTime(frequencyDetail, currentTime);
                case "QUARTERLY":
                    return calculateQuarterlyNextTime(frequencyDetail, currentTime);
                    return TimingTaskScheduleUtils.calculateNextExecutionTime("QUARTERLY", frequencyDetail, currentTime);
                case "YEARLY":
                    return TimingTaskScheduleUtils.calculateNextExecutionTime("YEARLY", frequencyDetail, currentTime);
                default:
                    throw new IllegalArgumentException("不支持的频率类型: " + frequencyType);
            }
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java
@@ -130,12 +130,33 @@
        qualityInspectService.qualityInspectExport(response, qualityInspect);
    }
    @GetMapping("/export/weilong/{id}")
    @Operation(summary = "导出伟龙模版检验结果")
    @Log(title = "导出伟龙模版检验结果", businessType = BusinessType.EXPORT)
    public void exportWeiLong(HttpServletResponse response, @PathVariable("id") Long id) {
        qualityInspectService.exportWeiLong(response, id);
    }
    /**
     * æäº¤
     *
     * @param qualityInspect
     * @return
     */
    @GetMapping("/export/baishi/{id}")
    @Operation(summary = "导出百事模版检验结果")
    @Log(title = "导出百事模版检验结果", businessType = BusinessType.EXPORT)
    public void exportBaiShi(HttpServletResponse response, @PathVariable("id") Long id) {
        qualityInspectService.exportBaiShi(response, id);
    }
    @GetMapping("/export/dali/{id}")
    @Operation(summary = "导出达利模版检验结果")
    @Log(title = "导出达利模版检验结果", businessType = BusinessType.EXPORT)
    public void exportDaLi(HttpServletResponse response, @PathVariable("id") Long id) {
        qualityInspectService.exportDaLi(response, id);
    }
    @PostMapping("/submit")
    @Operation(summary = "提交检验")
    @Log(title = "提交检验", businessType = BusinessType.OTHER)
@@ -165,4 +186,14 @@
    public void down(HttpServletResponse response, @RequestBody QualityInspect qualityInspect) {
        qualityInspectService.down(response, qualityInspect);
    }
    /**
     * è°ƒè¯•接口:分析模板结构
     */
    @GetMapping("/analyzeTemplate")
    @Operation(summary = "分析模板结构(调试)")
    public R<?> analyzeTemplate(@RequestParam String template) {
        String templatePath = "/static/" + template + ".doc";
        return R.ok(qualityInspectService.analyzeTemplate(templatePath));
    }
}
src/main/java/com/ruoyi/quality/service/IQualityInspectService.java
@@ -21,6 +21,12 @@
    void qualityInspectExport(HttpServletResponse response, QualityInspect qualityInspect);
    void exportWeiLong(HttpServletResponse response, Long id);
    void exportBaiShi(HttpServletResponse response, Long id);
    void exportDaLi(HttpServletResponse response, Long id);
    QualityInspectDto getDetailById(Integer id);
    int submit(QualityInspect qualityInspect);
@@ -33,4 +39,9 @@
    R batchQuickInspect(BatchQuickInspectRequest request);
    void down(HttpServletResponse response, QualityInspect qualityInspect);
    /**
     * åˆ†æžæ¨¡æ¿ç»“æž„
     */
    String analyzeTemplate(String templatePath);
}
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -25,6 +25,7 @@
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.pojo.QualityUnqualified;
import com.ruoyi.quality.utils.QualityInspectTemplateExportHelper;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.quality.service.IQualityInspectParamService;
@@ -71,6 +72,8 @@
    private SalesLedgerProductMapper salesLedgerProductMapper;
    private ProcurementRecordService procurementRecordService;
    private final QualityInspectTemplateExportHelper qualityInspectTemplateExportHelper;
    @Override
    public int add(QualityInspectDto qualityInspectDto) {
@@ -444,5 +447,24 @@
    }
    @Override
    public void exportWeiLong(HttpServletResponse response, Long id) {
        qualityInspectTemplateExportHelper.exportWeiLong(response, id);
    }
    @Override
    public void exportBaiShi(HttpServletResponse response, Long id) {
        qualityInspectTemplateExportHelper.exportBaiShi(response, id);
    }
    @Override
    public void exportDaLi(HttpServletResponse response, Long id) {
        qualityInspectTemplateExportHelper.exportDaLi(response, id);
    }
    @Override
    public String analyzeTemplate(String templatePath) {
        return qualityInspectTemplateExportHelper.analyzeTemplate(templatePath);
    }
}
src/main/java/com/ruoyi/quality/utils/QualityInspectTemplateExportHelper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,489 @@
package com.ruoyi.quality.utils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.service.IQualityInspectParamService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Table;
import org.apache.poi.hwpf.usermodel.TableCell;
import org.apache.poi.hwpf.usermodel.TableIterator;
import org.apache.poi.hwpf.usermodel.TableRow;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
/**
 * Word template export helper for process inspection.
 */
@Component
@RequiredArgsConstructor
public class QualityInspectTemplateExportHelper {
    private static final String WEILONG_TEMPLATE = "/static/伟龙模版.doc";
    private static final String BAISHI_TEMPLATE = "/static/百事模版.doc";
    private static final String DALI_TEMPLATE = "/static/达利模版.doc";
    private final QualityInspectMapper qualityInspectMapper;
    private final IQualityInspectParamService qualityInspectParamService;
    public void exportWeiLong(HttpServletResponse response, Long inspectId) {
        export(response, inspectId, WEILONG_TEMPLATE, "伟龙模版检验结果");
    }
    public void exportBaiShi(HttpServletResponse response, Long inspectId) {
        export(response, inspectId, BAISHI_TEMPLATE, "百事模版检验结果");
    }
    public void exportDaLi(HttpServletResponse response, Long inspectId) {
        export(response, inspectId, DALI_TEMPLATE, "达利模版检验结果");
    }
    private void export(HttpServletResponse response, Long inspectId, String templatePath, String fileName) {
        if (inspectId == null) {
            throw new ServiceException("检验单ID不能为空");
        }
        QualityInspect inspect = qualityInspectMapper.selectById(inspectId);
        if (inspect == null) {
            throw new ServiceException("检验单不存在");
        }
        List<QualityInspectParam> paramList = qualityInspectParamService.list(
                Wrappers.<QualityInspectParam>lambdaQuery()
                        .eq(QualityInspectParam::getInspectId, inspectId)
                        .orderByAsc(QualityInspectParam::getId));
        Map<String, String> valueMap = buildValueMap(paramList, inspect);
        try (InputStream inputStream = getClass().getResourceAsStream(templatePath)) {
            if (inputStream == null) {
                throw new ServiceException("模板文件不存在:" + templatePath);
            }
            HWPFDocument document = new HWPFDocument(inputStream);
            fillDocument(document, valueMap);
            response.reset();
            response.setContentType("application/msword");
            response.setCharacterEncoding("UTF-8");
            String encodedName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.setHeader("Content-Disposition", "attachment;filename=" + encodedName + ".doc");
            try (OutputStream outputStream = response.getOutputStream()) {
                document.write(outputStream);
                outputStream.flush();
            }
        } catch (IOException e) {
            throw new RuntimeException("导出失败", e);
        }
    }
    private void fillDocument(HWPFDocument document, Map<String, String> valueMap) {
        Range range = document.getRange();
        TableIterator iterator = new TableIterator(range);
        while (iterator.hasNext()) {
            Table table = iterator.next();
            fillTable(table, valueMap);
        }
    }
    private void fillTable(Table table, Map<String, String> valueMap) {
        String currentGroupLabel = "";
        // è®°å½•结论列的位置,避免在结论列下面继续填充
        int conclusionCellIndex = -1;
        for (int rowIndex = 0; rowIndex < table.numRows(); rowIndex++) {
            TableRow row = table.getRow(rowIndex);
            String firstCellText = getCellText(row, 0);
            String secondCellText = getCellText(row, 1);
            if (StringUtils.isNotBlank(firstCellText)) {
                currentGroupLabel = firstCellText;
            }
            // å…ˆåˆ¤æ–­æ˜¯å¦ä¸ºç»“论行
            String normalizedFirstCell = normalizeKey(firstCellText);
            boolean isConclusionRow = matchesSummaryRow(normalizedFirstCell);
            // å¦‚果是结论行,结论列始终是最后一列
            if (isConclusionRow) {
                conclusionCellIndex = row.numCells() - 1;
            }
            String value = resolveValue(valueMap, currentGroupLabel, firstCellText, secondCellText, isConclusionRow);
            if (StringUtils.isBlank(value)) {
                continue;
            }
            // æŸ¥æ‰¾ç»“果列位置
            int resultCellIndex;
            if (isConclusionRow) {
                resultCellIndex = conclusionCellIndex;
            } else {
                // æ™®é€šè¡Œï¼šæ‰¾ç¬¬ä¸€ä¸ªç©ºç™½å•元格作为结果列,但要排除结论列
                resultCellIndex = findResultCellIndex(row, conclusionCellIndex);
                if (resultCellIndex < 0) {
                    // ä¸ä½¿ç”¨æœ€åŽä¸€åˆ—,避免与结论列冲突
                    int lastCellIndex = row.numCells() - 1;
                    if (lastCellIndex > 1 && lastCellIndex != conclusionCellIndex) {
                        resultCellIndex = lastCellIndex - 1;
                    }
                }
            }
            if (resultCellIndex < 0 || resultCellIndex == conclusionCellIndex && !isConclusionRow) {
                continue;
            }
            TableCell resultCell = row.getCell(resultCellIndex);
            String cellText = cleanCellText(resultCell.text());
            // ä¸¥æ ¼æ£€æŸ¥ï¼šåªæœ‰ç©ºç™½æˆ–占位符才填充
            if (!isBlankLike(cellText) && !isPlaceholder(cellText)) {
                continue;
            }
            // ä½¿ç”¨æ›´å®‰å…¨çš„填充方式
            safeWriteCellText(resultCell, value);
        }
    }
    /**
     * æŸ¥æ‰¾ç»“果列,排除结论列
     */
    private int findResultCellIndex(TableRow row, int excludeIndex) {
        for (int i = 1; i < row.numCells(); i++) {
            if (i == excludeIndex) {
                continue;
            }
            String cellText = cleanCellText(row.getCell(i).text());
            if (isBlankLike(cellText) || isPlaceholder(cellText)) {
                return i;
            }
        }
        return -1;
    }
    /**
     * å®‰å…¨å†™å…¥å•元格文本,避免影响其他行
     */
    private void safeWriteCellText(TableCell cell, String value) {
        if (StringUtils.isBlank(value)) {
            return;
        }
        String originalText = cell.text();
        String cleanedOriginal = cleanCellText(originalText);
        // å¦‚果单元格已有内容且不是占位符,不写入
        if (StringUtils.isNotBlank(cleanedOriginal) && !isPlaceholder(cleanedOriginal)) {
            return;
        }
        // åªä½¿ç”¨replaceText,不使用insertBefore
        try {
            if (StringUtils.isNotBlank(originalText)) {
                cell.replaceText(originalText, value);
            } else {
                // ç©ºå•元格直接设置文本
                cell.insertBefore(value);
            }
        } catch (Exception e) {
            // å¤‡ç”¨æ–¹æ¡ˆï¼šä½¿ç”¨getRange方式
            try {
//                cell.getRange().insertAfter(value);
            } catch (Exception ignored) {}
        }
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå ä½ç¬¦
     */
    private boolean isPlaceholder(String text) {
        if (StringUtils.isBlank(text)) {
            return false;
        }
        String cleaned = text.trim();
        // å¸¸è§å ä½ç¬¦æ¨¡å¼
        return cleaned.equals("—") ||
               cleaned.equals("-") ||
               cleaned.equals("_") ||
               cleaned.equals("/") ||
               cleaned.equals("\\") ||
               cleaned.equals("□") ||
               cleaned.equals("■") ||
               cleaned.equals("○") ||
               cleaned.equals("●") ||
               cleaned.equals("※") ||
               cleaned.equals("*") ||
               cleaned.matches("^\\.{2,}$") || // å¤šä¸ªç‚¹
               cleaned.matches("^{2,}$") || // å¤šä¸ªå¤§æ‹¬å·
               cleaned.equalsIgnoreCase("N/A") ||
               cleaned.equalsIgnoreCase("NA") ||
               cleaned.equals("待检") ||
               cleaned.equals("待填") ||
               cleaned.equals("空白");
    }
    private int findResultCellIndex(TableRow row) {
        for (int i = 1; i < row.numCells(); i++) {
            if (isBlankLike(row.getCell(i).text())) {
                return i;
            }
        }
        return -1;
    }
    private String resolveValue(Map<String, String> valueMap,
                                String currentGroupLabel,
                                String firstCellText,
                                String secondCellText,
                                boolean isConclusionRow) {
        LinkedHashSet<String> candidates = new LinkedHashSet<>();
        addCandidate(candidates, firstCellText);
        addCandidate(candidates, secondCellText);
        addCandidate(candidates, firstCellText + secondCellText);
        if (StringUtils.isNotBlank(currentGroupLabel) && StringUtils.isNotBlank(secondCellText)) {
            addCandidate(candidates, currentGroupLabel + secondCellText);
        }
        if (StringUtils.isNotBlank(currentGroupLabel)
                && StringUtils.isNotBlank(firstCellText)
                && StringUtils.isBlank(secondCellText)) {
            addCandidate(candidates, currentGroupLabel);
        }
        // ç»“论行不填充,保留模板原始数据
        if (isConclusionRow) {
            return null;
        }
        // æ™®é€šè¡Œï¼šå…ˆæŸ¥æ£€éªŒå‚数值,最后才匹配结论行关键词
        for (String candidate : candidates) {
            String normalized = normalizeKey(candidate);
            // æ™®é€šè¡Œè·³è¿‡ç»“论关键词匹配
            if (matchesSummaryRow(normalized)) {
                continue;
            }
            String value = lookupValue(valueMap, normalized);
            if (StringUtils.isNotBlank(value)) {
                return value;
            }
        }
        return null;
    }
    private boolean matchesSummaryRow(String normalizedText) {
        if (StringUtils.isBlank(normalizedText)) {
            return false;
        }
        return normalizedText.contains("质量评定")
                || normalizedText.contains("检验结论")
                || normalizedText.contains("Gradeestimation")
                || normalizedText.contains("Conclusion")
                || normalizedText.contains("Evaluation")
                || normalizedText.contains("评定")
                || normalizedText.contains("结论");
    }
    private String lookupValue(Map<String, String> valueMap, String normalizedCandidate) {
        if (StringUtils.isBlank(normalizedCandidate)) {
            return null;
        }
        String value = valueMap.get(normalizedCandidate);
        if (StringUtils.isNotBlank(value)) {
            return value;
        }
        String chineseCandidate = stripEnglishLetters(normalizedCandidate);
        value = valueMap.get(chineseCandidate);
        if (StringUtils.isNotBlank(value)) {
            return value;
        }
        for (Map.Entry<String, String> entry : valueMap.entrySet()) {
            String key = entry.getKey();
            if (StringUtils.contains(key, normalizedCandidate)
                    || StringUtils.contains(normalizedCandidate, key)
                    || StringUtils.contains(key, chineseCandidate)
                    || StringUtils.contains(chineseCandidate, key)) {
                return entry.getValue();
            }
        }
        return null;
    }
    private Map<String, String> buildValueMap(List<QualityInspectParam> paramList, QualityInspect inspect) {
        Map<String, String> valueMap = new LinkedHashMap<>();
        for (QualityInspectParam param : paramList) {
            String value = StringUtils.trimToNull(param.getTestValue());
            if (StringUtils.isBlank(value)) {
                continue;
            }
            putValue(valueMap, param.getParameterItem(), value);
        }
        String checkResult = StringUtils.trimToNull(inspect.getCheckResult());
        if (StringUtils.isNotBlank(checkResult)) {
            putValue(valueMap, "质量评定", checkResult);
            putValue(valueMap, "检验结果", checkResult);
            putValue(valueMap, "检验结论", checkResult);
            putValue(valueMap, "Grade estimation", checkResult);
            putValue(valueMap, "Test Results", checkResult);
        }
        return valueMap;
    }
    private void putValue(Map<String, String> valueMap, String key, String value) {
        String normalizedKey = normalizeKey(key);
        if (StringUtils.isBlank(normalizedKey)) {
            return;
        }
        valueMap.put(normalizedKey, value);
        String chineseKey = stripEnglishLetters(normalizedKey);
        if (StringUtils.isNotBlank(chineseKey)) {
            valueMap.putIfAbsent(chineseKey, value);
        }
    }
    private void writeCellText(TableCell cell, String value) {
        if (StringUtils.isBlank(value)) {
            return;
        }
        String originalText = cell.text();
        try {
            cell.replaceText(originalText, value);
        } catch (Exception ignored) {
            // Fallback below.
        }
        String cleanedText = cleanCellText(cell.text());
        if (!cleanedText.contains(value)) {
            cell.insertBefore(value);
        }
    }
    private String getCellText(TableRow row, int index) {
        if (row.numCells() <= index) {
            return "";
        }
        return cleanCellText(row.getCell(index).text());
    }
    private String cleanCellText(String text) {
        if (text == null) {
            return "";
        }
        return text.replace("\u0007", "")
                .replace("\r", "")
                .replace("\n", "")
                .trim();
    }
    private boolean isBlankLike(String text) {
        String cleaned = cleanCellText(text);
        return StringUtils.isBlank(cleaned)
                || "-".equals(cleaned)
                || "_".equals(cleaned)
                || "—".equals(cleaned)
                || "·".equals(cleaned);
    }
    private void addCandidate(LinkedHashSet<String> candidates, String text) {
        if (StringUtils.isBlank(text)) {
            return;
        }
        candidates.add(text);
    }
    private String normalizeKey(String text) {
        String value = cleanCellText(text);
        if (StringUtils.isBlank(value)) {
            return "";
        }
        value = value.replace("\u00A0", "");
        value = value.replaceAll("\\s+", "");
        value = value.replace("(", "");
        value = value.replace(")", "");
        value = value.replace("(", "");
        value = value.replace(")", "");
        value = value.replace(",", "");
        value = value.replace(",", "");
        value = value.replace("。", "");
        value = value.replace(":", "");
        value = value.replace(":", "");
        value = value.replace(";", "");
        value = value.replace(";", "");
        value = value.replace("、", "");
        value = value.replace("“", "");
        value = value.replace("”", "");
        value = value.replace("【", "");
        value = value.replace("】", "");
        value = value.replace("%", "");
        return value;
    }
    private String stripEnglishLetters(String text) {
        if (StringUtils.isBlank(text)) {
            return "";
        }
        return text.replaceAll("[A-Za-z]", "");
    }
    /**
     * è°ƒè¯•方法:分析模板表格结构
     */
    public String analyzeTemplate(String templatePath) {
        StringBuilder sb = new StringBuilder();
        try (InputStream inputStream = getClass().getResourceAsStream(templatePath)) {
            if (inputStream == null) {
                return "模板文件不存在:" + templatePath;
            }
            HWPFDocument document = new HWPFDocument(inputStream);
            Range range = document.getRange();
            TableIterator iterator = new TableIterator(range);
            int tableIndex = 0;
            while (iterator.hasNext()) {
                Table table = iterator.next();
                sb.append("=== è¡¨æ ¼ ").append(tableIndex++).append(" ===\n");
                sb.append("行数: ").append(table.numRows()).append("\n");
                for (int rowIndex = 0; rowIndex < table.numRows(); rowIndex++) {
                    TableRow row = table.getRow(rowIndex);
                    sb.append("行").append(rowIndex).append(": ");
                    for (int cellIndex = 0; cellIndex < row.numCells(); cellIndex++) {
                        String cellText = cleanCellText(row.getCell(cellIndex).text());
                        sb.append("[列").append(cellIndex).append(": ").append(cellText).append("] ");
                    }
                    sb.append("\n");
                }
                sb.append("\n");
            }
        } catch (Exception e) {
            sb.append("分析失败: ").append(e.getMessage());
        }
        return sb.toString();
    }
}
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
@@ -13,6 +13,9 @@
        dm.maintenance_actually_time,
        dm.maintenance_result,
        dm.status,
        dm.acceptance_name,
        dm.acceptance_time,
        dm.acceptance_remark,
        dm.create_time,
        dm.update_time,
        dm.create_user,
@@ -65,6 +68,9 @@
               dm.maintenance_actually_time,
               dm.maintenance_result,
               dm.status,
               dm.acceptance_name,
               dm.acceptance_time,
               dm.acceptance_remark,
               dm.create_time,
               dm.update_time,
               dm.create_user,
src/main/resources/static/ΰÁúÄ£°æ.doc
Binary files differ
src/main/resources/static/°ÙÊÂÄ£°æ.doc
Binary files differ
src/main/resources/static/´ïÀûÄ£°æ.doc
Binary files differ