Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
3cb9ef4d9e | |
|
|
bc060341aa |
|
|
@ -36,6 +36,9 @@ build/
|
|||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
/start/src/main/resources/templates/
|
||||
/start/src/main/resources/templates/*
|
||||
!/start/src/main/resources/templates/template/
|
||||
/start/src/main/resources/templates/template/*
|
||||
!/start/src/main/resources/templates/template/high_work_permit_sign.docx
|
||||
/start/src/main/resources/nacos/
|
||||
/.idea/
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ spring:
|
|||
import:
|
||||
# - classpath:nacos.yml
|
||||
# - classpath:sdk.yml
|
||||
# - classpath:nacos-prod.yml
|
||||
# - classpath:sdk-prod.yml
|
||||
- classpath:nacos-prod-gwj.yml
|
||||
- classpath:sdk-prod-gwj.yml
|
||||
- classpath:nacos-prod.yml
|
||||
- classpath:sdk-prod.yml
|
||||
# - classpath:nacos-prod-gwj.yml
|
||||
# - classpath:sdk-prod-gwj.yml
|
||||
- classpath:swagger.yml
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<License>
|
||||
<Data>
|
||||
<Products>
|
||||
<Product>Aspose.Total for Java</Product>
|
||||
<Product>Aspose.Words for Java</Product>
|
||||
</Products>
|
||||
<EditionType>Enterprise</EditionType>
|
||||
<SubscriptionExpiry>20991231</SubscriptionExpiry>
|
||||
<LicenseExpiry>20991231</LicenseExpiry>
|
||||
<SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber>
|
||||
</Data>
|
||||
<Signature>
|
||||
sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=
|
||||
</Signature>
|
||||
</License>
|
||||
|
|
@ -53,5 +53,6 @@ message:
|
|||
code:
|
||||
taskLogTemplate: MS000106
|
||||
|
||||
|
||||
file:
|
||||
url: http://192.168.192.201:8991/file/uploadFiles2/
|
||||
|
||||
|
|
|
|||
|
|
@ -53,5 +53,6 @@ message:
|
|||
code:
|
||||
taskLogTemplate: MS000127
|
||||
|
||||
|
||||
file:
|
||||
url: https://jpfz.qhdsafety.com/gbsFileTest/
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<!doctype html><html lang="zh"><head data-built-info="@cqsjjb/scripts@2.0.0-alpha-1 Env/production (2026/8/27 17:39:08) App/eightwork"><meta charset="UTF-8"/><meta name="renderer" content="webkit"/><meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/><meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><title>--</title><script>(function () {
|
||||
const APP_ENV = {
|
||||
antd: {
|
||||
'ant-prefix': 'micro-temp',
|
||||
fontFamily: 'PingFangSC-Regular',
|
||||
colorPrimary: '#1677ff',
|
||||
borderRadius: parseInt('2')
|
||||
},
|
||||
appKey: '',
|
||||
basename: 'eightwork',
|
||||
API_HOST: ''
|
||||
};
|
||||
APP_ENV.API_HOST = sessionStorage.API_HOST || APP_ENV.API_HOST || window.location.origin;
|
||||
window.process = {
|
||||
env: { app: APP_ENV },
|
||||
NODE_ENV: 'production'
|
||||
};
|
||||
window.__JJB_ENVIRONMENT__ = {
|
||||
API_HOST: APP_ENV.API_HOST,
|
||||
redirect: '',
|
||||
FRAMEWORK: APP_ENV.antd
|
||||
};
|
||||
})();</script><script defer="defer" src="/eightwork/static/js/294.08b4c187b6115f95c749.js"></script><script defer="defer" src="/eightwork/static/js/263.04481293786545920a2c.js"></script><script defer="defer" src="/eightwork/static/js/main.b96f88f58ccf84a977c5.js"></script><link href="/eightwork/static/css/main.8865e458bd5e9fb78fb8.css" rel="stylesheet"></head><body><noscript>此网页需要开启JavaScript功能。</noscript><div id="root" style="width: 100%; height: 100%; position: relative;overflow-y: auto"></div><script type="text/javascript">/* @cqsjjb/script 输出当前应用基本信息、构建时间 */console.log("%c@cqsjjb/scripts@2.0.0-alpha-1 Env/production (2026/8/27 17:39:08) App/eightwork Version/master Java/dev_change_0814", "color: #1890ff; border-radius: 2px; padding: 0 4px; border: 1px solid #1890ff; background: #f9fcff")</script></body></html>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
module.exports={compact:!1,plugins:[["@babel/plugin-proposal-decorators",{legacy:!0}]],presets:[["@babel/preset-env",{targets:{browsers:["ie >= 10"]}}],["@babel/preset-react",{runtime:"automatic"}]]};
|
||||
|
|
@ -0,0 +1 @@
|
|||
module.exports={javaGit:"http://47.92.113.182:3000/zcloud_gbs/zcloud-gbs-eightwork.git",javaGitName:"zcloud-gbs-eightwork",environment:{development:{javaGitBranch:"<branch-name>",API_HOST:"https://gbs-gateway.qhdsafety.com"},production:{javaGitBranch:"dev_change_0814",API_HOST:""}},appIdentifier:"eightwork",contextInject:{appKey:"",fileUrl:"https://skqhdg.porthebei.com:9004/file/uploadFiles2/"},windowInject:{title:"微应用模板",links:[],element:{root:{id:"root"}},scripts:[]},server:{port:"8063",host:"127.0.0.1",open:!0},framework:{antd:{"ant-prefix":"micro-temp",fontFamily:"PingFangSC-Regular",colorPrimary:"#1677ff",borderRadius:2}},webpackConfig:{htmlWebpackPluginOption:{inject:!0}}};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,109 @@
|
|||
/*!
|
||||
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
||||
*
|
||||
* Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* isobject <https://github.com/jonschlinkert/isobject>
|
||||
*
|
||||
* Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* screenfull
|
||||
* v5.2.0 - 2021-11-03
|
||||
* (c) Sindre Sorhus; MIT License
|
||||
*/
|
||||
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,87 @@
|
|||
/*!
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
||||
|
||||
/** @preserve
|
||||
* Counter block mode compatible with Dr Brian Gladman fileenc.c
|
||||
* derived from CryptoJS.mode.CTR
|
||||
* Jan Hruby jhruby.web@gmail.com
|
||||
*/
|
||||
|
||||
/** @preserve
|
||||
(c) 2012 by Cédric Mesnil. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
|
||||
/** */
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "micro-app",
|
||||
"version": "2.0.0",
|
||||
"description": "建教帮微应用模板",
|
||||
"author": "JJB",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"serve": "node node_modules/@cqsjjb/scripts/webpack.dev.server.js",
|
||||
"build": "node node_modules/@cqsjjb/scripts/webpack.build.js",
|
||||
"push": "zy-gbs-cmd push java production",
|
||||
"clean-cache": "rimraf node_modules/.cache/webpack",
|
||||
"serve:development": "cross-env NODE_ENV=development npm run serve",
|
||||
"serve:production": "cross-env NODE_ENV=production npm run serve",
|
||||
"build:development": "cross-env NODE_ENV=development npm run build",
|
||||
"build:production": "cross-env NODE_ENV=production npm run build",
|
||||
"code-optimization": "node node_modules/@cqsjjb/scripts/code-optimization.js",
|
||||
"lint": "eslint --ext .js,.jsx,.tsx --fix src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ahooksjs/use-url-state": "^3.5.1",
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ant-design/pro-components": "^2.8.10",
|
||||
"@cqsjjb/jjb-common-decorator": "latest",
|
||||
"@cqsjjb/jjb-common-lib": "latest",
|
||||
"@cqsjjb/jjb-dva-runtime": "latest",
|
||||
"@cqsjjb/jjb-react-admin-component": "latest",
|
||||
"@xyflow/react": "^12.10.1",
|
||||
"ahooks": "^3.9.5",
|
||||
"antd": "^5.27.6",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-to-print": "^3.3.0",
|
||||
"zy-react-data": "^1.0.2",
|
||||
"zy-react-library": "^1.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
"@babel/plugin-proposal-decorators": "^7.19.3",
|
||||
"@cqsjjb/scripts": "2.0.0-alpha-1",
|
||||
"@eslint-react/eslint-plugin": "^2.2.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint-plugin-format": "^1.0.2",
|
||||
"eslint-plugin-react-hooks": "^7.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.23",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
高处作业安全许可证
|
||||
证书编号:{{checkNo}}
|
||||
实施单位
|
||||
{{implementationUnit}}
|
||||
作业地点
|
||||
{{workLocation}}
|
||||
申请时间
|
||||
{{applyYear}}年 {{applyMonth}}月 {{applyDay}}日 {{applyHour}}时 {{applyMinute}}分
|
||||
作业级别
|
||||
{{@level1}} 1级 {{@level2}} 2级{{@level3}} 3级 {{@level4}} 4级
|
||||
项目批准人
|
||||
{{projectApprover}}
|
||||
方案编制人
|
||||
{{planCompiler}}
|
||||
方案批准人
|
||||
{{planApprover}}
|
||||
工作负责人
|
||||
{{workLeader}}
|
||||
高处作业人
|
||||
{{highWorkPerson}}
|
||||
监护人
|
||||
{{guardian}}
|
||||
工作内容{{workContent}}
|
||||
预计作业时间: {{startYear}}年 {{startMonth}}月 {{startDay}}日 {{startHour}}时 {{startMinute}}分 至 {{endYear}}年 {{endMonth}}月 {{endDay}}日 {{endHour}}时 {{endMinute}}分
|
||||
风险辨识及管控措施落实情况
|
||||
确认人
|
||||
1.{{measureContent1}}
|
||||
{{@measureSign1}}
|
||||
2.{{measureContent2}}
|
||||
{{@measureSign2}}
|
||||
3.{{measureContent3}}
|
||||
{{@measureSign3}}
|
||||
4.{{measureContent4}}
|
||||
{{@measureSign4}}
|
||||
5.{{measureContent5}}
|
||||
{{@measureSign5}}
|
||||
6.{{measureContent6}}
|
||||
{{@measureSign6}}
|
||||
7.{{measureContent7}}
|
||||
{{@measureSign7}}
|
||||
8.{{measureContent8}}
|
||||
{{@measureSign8}}
|
||||
9.{{measureContent9}}
|
||||
{{@measureSign9}}
|
||||
10.{{measureContent10}}
|
||||
{{@measureSign10}}
|
||||
安全交底人
|
||||
{{@safetyDiscloserSign}}
|
||||
被交底人
|
||||
{{@safetyDisclosureRecipientSigns}}
|
||||
实施单位意见
|
||||
签名:{{@implementationSign}} {{implementationYear}}年{{implementationMonth}}月{{implementationDay}}日{{implementationHour}}时{{implementationMinute}}分
|
||||
发包单位发包部门意见
|
||||
签名:{{@contractSign}} {{contractYear}}年{{contractMonth}}月{{contractDay}}日{{contractHour}}时{{contractMinute}}分
|
||||
发包单位安监部门意见
|
||||
签名:{{@safetySupervisionSign}} {{safetySupervisionYear}}年{{safetySupervisionMonth}}月{{safetySupervisionDay}}日{{safetySupervisionHour}}时{{safetySupervisionMinute}}分
|
||||
属地单位意见
|
||||
签名:{{@territorialSign}} {{territorialYear}}年{{territorialMonth}}月{{territorialDay}}日{{territorialHour}}时{{territorialMinute}}分
|
||||
|
|
@ -8,6 +8,7 @@ import com.zcloud.eightwork.dto.EightworkInfoPageQry;
|
|||
import com.zcloud.eightwork.dto.EightworkInfoUpdateCmd;
|
||||
import com.zcloud.eightwork.dto.EightworkInfoWithdrawCmd;
|
||||
import com.zcloud.eightwork.dto.ForceTerminateCmd;
|
||||
import com.zcloud.eightwork.dto.HighWorkPermitExportQry;
|
||||
import com.zcloud.eightwork.dto.ScreenDangerWorkDataStatisticsQry;
|
||||
import com.zcloud.eightwork.dto.ScreenDepartmentStatisticsQry;
|
||||
import com.zcloud.eightwork.dto.ScreenStatisticsQry;
|
||||
|
|
@ -184,5 +185,21 @@ public class EightworkInfoController {
|
|||
return MultiResponse.of(eightworkInfoService.screenWorkPieStatistics(qry));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出高处作业安全许可证。
|
||||
*
|
||||
* <p>GET 查询参数使用 HighWorkPermitExportQry 对象传入作业票主表ID列表;导出一张作业票时返回 PDF,
|
||||
* 导出多张时返回包含多个 PDF 的压缩包。</p>
|
||||
*
|
||||
* @param qry 高处作业安全许可证导出查询对象
|
||||
* @param response 导出文件响应
|
||||
*/
|
||||
@ApiOperation("高处作业安全许可证导出(单张PDF,多张压缩包)")
|
||||
@GetMapping("/exportHighWorkPermit")
|
||||
public void exportHighWorkPermit(@Validated HighWorkPermitExportQry qry,
|
||||
HttpServletResponse response) {
|
||||
eightworkInfoService.exportHighWorkPermit(qry, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ public class TaskLogAddExe {
|
|||
log.info("撤回作业票(不生成新票号): workId={}, checkNo={}", existingInfo.getWorkId(), checkNo);
|
||||
} else {
|
||||
// 不是撤回作业票 , 生成新作业票
|
||||
checkNo = generateFormalCheckNo(cmd.getWorkType());
|
||||
checkNo = generateFormalCheckNo(cmd.getWorkType(), cmd.getCorpinfoId());
|
||||
existingInfo.setCheckNo(checkNo);
|
||||
log.info("(生成新票号): workId={}, checkNo={}", existingInfo.getWorkId(), checkNo);
|
||||
}
|
||||
|
|
@ -204,7 +204,11 @@ public class TaskLogAddExe {
|
|||
/**
|
||||
* 生成正式票号
|
||||
*/
|
||||
private String generateFormalCheckNo(String workType) {
|
||||
private String generateFormalCheckNo(String workType, Long corpinfoId) {
|
||||
String corpName = eightworkInfoRepository.getCorpNameById(corpinfoId);
|
||||
if (StringUtils.isBlank(corpName)) {
|
||||
throw new BizException("未查询到企业名称,无法生成作业票号");
|
||||
}
|
||||
String workTypeCode = WorkCodeEnum.getCodeByWorkType(workType);
|
||||
String date = DateUtil.getDays();
|
||||
|
||||
|
|
@ -220,8 +224,8 @@ public class TaskLogAddExe {
|
|||
|
||||
log.info("生成正式票号: workType={}, date={}, seq={}", workType, date, seq);
|
||||
|
||||
// 正式票号: QG + 作业类型代码 + 日期 + 4位序号
|
||||
return "QG" + workTypeCode + date + String.format("%04d", seq);
|
||||
// 正式票号: 企业名称 + 作业类型代码 + 日期 + 4位序号
|
||||
return corpName + workTypeCode + date + String.format("%04d", seq);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -346,7 +350,7 @@ public class TaskLogAddExe {
|
|||
*/
|
||||
private String saveWork(TaskLogAddCmd cmd) {
|
||||
// 生成正式票号
|
||||
String checkNo = generateFormalCheckNo(cmd.getWorkType());
|
||||
String checkNo = generateFormalCheckNo(cmd.getWorkType(), cmd.getCorpinfoId());
|
||||
|
||||
EightworkInfoDO eightworkInfo = new EightworkInfoDO(
|
||||
Tools.get32UUID(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,888 @@
|
|||
package com.zcloud.eightwork.command.query;
|
||||
|
||||
import com.alibaba.cola.exception.BizException;
|
||||
import com.aspose.words.FolderFontSource;
|
||||
import com.aspose.words.FontSettings;
|
||||
import com.aspose.words.FontSourceBase;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.deepoove.poi.data.PictureRenderData;
|
||||
import com.deepoove.poi.data.PictureType;
|
||||
import com.deepoove.poi.data.Pictures;
|
||||
import com.zcloud.eightwork.domain.model.enums.WorkCodeEnum;
|
||||
import com.zcloud.eightwork.dto.HighWorkPermitExportQry;
|
||||
import com.zcloud.eightwork.persistence.dataobject.EightworkInfoDO;
|
||||
import com.zcloud.eightwork.persistence.dataobject.MeasuresLogsArchiveDO;
|
||||
import com.zcloud.eightwork.persistence.dataobject.MeasuresLogsDO;
|
||||
import com.zcloud.eightwork.persistence.dataobject.TaskLogDO;
|
||||
import com.zcloud.eightwork.persistence.repository.EightworkInfoRepository;
|
||||
import com.zcloud.eightwork.persistence.repository.MeasuresLogsArchiveRepository;
|
||||
import com.zcloud.eightwork.persistence.repository.MeasuresLogsRepository;
|
||||
import com.zcloud.eightwork.persistence.repository.TaskLogRepository;
|
||||
import com.zcloud.gbscommon.utils.Tools;
|
||||
import com.zcloud.gbscommon.utils.WordToPdfUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* 高处作业安全许可证导出执行器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HighWorkPermitExportExe {
|
||||
|
||||
private static final String TEMPLATE_PATH = "templates/template/high_work_permit_sign.docx";
|
||||
private static final String FONT_RESOURCE_PATH = "fonts/simsun.ttc";
|
||||
private static final String FONT_FILE_NAME = "simsun.ttc";
|
||||
private static final Object FONT_REGISTER_LOCK = new Object();
|
||||
private static final DateTimeFormatter FILE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
private static final int MEASURE_SIGN_WIDTH = 70;
|
||||
private static final int MEASURE_SIGN_HEIGHT = 18;
|
||||
private static final int OPINION_SIGN_WIDTH = 76;
|
||||
private static final int OPINION_SIGN_HEIGHT = 18;
|
||||
private static final int DISCLOSER_SIGN_WIDTH = 72;
|
||||
private static final int RECIPIENT_SIGN_WIDTH = 220;
|
||||
private static final int DISCLOSURE_SIGN_HEIGHT = 20;
|
||||
private static final long SAFETY_DISCLOSER_STEP_ID = 18L;
|
||||
private static final long SAFETY_DISCLOSURE_RECIPIENT_STEP_ID = 19L;
|
||||
private static final long IMPLEMENTATION_OPINION_STEP_ID = 41L;
|
||||
private static final long CONTRACT_OPINION_STEP_ID = 42L;
|
||||
private static final long SAFETY_SUPERVISION_OPINION_STEP_ID = 43L;
|
||||
private static final long TERRITORIAL_OPINION_STEP_ID = 44L;
|
||||
private static final int WORK_LEVEL_MARK_SIZE = 10;
|
||||
private static final int SIGNATURE_RENDER_SCALE = 8;
|
||||
private static volatile boolean fontRegistered;
|
||||
|
||||
private final EightworkInfoRepository eightworkInfoRepository;
|
||||
private final MeasuresLogsRepository measuresLogsRepository;
|
||||
private final MeasuresLogsArchiveRepository measuresLogsArchiveRepository;
|
||||
private final TaskLogRepository taskLogRepository;
|
||||
|
||||
@Value("${file.url:}")
|
||||
private String fileUrl;
|
||||
|
||||
public void execute(HighWorkPermitExportQry qry, HttpServletResponse response) {
|
||||
try {
|
||||
List<EightworkInfoDO> workList = getWorkList(qry.getIds());
|
||||
registerPdfFont();
|
||||
Map<String, byte[]> pdfFiles = new LinkedHashMap<>();
|
||||
Map<String, Integer> fileNameCounts = new HashMap<>();
|
||||
|
||||
for (EightworkInfoDO work : workList) {
|
||||
byte[] wordBytes = renderWord(work);
|
||||
byte[] pdfBytes = WordToPdfUtil.convertWordBytesToPdfBytes(wordBytes);
|
||||
String fileName = buildPdfFileName(work, fileNameCounts);
|
||||
pdfFiles.put(fileName, pdfBytes);
|
||||
}
|
||||
|
||||
if (pdfFiles.size() == 1) {
|
||||
Map.Entry<String, byte[]> file = pdfFiles.entrySet().iterator().next();
|
||||
writeResponse(response, file.getValue(), "application/pdf", file.getKey());
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] zipBytes = buildZip(pdfFiles);
|
||||
String zipName = "高处作业安全许可证_" + LocalDateTime.now().format(FILE_TIME_FORMATTER) + ".zip";
|
||||
writeResponse(response, zipBytes, "application/zip", zipName);
|
||||
} catch (BizException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("高处作业安全许可证导出失败", e);
|
||||
throw new BizException("高处作业安全许可证导出失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPdfFont() throws Exception {
|
||||
if (fontRegistered) {
|
||||
return;
|
||||
}
|
||||
synchronized (FONT_REGISTER_LOCK) {
|
||||
if (fontRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path fontDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "eightwork-pdf-fonts");
|
||||
Path fontFile = fontDirectory.resolve(FONT_FILE_NAME);
|
||||
Files.createDirectories(fontDirectory);
|
||||
try (InputStream inputStream = HighWorkPermitExportExe.class.getClassLoader()
|
||||
.getResourceAsStream(FONT_RESOURCE_PATH)) {
|
||||
if (inputStream == null) {
|
||||
throw new BizException("高处作业许可证中文字体资源不存在");
|
||||
}
|
||||
Files.copy(inputStream, fontFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
FontSourceBase[] currentSources = FontSettings.getFontsSources();
|
||||
FontSourceBase[] fontSources = new FontSourceBase[currentSources.length + 1];
|
||||
fontSources[0] = new FolderFontSource(fontDirectory.toString(), false, 100);
|
||||
System.arraycopy(currentSources, 0, fontSources, 1, currentSources.length);
|
||||
FontSettings.setFontsSources(fontSources);
|
||||
FontSettings.addFontSubstitutes("宋体", "SimSun");
|
||||
FontSettings.setDefaultFontName("SimSun");
|
||||
fontRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private List<EightworkInfoDO> getWorkList(List<Long> requestIds) {
|
||||
if (CollectionUtils.isEmpty(requestIds) || requestIds.contains(null)) {
|
||||
throw new BizException("作业票主表ID不能为空");
|
||||
}
|
||||
|
||||
List<Long> ids = new ArrayList<>(new LinkedHashSet<>(requestIds));
|
||||
List<EightworkInfoDO> records = eightworkInfoRepository.listByIds(ids);
|
||||
Map<Long, EightworkInfoDO> recordMap = new HashMap<>();
|
||||
for (EightworkInfoDO record : records) {
|
||||
recordMap.put(record.getId(), record);
|
||||
}
|
||||
|
||||
List<EightworkInfoDO> result = new ArrayList<>();
|
||||
for (Long id : ids) {
|
||||
EightworkInfoDO record = recordMap.get(id);
|
||||
if (record == null) {
|
||||
throw new BizException("未查询到作业票,ID:" + id);
|
||||
}
|
||||
if (!WorkCodeEnum.HIGH_WORK.getWorkType().equals(record.getWorkType())) {
|
||||
throw new BizException("仅支持导出高处作业票,ID:" + id);
|
||||
}
|
||||
result.add(record);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] renderWord(EightworkInfoDO work) throws Exception {
|
||||
HighWorkPermitInfo info = parseInfo(work);
|
||||
List<TaskLogDO> taskLogs = taskLogRepository.listAllByWorkId(work.getWorkId());
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("checkNo", value(work.getCheckNo()));
|
||||
data.put("implementationUnit", firstNonBlank(info.getImplementationUnit(), info.getImplementationUnitName(),
|
||||
info.getWorkUnit(), info.getWorkUnitName(), info.getConstructionUnit()));
|
||||
if (StringUtils.isBlank((String) data.get("implementationUnit"))) {
|
||||
data.put("implementationUnit", value(eightworkInfoRepository.getCorpNameById(work.getCorpinfoId())));
|
||||
}
|
||||
data.put("workLocation", firstNonBlank(info.getWorkLocation(), info.getWorkPlace(), info.getOperationLocation(),
|
||||
info.getWorkAddress(), info.getWorkSite(), info.getWorkPosition()));
|
||||
data.put("workContent", firstNonBlank(info.getWorkContent(), info.getOperationContent(), info.getWorkDescription()));
|
||||
|
||||
String applyTime = firstNonBlank(info.getApplyTime(), info.getApplicationTime());
|
||||
if (StringUtils.isBlank(applyTime) && work.getCreateTime() != null) {
|
||||
applyTime = work.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
putDateParts(data, "apply", applyTime);
|
||||
putDateParts(data, "start", firstNonBlank(info.getWorkStartTime(), info.getStartTime()));
|
||||
putDateParts(data, "end", firstNonBlank(info.getWorkEndTime(), info.getEndTime()));
|
||||
putWorkLevel(data, work.getCheckNo(), work.getWorkLevel(), info.getWorkLevel(), info.getWorkLevelName(),
|
||||
info.getOperationLevel(), info.getHighWorkLevel());
|
||||
|
||||
data.put("projectApprover", firstNonBlank(
|
||||
firstNonBlank(info.getProjectApproverName(), info.getProjectApprovalName()),
|
||||
findStepActor(info, "项目批准")));
|
||||
data.put("planCompiler", firstNonBlank(
|
||||
firstNonBlank(info.getPlanPreparerName(), info.getPlanCompilerName(), info.getSchemeCompilerName()),
|
||||
findStepActor(info, "方案编制")));
|
||||
data.put("planApprover", firstNonBlank(
|
||||
firstNonBlank(info.getPlanApproverName(), info.getSchemeApproverName()),
|
||||
findStepActor(info, "方案批准")));
|
||||
data.put("workLeader", firstNonBlank(
|
||||
firstNonBlank(info.getWorkChargeUserName(), info.getWorkLeaderName(), info.getWorkResponsibleName(),
|
||||
info.getOperationLeaderName()),
|
||||
findStepActor(info, "工作负责人", "作业负责人")));
|
||||
data.put("highWorkPerson", firstNonBlank(
|
||||
firstNonBlank(textValue(info.getWorkUserName()), info.getHighWorkPersonName(), info.getWorkPersonName(),
|
||||
info.getOperationPersonName()),
|
||||
findStepActor(info, "高处作业人", "高处作业人员")));
|
||||
data.put("guardian", firstNonBlank(
|
||||
firstNonBlank(textValue(info.getWorkGuardianUserName()), info.getGuardianName(), info.getMonitorName()),
|
||||
findStepActor(info, "监护人", "监护")));
|
||||
|
||||
putSafetyDisclosureSignatures(data, taskLogs, work.getCheckNo());
|
||||
putOpinionSignatures(data, taskLogs, "implementation", work.getCheckNo(), IMPLEMENTATION_OPINION_STEP_ID);
|
||||
putOpinionSignatures(data, taskLogs, "contract", work.getCheckNo(), CONTRACT_OPINION_STEP_ID);
|
||||
putOpinionSignatures(data, taskLogs, "safetySupervision", work.getCheckNo(), SAFETY_SUPERVISION_OPINION_STEP_ID);
|
||||
putOpinionSignatures(data, taskLogs, "territorial", work.getCheckNo(), TERRITORIAL_OPINION_STEP_ID);
|
||||
putMeasureSignatures(data, work.getWorkId(), work.getCheckNo());
|
||||
|
||||
return Tools.renderTemplate(TEMPLATE_PATH, data).toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析作业票详情。导出器后续只使用对象字段和步骤对象,不直接读写 JSON。
|
||||
*/
|
||||
private HighWorkPermitInfo parseInfo(EightworkInfoDO work) {
|
||||
if (StringUtils.isBlank(work.getInfo())) {
|
||||
return new HighWorkPermitInfo();
|
||||
}
|
||||
try {
|
||||
return HighWorkPermitInfo.parse(work.getInfo());
|
||||
} catch (Exception e) {
|
||||
throw new BizException("作业票详情格式错误,票号:" + value(work.getCheckNo()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按主表级别、详情编码、详情名称依次识别作业级别。
|
||||
* 主表存在非标准历史值时,继续使用详情中的有效值,避免导致许可证未勾选任何级别。
|
||||
*/
|
||||
private void putWorkLevel(Map<String, Object> data, String checkNo, String... workLevels) {
|
||||
int selectedLevel = 0;
|
||||
String originalValue = "";
|
||||
for (String workLevel : workLevels) {
|
||||
if (StringUtils.isBlank(workLevel)) {
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isBlank(originalValue)) {
|
||||
originalValue = workLevel;
|
||||
}
|
||||
selectedLevel = resolveWorkLevel(workLevel);
|
||||
if (selectedLevel != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selectedLevel == 0 && StringUtils.isNotBlank(originalValue)) {
|
||||
log.warn("高处作业许可证未识别作业级别: checkNo={}, workLevel={}", checkNo, originalValue);
|
||||
}
|
||||
for (int level = 1; level <= 4; level++) {
|
||||
data.put("level" + level, buildWorkLevelMark(selectedLevel == level));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 作业级别使用图片绘制方框与勾选,避免 PDF 转换时因字体缺字导致勾选样式异常。
|
||||
*/
|
||||
private PictureRenderData buildWorkLevelMark(boolean selected) {
|
||||
BufferedImage image = new BufferedImage(WORK_LEVEL_MARK_SIZE, WORK_LEVEL_MARK_SIZE, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
try {
|
||||
graphics.setColor(Color.BLACK);
|
||||
graphics.setStroke(new BasicStroke(1F));
|
||||
graphics.drawRect(1, 1, WORK_LEVEL_MARK_SIZE - 3, WORK_LEVEL_MARK_SIZE - 3);
|
||||
if (selected) {
|
||||
graphics.setStroke(new BasicStroke(1.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
graphics.drawLine(3, 5, 4, 7);
|
||||
graphics.drawLine(4, 7, 8, 3);
|
||||
}
|
||||
} finally {
|
||||
graphics.dispose();
|
||||
}
|
||||
return Pictures.ofBufferedImage(image, PictureType.PNG)
|
||||
.size(WORK_LEVEL_MARK_SIZE, WORK_LEVEL_MARK_SIZE)
|
||||
.create();
|
||||
}
|
||||
|
||||
private int resolveWorkLevel(String workLevel) {
|
||||
if (StringUtils.isBlank(workLevel)) {
|
||||
return 0;
|
||||
}
|
||||
String normalized = workLevel.trim()
|
||||
.replaceAll("\\s+", "")
|
||||
.replace("_", "")
|
||||
.replace("-", "")
|
||||
.replace("1", "1")
|
||||
.replace("2", "2")
|
||||
.replace("3", "3")
|
||||
.replace("4", "4")
|
||||
.toUpperCase();
|
||||
switch (normalized) {
|
||||
case "HIGHWORKLEVEL0":
|
||||
case "HIGHWORKLEVEL1":
|
||||
return 1;
|
||||
case "HIGHWORKLEVEL2":
|
||||
return 2;
|
||||
case "HIGHWORKLEVEL3":
|
||||
return 3;
|
||||
case "HIGHWORKLEVEL4":
|
||||
return 4;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (matchesWorkLevel(normalized, "1", "一级", "Ⅰ", "I")) {
|
||||
return 1;
|
||||
}
|
||||
if (matchesWorkLevel(normalized, "2", "二级", "Ⅱ", "II")) {
|
||||
return 2;
|
||||
}
|
||||
if (matchesWorkLevel(normalized, "3", "三级", "Ⅲ", "III")) {
|
||||
return 3;
|
||||
}
|
||||
if (matchesWorkLevel(normalized, "4", "四级", "Ⅳ", "IV") || normalized.contains("特级")) {
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean matchesWorkLevel(String workLevel, String number, String chineseLevel,
|
||||
String romanNumber, String asciiRomanNumber) {
|
||||
return workLevel.equals(number)
|
||||
|| workLevel.contains(number + "级")
|
||||
|| workLevel.contains(chineseLevel)
|
||||
|| workLevel.contains(romanNumber + "级")
|
||||
|| workLevel.equals(romanNumber)
|
||||
|| workLevel.contains("LEVEL" + number)
|
||||
|| workLevel.contains("GC" + number)
|
||||
|| workLevel.equals(asciiRomanNumber)
|
||||
|| workLevel.equals(asciiRomanNumber + "级");
|
||||
}
|
||||
|
||||
private void putDateParts(Map<String, Object> data, String prefix, String dateTime) {
|
||||
List<String> parts = new ArrayList<>();
|
||||
if (StringUtils.isNotBlank(dateTime)) {
|
||||
for (String part : dateTime.split("[^0-9]+")) {
|
||||
if (StringUtils.isNotBlank(part)) {
|
||||
parts.add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
data.put(prefix + "Year", getPart(parts, 0));
|
||||
data.put(prefix + "Month", getPart(parts, 1));
|
||||
data.put(prefix + "Day", getPart(parts, 2));
|
||||
data.put(prefix + "Hour", getPart(parts, 3));
|
||||
data.put(prefix + "Minute", getPart(parts, 4));
|
||||
}
|
||||
|
||||
private String getPart(List<String> parts, int index) {
|
||||
return parts.size() > index ? parts.get(index) : "";
|
||||
}
|
||||
|
||||
private void putDateParts(Map<String, Object> data, String prefix, LocalDateTime dateTime) {
|
||||
putDateParts(data, prefix, dateTime == null ? "" : dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 签字图片以 task_log 为唯一来源:18 为安全交底人,19 为接受交底人。
|
||||
* 接受交底人可能存在多条流程日志,按流程记录顺序合并为多张签字图片。
|
||||
*/
|
||||
private void putSafetyDisclosureSignatures(Map<String, Object> data, List<TaskLogDO> taskLogs,
|
||||
String checkNo) {
|
||||
data.put("safetyDiscloserSign", buildSignaturePicture(
|
||||
getTaskLogSignPaths(taskLogs, SAFETY_DISCLOSER_STEP_ID),
|
||||
DISCLOSER_SIGN_WIDTH, DISCLOSURE_SIGN_HEIGHT, checkNo));
|
||||
data.put("safetyDisclosureRecipientSigns", buildSignaturePicture(
|
||||
getTaskLogSignPaths(taskLogs, SAFETY_DISCLOSURE_RECIPIENT_STEP_ID),
|
||||
RECIPIENT_SIGN_WIDTH, DISCLOSURE_SIGN_HEIGHT, checkNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 四类意见按固定流程节点取签字与时间:41 实施单位、42 发包部门、43 安监部门、44 属地单位。
|
||||
*/
|
||||
private void putOpinionSignatures(Map<String, Object> data, List<TaskLogDO> taskLogs, String prefix,
|
||||
String checkNo, long stepId) {
|
||||
List<TaskLogDO> stepLogs = getTaskLogs(taskLogs, stepId);
|
||||
data.put(prefix + "Sign", buildSignaturePicture(getTaskLogSignPaths(stepLogs),
|
||||
OPINION_SIGN_WIDTH, OPINION_SIGN_HEIGHT, checkNo));
|
||||
TaskLogDO latestLog = getLatestSignedTaskLog(stepLogs);
|
||||
putDateParts(data, prefix, latestLog == null ? null : latestLog.getSignTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 风险措施的内容和确认人签字均以 measures_logs 为准,按 order_by 对应许可证第 1 至 10 行。
|
||||
*/
|
||||
private void putMeasureSignatures(Map<String, Object> data, String workId, String checkNo) {
|
||||
List<MeasureValue> measures = getMeasures(workId);
|
||||
Map<Integer, List<String>> measureSignPaths = new HashMap<>();
|
||||
for (int index = 1; index <= 10; index++) {
|
||||
data.put("measureContent" + index, "");
|
||||
data.put("measureSign" + index, null);
|
||||
}
|
||||
|
||||
int sequentialIndex = 1;
|
||||
Set<Integer> usedMeasureIndexes = new LinkedHashSet<>();
|
||||
for (MeasureValue measure : measures) {
|
||||
int targetIndex;
|
||||
if (measure.orderBy != null && measure.orderBy >= 1 && measure.orderBy <= 10
|
||||
&& !usedMeasureIndexes.contains(measure.orderBy)) {
|
||||
targetIndex = measure.orderBy;
|
||||
} else {
|
||||
while (sequentialIndex <= 10 && usedMeasureIndexes.contains(sequentialIndex)) {
|
||||
sequentialIndex++;
|
||||
}
|
||||
targetIndex = sequentialIndex++;
|
||||
}
|
||||
if (targetIndex > 10) {
|
||||
continue;
|
||||
}
|
||||
usedMeasureIndexes.add(targetIndex);
|
||||
appendMeasureContent(data, targetIndex, measure.content);
|
||||
addSignPaths(measureSignPaths, targetIndex, measure.signPath);
|
||||
}
|
||||
for (int index = 1; index <= 10; index++) {
|
||||
data.put("measureSign" + index, buildSignaturePicture(measureSignPaths.get(index),
|
||||
MEASURE_SIGN_WIDTH, MEASURE_SIGN_HEIGHT, checkNo));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendMeasureContent(Map<String, Object> data, int index, String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return;
|
||||
}
|
||||
String key = "measureContent" + index;
|
||||
String currentContent = String.valueOf(data.get(key));
|
||||
data.put(key, StringUtils.isBlank(currentContent) ? content : currentContent + "\n" + content);
|
||||
}
|
||||
|
||||
private List<MeasureValue> getMeasures(String workId) {
|
||||
List<MeasuresLogsDO> currentMeasures = measuresLogsRepository.listAll(workId);
|
||||
if (!CollectionUtils.isEmpty(currentMeasures)) {
|
||||
List<MeasureValue> result = new ArrayList<>();
|
||||
for (MeasuresLogsDO measure : currentMeasures) {
|
||||
result.add(new MeasureValue(measure.getContent(), measure.getSignPath(), measure.getOrderBy()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
List<MeasuresLogsArchiveDO> archiveMeasures = measuresLogsArchiveRepository.list(
|
||||
new LambdaQueryWrapper<MeasuresLogsArchiveDO>()
|
||||
.eq(MeasuresLogsArchiveDO::getWorkId, workId)
|
||||
.orderByAsc(MeasuresLogsArchiveDO::getType)
|
||||
.orderByAsc(MeasuresLogsArchiveDO::getOrderBy)
|
||||
.orderByAsc(MeasuresLogsArchiveDO::getCreateTime));
|
||||
List<MeasureValue> result = new ArrayList<>();
|
||||
for (MeasuresLogsArchiveDO measure : archiveMeasures) {
|
||||
result.add(new MeasureValue(measure.getContent(), measure.getSignPath(), measure.getOrderBy()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<TaskLogDO> getTaskLogs(List<TaskLogDO> taskLogs, long stepId) {
|
||||
List<TaskLogDO> result = new ArrayList<>();
|
||||
if (CollectionUtils.isEmpty(taskLogs)) {
|
||||
return result;
|
||||
}
|
||||
for (TaskLogDO taskLog : taskLogs) {
|
||||
if (taskLog != null && Long.valueOf(stepId).equals(taskLog.getStepId())) {
|
||||
result.add(taskLog);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> getTaskLogSignPaths(List<TaskLogDO> taskLogs, long stepId) {
|
||||
return getTaskLogSignPaths(getTaskLogs(taskLogs, stepId));
|
||||
}
|
||||
|
||||
private List<String> getTaskLogSignPaths(List<TaskLogDO> taskLogs) {
|
||||
List<String> result = new ArrayList<>();
|
||||
if (CollectionUtils.isEmpty(taskLogs)) {
|
||||
return result;
|
||||
}
|
||||
for (TaskLogDO taskLog : taskLogs) {
|
||||
if (isSigned(taskLog)) {
|
||||
addSignPaths(result, taskLog.getSignPath());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addSignPaths(Map<Integer, List<String>> signPaths, int index, String signPath) {
|
||||
List<String> paths = signPaths.get(index);
|
||||
if (paths == null) {
|
||||
paths = new ArrayList<>();
|
||||
signPaths.put(index, paths);
|
||||
}
|
||||
addSignPaths(paths, signPath);
|
||||
}
|
||||
|
||||
private TaskLogDO getLatestSignedTaskLog(List<TaskLogDO> taskLogs) {
|
||||
TaskLogDO latestLog = null;
|
||||
if (CollectionUtils.isEmpty(taskLogs)) {
|
||||
return null;
|
||||
}
|
||||
for (TaskLogDO taskLog : taskLogs) {
|
||||
if (!isSigned(taskLog) || taskLog.getSignTime() == null) {
|
||||
continue;
|
||||
}
|
||||
if (latestLog == null || taskLog.getSignTime().isAfter(latestLog.getSignTime())) {
|
||||
latestLog = taskLog;
|
||||
}
|
||||
}
|
||||
return latestLog;
|
||||
}
|
||||
|
||||
private void addSignPaths(List<String> signPaths, String signPath) {
|
||||
if (StringUtils.isNotBlank(signPath) && !signPaths.contains(signPath.trim())) {
|
||||
signPaths.add(signPath.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSigned(TaskLogDO taskLog) {
|
||||
return taskLog != null && (taskLog.getSignTime() != null || StringUtils.isNotBlank(taskLog.getSignPath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建签字图片。签字文件丢失、地址不可访问或格式不支持时跳过该图片,避免影响作业票导出。
|
||||
*/
|
||||
private PictureRenderData buildSignaturePicture(List<String> signPaths, int width, int height,
|
||||
String checkNo) {
|
||||
if (CollectionUtils.isEmpty(signPaths)) {
|
||||
return null;
|
||||
}
|
||||
List<BufferedImage> images = new ArrayList<>();
|
||||
for (String signPath : signPaths) {
|
||||
try {
|
||||
byte[] imageBytes = loadSignatureBytes(signPath, checkNo);
|
||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
|
||||
if (image == null) {
|
||||
log.warn("签字图片格式不支持,已跳过:票号={}, 路径={}", value(checkNo), signPath);
|
||||
continue;
|
||||
}
|
||||
images.add(image);
|
||||
} catch (Exception e) {
|
||||
log.warn("签字图片加载失败,已跳过:票号={}, 路径={}, 原因={}",
|
||||
value(checkNo), signPath, e.getMessage());
|
||||
}
|
||||
}
|
||||
if (images.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BufferedImage signatureImage = composeSignatureImages(images, width, height);
|
||||
return Pictures.ofBufferedImage(signatureImage, PictureType.PNG).size(width, height).create();
|
||||
}
|
||||
|
||||
private byte[] loadSignatureBytes(String signPath, String checkNo) throws Exception {
|
||||
if (signPath.startsWith("data:image")) {
|
||||
int separatorIndex = signPath.indexOf(',');
|
||||
if (separatorIndex < 0) {
|
||||
throw new BizException("签字图片数据格式错误,票号:" + value(checkNo));
|
||||
}
|
||||
return Base64.getDecoder().decode(signPath.substring(separatorIndex + 1));
|
||||
}
|
||||
|
||||
String imageUrl = buildSignatureUrl(signPath, checkNo);
|
||||
URLConnection connection = new URL(imageUrl).openConnection();
|
||||
connection.setConnectTimeout(10000);
|
||||
connection.setReadTimeout(10000);
|
||||
try {
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
int responseCode = ((HttpURLConnection) connection).getResponseCode();
|
||||
if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
|
||||
throw new IOException("签字图片请求失败,状态码:" + responseCode + ",地址:" + imageUrl);
|
||||
}
|
||||
}
|
||||
try (InputStream inputStream = connection.getInputStream();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int length;
|
||||
while ((length = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, length);
|
||||
}
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
} finally {
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
((HttpURLConnection) connection).disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSignatureUrl(String signPath, String checkNo) {
|
||||
if (signPath.startsWith("http://") || signPath.startsWith("https://")) {
|
||||
return signPath;
|
||||
}
|
||||
if (StringUtils.isBlank(fileUrl)) {
|
||||
throw new BizException("签字图片地址配置不存在,票号:" + value(checkNo));
|
||||
}
|
||||
if (fileUrl.endsWith("/") && signPath.startsWith("/")) {
|
||||
return fileUrl + signPath.substring(1);
|
||||
}
|
||||
if (!fileUrl.endsWith("/") && !signPath.startsWith("/")) {
|
||||
return fileUrl + "/" + signPath;
|
||||
}
|
||||
return fileUrl + signPath;
|
||||
}
|
||||
|
||||
private BufferedImage composeSignatureImages(List<BufferedImage> images, int width, int height) {
|
||||
int renderWidth = width * SIGNATURE_RENDER_SCALE;
|
||||
int renderHeight = height * SIGNATURE_RENDER_SCALE;
|
||||
BufferedImage result = new BufferedImage(renderWidth, renderHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = result.createGraphics();
|
||||
try {
|
||||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
|
||||
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
int slotWidth = Math.max(1, renderWidth / images.size());
|
||||
int horizontalMargin = SIGNATURE_RENDER_SCALE * 4;
|
||||
int verticalMargin = SIGNATURE_RENDER_SCALE * 2;
|
||||
for (int index = 0; index < images.size(); index++) {
|
||||
BufferedImage image = cropSignatureImage(images.get(index));
|
||||
double scale = Math.min((double) Math.max(1, slotWidth - horizontalMargin) / image.getWidth(),
|
||||
(double) Math.max(1, renderHeight - verticalMargin) / image.getHeight());
|
||||
int drawWidth = Math.max(1, (int) Math.round(image.getWidth() * scale));
|
||||
int drawHeight = Math.max(1, (int) Math.round(image.getHeight() * scale));
|
||||
int x = index * slotWidth + horizontalMargin / 2;
|
||||
int y = Math.max(0, (renderHeight - drawHeight) / 2);
|
||||
graphics.drawImage(image, x, y, drawWidth, drawHeight, null);
|
||||
}
|
||||
} finally {
|
||||
graphics.dispose();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 签字文件通常包含大面积纯白画布,先裁除白边后再等比缩放,保证笔迹在许可证中清晰可见。
|
||||
*/
|
||||
private BufferedImage cropSignatureImage(BufferedImage image) {
|
||||
BufferedImage source = removeSignatureFrame(image);
|
||||
int minX = source.getWidth();
|
||||
int minY = source.getHeight();
|
||||
int maxX = -1;
|
||||
int maxY = -1;
|
||||
for (int y = 0; y < source.getHeight(); y++) {
|
||||
for (int x = 0; x < source.getWidth(); x++) {
|
||||
int pixel = source.getRGB(x, y);
|
||||
int alpha = (pixel >>> 24) & 255;
|
||||
int red = (pixel >>> 16) & 255;
|
||||
int green = (pixel >>> 8) & 255;
|
||||
int blue = pixel & 255;
|
||||
if (alpha == 0 || (red >= 245 && green >= 245 && blue >= 245)) {
|
||||
continue;
|
||||
}
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
}
|
||||
if (maxX < minX || maxY < minY) {
|
||||
return source;
|
||||
}
|
||||
int padding = 2;
|
||||
int left = Math.max(0, minX - padding);
|
||||
int top = Math.max(0, minY - padding);
|
||||
int right = Math.min(source.getWidth() - 1, maxX + padding);
|
||||
int bottom = Math.min(source.getHeight() - 1, maxY + padding);
|
||||
return makeSignatureBackgroundTransparent(source.getSubimage(left, top, right - left + 1, bottom - top + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 签字图片只保留笔迹,白色画布转换为透明背景,避免许可证中出现图片矩形边界。
|
||||
*/
|
||||
private BufferedImage makeSignatureBackgroundTransparent(BufferedImage image) {
|
||||
BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
int pixel = image.getRGB(x, y);
|
||||
int alpha = (pixel >>> 24) & 255;
|
||||
int red = (pixel >>> 16) & 255;
|
||||
int green = (pixel >>> 8) & 255;
|
||||
int blue = pixel & 255;
|
||||
if (alpha == 0 || (red >= 245 && green >= 245 && blue >= 245)) {
|
||||
result.setRGB(x, y, 0);
|
||||
continue;
|
||||
}
|
||||
result.setRGB(x, y, pixel);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部分签字图片由前端画板导出时带有完整黑色边框。边框不属于笔迹,四边同时存在时先去除,
|
||||
* 避免后续裁剪把整张白色画布当作签字内容。
|
||||
*/
|
||||
private BufferedImage removeSignatureFrame(BufferedImage image) {
|
||||
int top = findSolidDarkEdge(image, true, false);
|
||||
int bottom = findSolidDarkEdge(image, true, true);
|
||||
int left = findSolidDarkEdge(image, false, false);
|
||||
int right = findSolidDarkEdge(image, false, true);
|
||||
if (top == 0 || bottom == 0 || left == 0 || right == 0) {
|
||||
return image;
|
||||
}
|
||||
int width = image.getWidth() - left - right;
|
||||
int height = image.getHeight() - top - bottom;
|
||||
if (width < 2 || height < 2) {
|
||||
return image;
|
||||
}
|
||||
return image.getSubimage(left, top, width, height);
|
||||
}
|
||||
|
||||
private int findSolidDarkEdge(BufferedImage image, boolean horizontal, boolean reverse) {
|
||||
int edgeLength = horizontal ? image.getHeight() : image.getWidth();
|
||||
int crossLength = horizontal ? image.getWidth() : image.getHeight();
|
||||
int scanLimit = Math.min(20, Math.max(1, edgeLength / 10));
|
||||
int thickness = 0;
|
||||
for (int offset = 0; offset < scanLimit; offset++) {
|
||||
int edge = reverse ? edgeLength - 1 - offset : offset;
|
||||
int darkCount = 0;
|
||||
for (int cross = 0; cross < crossLength; cross++) {
|
||||
int pixel = horizontal ? image.getRGB(cross, edge) : image.getRGB(edge, cross);
|
||||
int alpha = (pixel >>> 24) & 255;
|
||||
int red = (pixel >>> 16) & 255;
|
||||
int green = (pixel >>> 8) & 255;
|
||||
int blue = pixel & 255;
|
||||
if (alpha > 0 && red < 50 && green < 50 && blue < 50) {
|
||||
darkCount++;
|
||||
}
|
||||
}
|
||||
if ((double) darkCount / crossLength < 0.98D) {
|
||||
break;
|
||||
}
|
||||
thickness++;
|
||||
}
|
||||
return thickness;
|
||||
}
|
||||
|
||||
private String findStepActor(HighWorkPermitInfo info, String... stepKeywords) {
|
||||
Set<String> actors = new LinkedHashSet<>();
|
||||
for (HighWorkPermitStepInfo step : findSteps(info, stepKeywords)) {
|
||||
if (isSigned(step) && StringUtils.isNotBlank(step.getActUserName())) {
|
||||
actors.add(step.getActUserName());
|
||||
}
|
||||
}
|
||||
return String.join("、", actors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按原始步骤键排序后查找步骤,保证多人签字步骤的导出顺序稳定。
|
||||
*/
|
||||
private List<HighWorkPermitStepInfo> findSteps(HighWorkPermitInfo info, String... stepKeywords) {
|
||||
List<HighWorkPermitStepInfo> result = new ArrayList<>();
|
||||
if (info.getStepInfoMap() == null) {
|
||||
return result;
|
||||
}
|
||||
List<String> keys = new ArrayList<>(info.getStepInfoMap().keySet());
|
||||
Collections.sort(keys);
|
||||
for (String key : keys) {
|
||||
HighWorkPermitStepInfo step = info.getStepInfoMap().get(key);
|
||||
if (step == null || StringUtils.isBlank(step.getStepName())) {
|
||||
continue;
|
||||
}
|
||||
String stepName = step.getStepName();
|
||||
for (String keyword : stepKeywords) {
|
||||
if (stepName.contains(keyword)) {
|
||||
result.add(step);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isSigned(HighWorkPermitStepInfo step) {
|
||||
Integer status = step.getStatus();
|
||||
return Integer.valueOf(1).equals(status)
|
||||
|| StringUtils.isNotBlank(step.getSignTime())
|
||||
|| StringUtils.isNotBlank(step.getSignPath());
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
for (String current : values) {
|
||||
if (StringUtils.isNotBlank(current)) {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private byte[] buildZip(Map<String, byte[]> pdfFiles) throws Exception {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
|
||||
for (Map.Entry<String, byte[]> file : pdfFiles.entrySet()) {
|
||||
zipOutputStream.putNextEntry(new ZipEntry(file.getKey()));
|
||||
zipOutputStream.write(file.getValue());
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
zipOutputStream.finish();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private String buildPdfFileName(EightworkInfoDO work, Map<String, Integer> fileNameCounts) {
|
||||
String checkNo = StringUtils.isBlank(work.getCheckNo()) ? String.valueOf(work.getId()) : work.getCheckNo();
|
||||
String baseName = sanitizeFileName("高处作业安全许可证_" + checkNo);
|
||||
Integer count = fileNameCounts.get(baseName);
|
||||
fileNameCounts.put(baseName, count == null ? 1 : count + 1);
|
||||
return count == null ? baseName + ".pdf" : baseName + "_" + (count + 1) + ".pdf";
|
||||
}
|
||||
|
||||
private String sanitizeFileName(String fileName) {
|
||||
return fileName.replaceAll("[\\\\/:*?\"<>|\\r\\n]", "_");
|
||||
}
|
||||
|
||||
private void writeResponse(HttpServletResponse response, byte[] bytes, String contentType, String fileName) throws Exception {
|
||||
response.reset();
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType(contentType);
|
||||
response.setHeader("Content-Disposition",
|
||||
"attachment; filename*=UTF-8''" + URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"));
|
||||
response.setContentLength(bytes.length);
|
||||
try (OutputStream outputStream = response.getOutputStream()) {
|
||||
outputStream.write(bytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private String value(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单选字符串或多选数组转换为模板使用的文本。
|
||||
*/
|
||||
private String textValue(Object value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
if (value instanceof Iterable) {
|
||||
List<String> values = new ArrayList<>();
|
||||
for (Object item : (Iterable<?>) value) {
|
||||
if (item != null && StringUtils.isNotBlank(String.valueOf(item))) {
|
||||
values.add(String.valueOf(item));
|
||||
}
|
||||
}
|
||||
return String.join("、", values);
|
||||
}
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
private static class MeasureValue {
|
||||
private final String content;
|
||||
private final String signPath;
|
||||
private final Integer orderBy;
|
||||
|
||||
private MeasureValue(String content, String signPath, Integer orderBy) {
|
||||
this.content = content;
|
||||
this.signPath = signPath;
|
||||
this.orderBy = orderBy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package com.zcloud.eightwork.command.query;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 高处作业安全许可证导出详情对象。
|
||||
*
|
||||
* <p>对象字段对应 eightwork_info.info 中的作业基础信息。原始 JSON 中的步骤仍保持
|
||||
* step_{步骤ID}、step_{步骤ID}_{序号} 结构,解析时统一归集到 stepInfoMap,避免导出逻辑直接操作 JSON。</p>
|
||||
*/
|
||||
@Data
|
||||
public class HighWorkPermitInfo {
|
||||
|
||||
private String implementationUnit;
|
||||
private String implementationUnitName;
|
||||
private String workUnit;
|
||||
private String workUnitName;
|
||||
private String constructionUnit;
|
||||
private String workLocation;
|
||||
private String workPlace;
|
||||
private String operationLocation;
|
||||
private String workAddress;
|
||||
private String workSite;
|
||||
private String workPosition;
|
||||
private String workContent;
|
||||
private String operationContent;
|
||||
private String workDescription;
|
||||
private String applyTime;
|
||||
private String applicationTime;
|
||||
private String workStartTime;
|
||||
private String startTime;
|
||||
private String workEndTime;
|
||||
private String endTime;
|
||||
private String workLevel;
|
||||
private String workLevelName;
|
||||
private String operationLevel;
|
||||
private String highWorkLevel;
|
||||
|
||||
/** 项目批准人。 */
|
||||
private Long projectApproverId;
|
||||
private String projectApproverName;
|
||||
private String projectApprovalName;
|
||||
|
||||
/** 方案编制人。 */
|
||||
private Long planPreparerId;
|
||||
private String planPreparerName;
|
||||
|
||||
/** 方案批准人。 */
|
||||
private Long planApproverId;
|
||||
private String planApproverName;
|
||||
private String schemeApproverName;
|
||||
private String planCompilerName;
|
||||
private String schemeCompilerName;
|
||||
|
||||
/** 工作负责人。 */
|
||||
private Long workChargeUserId;
|
||||
private String workChargeUserName;
|
||||
private String workLeaderName;
|
||||
private String workResponsibleName;
|
||||
private String operationLeaderName;
|
||||
|
||||
/** 高处作业人,多选时名称字段保存多个作业人名称。 */
|
||||
private Object workUserId;
|
||||
private Object workUserName;
|
||||
private String highWorkPersonName;
|
||||
private String workPersonName;
|
||||
private String operationPersonName;
|
||||
|
||||
/** 监护人,多选时名称字段保存多个监护人名称。 */
|
||||
private Object workGuardianUserId;
|
||||
private Object workGuardianUserName;
|
||||
private String guardianName;
|
||||
private String monitorName;
|
||||
|
||||
/**
|
||||
* 按原始步骤键保存的步骤信息,键示例:step_3、step_3_1。
|
||||
*/
|
||||
private Map<String, HighWorkPermitStepInfo> stepInfoMap = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 将 eightwork_info.info 解析为导出对象,并保留历史步骤键的排序依据。
|
||||
*
|
||||
* @param infoJson 作业票详情 JSON 字符串
|
||||
* @return 高处作业许可证导出详情对象
|
||||
*/
|
||||
public static HighWorkPermitInfo parse(String infoJson) {
|
||||
HighWorkPermitInfo info = JSON.parseObject(infoJson, HighWorkPermitInfo.class);
|
||||
if (info == null) {
|
||||
info = new HighWorkPermitInfo();
|
||||
}
|
||||
|
||||
Map<String, Object> values = JSON.parseObject(infoJson,
|
||||
new TypeReference<LinkedHashMap<String, Object>>() {
|
||||
});
|
||||
Map<String, HighWorkPermitStepInfo> stepInfoMap = new LinkedHashMap<>();
|
||||
if (values != null) {
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (!entry.getKey().startsWith("step_")) {
|
||||
continue;
|
||||
}
|
||||
HighWorkPermitStepInfo stepInfo = JSON.parseObject(JSON.toJSONString(entry.getValue()),
|
||||
HighWorkPermitStepInfo.class);
|
||||
if (stepInfo != null) {
|
||||
stepInfoMap.put(entry.getKey(), stepInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
info.setStepInfoMap(stepInfoMap);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.zcloud.eightwork.command.query;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 高处作业安全许可证导出的签字步骤对象。
|
||||
*
|
||||
* <p>对应 eightwork_info.info 中 step_{步骤ID} 或 step_{步骤ID}_{序号} 的值。</p>
|
||||
*/
|
||||
@Data
|
||||
public class HighWorkPermitStepInfo {
|
||||
|
||||
private String stepName;
|
||||
private String actUserName;
|
||||
private Integer status;
|
||||
private String signTime;
|
||||
private String signPath;
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.zcloud.eightwork.command.EightworkInfoRemoveExe;
|
|||
import com.zcloud.eightwork.command.EightworkInfoUpdateExe;
|
||||
import com.zcloud.eightwork.command.EightworkInfoWithdrawExe;
|
||||
import com.zcloud.eightwork.command.query.EightworkInfoQueryExe;
|
||||
import com.zcloud.eightwork.command.query.HighWorkPermitExportExe;
|
||||
import com.zcloud.eightwork.domain.gateway.TaskLogGateway;
|
||||
import com.zcloud.eightwork.domain.model.TaskLogE;
|
||||
import com.zcloud.eightwork.domain.model.enums.TaskLogStatus;
|
||||
|
|
@ -17,6 +18,7 @@ import com.zcloud.eightwork.dto.DangerWorkStatisticsQry;
|
|||
import com.zcloud.eightwork.dto.EightworkInfoAddCmd;
|
||||
import com.zcloud.eightwork.dto.EightworkInfoPageQry;
|
||||
import com.zcloud.eightwork.dto.EightworkInfoUpdateCmd;
|
||||
import com.zcloud.eightwork.dto.HighWorkPermitExportQry;
|
||||
import com.zcloud.eightwork.dto.ScreenDangerWorkDataStatisticsQry;
|
||||
import com.zcloud.eightwork.dto.ScreenDepartmentStatisticsQry;
|
||||
import com.zcloud.eightwork.dto.ScreenStatisticsQry;
|
||||
|
|
@ -65,6 +67,7 @@ public class EightworkInfoServiceImpl implements EightworkInfoServiceI {
|
|||
private final TaskLogGateway taskLogGateway;
|
||||
private final EightworkInfoRepository eightworkInfoRepository;
|
||||
private final EightworkInfoWithdrawExe eightworkInfoWithdrawExe;
|
||||
private final HighWorkPermitExportExe highWorkPermitExportExe;
|
||||
|
||||
@Override
|
||||
public EightworkInfoCO queryById(Long id) {
|
||||
|
|
@ -230,5 +233,10 @@ public class EightworkInfoServiceImpl implements EightworkInfoServiceI {
|
|||
public List<ScreenWorkPieStatisticsCO> screenWorkPieStatistics(ScreenWorkPieStatisticsQry qry) {
|
||||
return eightworkInfoQueryExe.screenWorkPieStatistics(qry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportHighWorkPermit(HighWorkPermitExportQry qry, HttpServletResponse response) {
|
||||
highWorkPermitExportExe.execute(qry, response);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.zcloud.eightwork.dto.DangerWorkStatisticsQry;
|
|||
import com.zcloud.eightwork.dto.EightworkInfoAddCmd;
|
||||
import com.zcloud.eightwork.dto.EightworkInfoPageQry;
|
||||
import com.zcloud.eightwork.dto.EightworkInfoUpdateCmd;
|
||||
import com.zcloud.eightwork.dto.HighWorkPermitExportQry;
|
||||
import com.zcloud.eightwork.dto.ScreenDangerWorkDataStatisticsQry;
|
||||
import com.zcloud.eightwork.dto.ScreenDepartmentStatisticsQry;
|
||||
import com.zcloud.eightwork.dto.ScreenStatisticsQry;
|
||||
|
|
@ -85,5 +86,7 @@ public interface EightworkInfoServiceI {
|
|||
ScreenWorkStatusStatisticsCO screenWorkStatusStatistics(ScreenWorkStatusStatisticsQry qry);
|
||||
|
||||
List<ScreenWorkPieStatisticsCO> screenWorkPieStatistics(ScreenWorkPieStatisticsQry qry);
|
||||
|
||||
void exportHighWorkPermit(HighWorkPermitExportQry qry, HttpServletResponse response);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.zcloud.eightwork.dto;
|
||||
|
||||
import com.alibaba.cola.dto.Query;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 高处作业安全许可证导出请求
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class HighWorkPermitExportQry extends Query {
|
||||
|
||||
@ApiModelProperty(value = "作业票主表ID列表", name = "ids", required = true)
|
||||
@NotEmpty(message = "作业票主表ID不能为空")
|
||||
private List<Long> ids;
|
||||
}
|
||||
|
|
@ -54,5 +54,7 @@ public interface EightworkInfoMapper extends BaseMapper<EightworkInfoDO> {
|
|||
ScreenWorkStatusStatisticsDTO screenWorkStatusStatistics(@Param("params") Map<String, Object> params);
|
||||
|
||||
List<ScreenWorkPieStatisticsDTO> screenWorkPieStatistics(@Param("params") Map<String, Object> params);
|
||||
|
||||
String getCorpNameById(@Param("corpinfoId") Long corpinfoId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ public interface EightworkInfoRepository extends BaseRepository<EightworkInfoDO>
|
|||
|
||||
List<ScreenWorkPieStatisticsDTO> screenWorkPieStatistics(Map<String, Object> params);
|
||||
|
||||
String getCorpNameById(Long corpinfoId);
|
||||
|
||||
/**
|
||||
* 使用 CAS 方式更新 lockFlag(乐观锁)
|
||||
* 只有当 lockFlag 和 status 都匹配时才更新
|
||||
|
|
|
|||
|
|
@ -130,6 +130,11 @@ public class EightworkInfoRepositoryImpl extends BaseRepositoryImpl<EightworkInf
|
|||
return eightworkInfoMapper.screenWorkPieStatistics(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCorpNameById(Long corpinfoId) {
|
||||
return eightworkInfoMapper.getCorpNameById(corpinfoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateLockFlagToLocked(Long id, Integer expectedLockFlag, Integer newLockFlag, Integer expectedStatus) {
|
||||
LambdaUpdateWrapper<EightworkInfoDO> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
|
|
|
|||
|
|
@ -340,4 +340,12 @@
|
|||
group by t.work_type
|
||||
</select>
|
||||
|
||||
<select id="getCorpNameById" resultType="java.lang.String">
|
||||
select corp_name
|
||||
from vi_corp_info
|
||||
where id = #{corpinfoId}
|
||||
and delete_enum = 'FALSE'
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
|
|||
Loading…
Reference in New Issue