CostProjectTaskSurveyGenericController.java 139 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929
  1. package com.hotent.enterpriseDeclare.controller.material;
  2. import com.alibaba.fastjson.JSON;
  3. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5. import com.hotent.base.annotation.ApiGroup;
  6. import com.hotent.base.constants.ApiGroupConsts;
  7. import com.hotent.base.model.CommonResult;
  8. import com.hotent.base.util.StringUtil;
  9. import com.hotent.baseInfo.manager.CostCatalogSurveyManager;
  10. import com.hotent.baseInfo.model.CostCatalogSurvey;
  11. import com.hotent.enterpriseDeclare.manager.CostAuditPeriodRecordManager;
  12. import com.hotent.enterpriseDeclare.manager.CostSurveyTemplateUploadDataManager;
  13. import com.hotent.enterpriseDeclare.model.CostAuditPeriodRecord;
  14. import com.hotent.enterpriseDeclare.model.CostSurveyTemplateUploadData;
  15. import com.hotent.project.manager.CostProjectApprovalManager;
  16. import com.hotent.project.manager.CostProjectTaskManager;
  17. import com.hotent.project.manager.CostProjectTaskMaterialManager;
  18. import com.hotent.project.model.CostProjectApproval;
  19. import com.hotent.project.model.CostProjectTask;
  20. import com.hotent.project.model.CostProjectTaskMaterial;
  21. import com.hotent.surveyinfo.dao.*;
  22. import com.hotent.surveyinfo.manager.*;
  23. import com.hotent.surveyinfo.model.*;
  24. import com.hotent.surveyinfo.model.dto.CostItemData;
  25. import com.hotent.baseInfo.manager.CostDictDataManager;
  26. import com.hotent.baseInfo.model.CostDictData;
  27. import io.swagger.annotations.Api;
  28. import io.swagger.annotations.ApiOperation;
  29. import io.swagger.annotations.ApiParam;
  30. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  31. import org.apache.poi.ss.usermodel.*;
  32. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  33. import org.nfunk.jep.JEP;
  34. import org.nfunk.jep.function.Str;
  35. import org.springframework.beans.factory.annotation.Autowired;
  36. import org.springframework.web.bind.annotation.*;
  37. import org.springframework.web.multipart.MultipartFile;
  38. import javax.servlet.http.HttpServletResponse;
  39. import java.io.InputStream;
  40. import java.net.URLEncoder;
  41. import java.time.format.DateTimeFormatter;
  42. import java.util.*;
  43. import java.util.stream.Collectors;
  44. /**
  45. * 通用调查表接口(整合成本调查表和财务数据表)
  46. *
  47. * @author 超级管理员
  48. * @company 山西清众科技股份有限公司
  49. * @since 2025-01-27
  50. */
  51. @RestController
  52. @RequestMapping("/api/surveyTemplate/v1/")
  53. @Api(tags = "通用调查表接口")
  54. @ApiGroup(group = {ApiGroupConsts.GROUP_COST})
  55. public class CostProjectTaskSurveyGenericController {
  56. // 成本调查表相关
  57. @Autowired
  58. private CostSurveyTemplateManager costSurveyTemplateManager;
  59. @Autowired
  60. private CostSurveyTemplateVersionManager costSurveyTemplateVersionManager;
  61. @Autowired
  62. private CostSurveyTemplateHeadersDao costSurveyTemplateHeadersDao;
  63. @Autowired
  64. private CostSurveyTemplateItemsDao costSurveyTemplateItemsDao;
  65. // 财务数据表相关
  66. @Autowired
  67. private CostSurveyFdTemplateManager costSurveyFdTemplateManager;
  68. @Autowired
  69. private CostSurveyFdTemplateVersionManager costSurveyFdTemplateVersionManager;
  70. @Autowired
  71. private CostSurveyFdTemplateHeadersDao costSurveyFdTemplateHeadersDao;
  72. @Autowired
  73. private CostSurveyFdTemplateItemsDao costSurveyFdTemplateItemsDao;
  74. // 核定表
  75. @Autowired
  76. private CostVerifyTemplateManager costVerifyTemplateManager;
  77. @Autowired
  78. private CostVerifyTemplateHeadersDao costVerifyTemplateHeadersDao;
  79. @Autowired
  80. private CostVerifyTemplateItemsDao costVerifyTemplateItemsDao;
  81. // 通用组件
  82. @Autowired
  83. private CostSurveyTemplateUploadManager costSurveyTemplateUploadManager;
  84. @Autowired
  85. private CostSurveyTemplateUploadDataManager costSurveyTemplateUploadDataManager;
  86. @Autowired
  87. private CostProjectTaskMaterialManager costProjectTaskMaterialManager;
  88. @Autowired
  89. private CostAuditPeriodRecordManager costAuditPeriodRecordManager;
  90. @Autowired
  91. private CostProjectApprovalManager costProjectApprovalManager;
  92. @Autowired
  93. private CostProjectTaskManager costProjectTaskManager;
  94. @Autowired
  95. private CostSurveyTemplateHeadersManager costSurveyTemplateHeadersManager;
  96. @Autowired
  97. private CostSurveyFdTemplateHeadersManager costSurveyFdTemplateHeadersManager;
  98. @Autowired
  99. private CostDictDataManager costDictDataManager;
  100. /**
  101. * 企业报送-调查表-列表
  102. *
  103. * @param taskId 任务ID
  104. * @param catalogId 目录ID(成本调查表使用)
  105. * @param type 类型:1-成本调查表 2-财务数据表
  106. * @return 上传记录列表
  107. * @throws Exception
  108. */
  109. @GetMapping(value = "/listByTaskId")
  110. @ApiOperation(value = "根据任务ID获取调查表列表", httpMethod = "GET")
  111. public CommonResult<?> listByTaskId(
  112. @ApiParam(name = "taskId", value = "任务ID", required = true)
  113. @RequestParam(required = true) String taskId,
  114. @ApiParam(name = "catalogId", value = "目录ID(成本调查表使用)")
  115. @RequestParam(required = false) String catalogId,
  116. @ApiParam(name = "type", value = "类型:1-成本调查表 2-财务数据表", required = true)
  117. @RequestParam(required = true) String type) throws Exception {
  118. switch (type) {
  119. case "1": {
  120. // 成本调查表逻辑
  121. List<CostSurveyTemplateUpload> uploadList = costSurveyTemplateUploadManager.listByTaskId(taskId);
  122. //创建记录
  123. if (uploadList.isEmpty()) {
  124. CostProjectTask task = costProjectTaskManager.getById(taskId);
  125. List<CostSurveyTemplate> costSurveyTemplates = costSurveyTemplateManager.taskListByCatalogId(task.getCatalogId());
  126. for (CostSurveyTemplate template : costSurveyTemplates) {
  127. CostSurveyTemplateUpload upload = new CostSurveyTemplateUpload();
  128. upload.setSurveyTemplateId(template.getSurveyTemplateId());
  129. upload.setSurveyTemplateName(template.getSurveyTemplateName());
  130. upload.setTemplateType(template.getTemplateType());
  131. upload.setId(null);
  132. upload.setTaskId(taskId);
  133. upload.setIsUpload("0");
  134. upload.setAuditedStatus("0");
  135. upload.setCreateTime(java.time.LocalDateTime.now());
  136. upload.setUploadTime(java.time.LocalDateTime.now());
  137. costSurveyTemplateUploadManager.save(upload);
  138. }
  139. }
  140. return CommonResult.ok().value(costSurveyTemplateUploadManager.listByTaskId(taskId));
  141. }
  142. case "2": {
  143. // 财务数据表逻辑
  144. QueryWrapper<CostProjectTaskMaterial> wrapper = new QueryWrapper<>();
  145. wrapper.eq("task_id", taskId);
  146. List<CostProjectTaskMaterial> materialList = costProjectTaskMaterialManager.list(wrapper);
  147. return CommonResult.ok().value(materialList);
  148. }
  149. default:
  150. return CommonResult.error().message("不支持的类型");
  151. }
  152. }
  153. /**
  154. * 企业报送-调查表-获取所需表格字段
  155. *
  156. * @param surveyTemplateId 调查表模板ID
  157. * @param type 类型:1-成本调查表 2-财务数据表
  158. * @return 指标项数据列表(带key-value拼接)
  159. */
  160. @GetMapping(value = "/listItemsByCurrentTemplateId")
  161. @ApiOperation(value = "根据调查表现行模板ID获取所有指标数据", httpMethod = "GET")
  162. public CommonResult<CostItemData> listItemsByCurrentTemplateId(
  163. @ApiParam(name = "surveyTemplateId", value = "关联的调查表ID", required = true)
  164. @RequestParam(required = true) String surveyTemplateId,
  165. @ApiParam(name = "type", value = "类型:1-成本调查表 2-财务数据表", required = true)
  166. @RequestParam(required = true) String type) throws Exception {
  167. switch (type) {
  168. case "1": {
  169. // 成本调查表逻辑
  170. CostSurveyTemplateVersion versionTemplate = costSurveyTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  171. if (versionTemplate == null) {
  172. return CommonResult.<CostItemData>ok().value(new CostItemData());
  173. }
  174. List<CostSurveyTemplateItems> items = costSurveyTemplateItemsDao.selectBySurveyTemplateIdAndVersion(surveyTemplateId, versionTemplate.getId());
  175. // 使用已有的manager方法替代新接口
  176. List<CostSurveyTemplateHeaders> headList = costSurveyTemplateHeadersManager.listVisibleBySurveyTemplateIdAndVersion(surveyTemplateId, versionTemplate.getId());
  177. CostItemData costItemData = buildCostItemData(items, headList);
  178. return CommonResult.<CostItemData>ok().value(costItemData);
  179. }
  180. case "2": {
  181. // 财务数据表逻辑
  182. CostSurveyFdTemplateVersion versionTemplate = costSurveyFdTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  183. if (versionTemplate == null) {
  184. return CommonResult.<CostItemData>ok().value(new CostItemData());
  185. }
  186. List<CostSurveyFdTemplateItems> items = costSurveyFdTemplateItemsDao.selectBySurveyTemplateIdAndVersion(surveyTemplateId, versionTemplate.getId());
  187. // 使用已有的manager方法替代新接口
  188. List<CostSurveyFdTemplateHeaders> headList = costSurveyFdTemplateHeadersManager.listVisibleBySurveyTemplateIdAndVersion(surveyTemplateId, versionTemplate.getId());
  189. CostItemData costItemData = buildCostItemDataFd(items, headList);
  190. return CommonResult.<CostItemData>ok().value(costItemData);
  191. }
  192. default:
  193. return CommonResult.<CostItemData>error().message("不支持的类型");
  194. }
  195. }
  196. /**
  197. * 企业报送-调查表-在线填报
  198. *
  199. * @param dataList 数据列表
  200. * @return 保存结果
  201. */
  202. @PostMapping(value = "/saveUploadData")
  203. @ApiOperation(value = "保存调查表模板上传数据", httpMethod = "POST")
  204. public CommonResult<String> saveUploadData(
  205. @ApiParam(name = "dataList", value = "数据列表", required = true)
  206. @RequestBody List<CostSurveyTemplateUploadData> dataList) throws Exception {
  207. System.err.println(JSON.toJSON(dataList));
  208. if (dataList == null || dataList.isEmpty()) {
  209. return CommonResult.<String>error().message("数据列表不能为空");
  210. }
  211. // 从第一条数据中提取信息
  212. CostSurveyTemplateUploadData firstData = dataList.get(0);
  213. String refId = firstData.getRefId();
  214. if (refId == null) {
  215. refId = firstData.getUploadId();
  216. }
  217. String surveyTemplateId = firstData.getSurveyTemplateId();
  218. String type = firstData.getType();
  219. // 判断模板类型
  220. String templateType = null;
  221. if (StringUtil.isNotEmpty(surveyTemplateId)) {
  222. if ("1".equals(type)) {
  223. CostSurveyTemplate template = costSurveyTemplateManager.getDetail(surveyTemplateId);
  224. if (template != null) {
  225. templateType = template.getTemplateType();
  226. }
  227. } else if ("2".equals(type)) {
  228. CostSurveyFdTemplate template = costSurveyFdTemplateManager.getDetail(surveyTemplateId);
  229. if (template != null) {
  230. templateType = template.getTemplateType();
  231. }
  232. }
  233. } else if (StringUtil.isNotEmpty(refId)) {
  234. if ("1".equals(type)) {
  235. CostSurveyTemplateUpload upload = costSurveyTemplateUploadManager.get(refId);
  236. if (upload != null && StringUtil.isNotEmpty(upload.getSurveyTemplateId())) {
  237. CostSurveyTemplate template = costSurveyTemplateManager.getDetail(upload.getSurveyTemplateId());
  238. if (template != null) {
  239. templateType = template.getTemplateType();
  240. }
  241. }
  242. } else if ("2".equals(type)) {
  243. CostProjectTaskMaterial material = costProjectTaskMaterialManager.getById(refId);
  244. if (material != null && StringUtil.isNotEmpty(material.getTemplateId())) {
  245. CostSurveyFdTemplate template = costSurveyFdTemplateManager.getDetail(material.getTemplateId());
  246. if (template != null) {
  247. templateType = template.getTemplateType();
  248. }
  249. }
  250. }
  251. }
  252. // 如果是动态表(3),需要中间表
  253. if ("3".equals(templateType)) {
  254. String periodRecordId = firstData.getPeriodRecordId();
  255. if (StringUtil.isEmpty(periodRecordId)) {
  256. return CommonResult.<String>error().message("动态表需要先创建监审期间记录,periodRecordId不能为空");
  257. }
  258. }
  259. // 设置 type
  260. for (CostSurveyTemplateUploadData data : dataList) {
  261. data.setType(type);
  262. }
  263. costSurveyTemplateUploadDataManager.saveData(dataList);
  264. // 只有成本调查表需要更新upload状态dataList = {ArrayList@29238} size = 27
  265. if ("1".equals(type) && refId != null && !refId.isEmpty()) {
  266. CostSurveyTemplateUpload upload = costSurveyTemplateUploadManager.getById(refId);
  267. if (upload != null) {
  268. upload.setIsUpload("1");
  269. if ("2".equals(upload.getAuditedStatus())) {
  270. upload.setAuditedStatus("0");
  271. }
  272. costSurveyTemplateUploadManager.updateById(upload);
  273. }
  274. }
  275. // 如果是财务数据表,更新材料上传状态
  276. if ("2".equals(type) && StringUtil.isNotEmpty(refId)) {
  277. CostProjectTaskMaterial material = costProjectTaskMaterialManager.getById(refId);
  278. if (material != null) {
  279. material.setIsUpload("1");
  280. costProjectTaskMaterialManager.updateById(material);
  281. }
  282. }
  283. return CommonResult.<String>ok().message("保存数据成功");
  284. }
  285. /**
  286. * 企业报送-调查表-在线填报记录查看
  287. *
  288. * @return 数据列表
  289. */
  290. @PostMapping(value = "/getUploadData")
  291. @ApiOperation(value = "查询调查表模板上传数据", httpMethod = "POST")
  292. public CommonResult<List<CostSurveyTemplateUploadData>> getUploadData(
  293. @ApiParam(name = "queryData", value = "查询条件", required = true)
  294. @RequestBody CostSurveyTemplateUploadData queryData) throws Exception {
  295. if (queryData == null) {
  296. return CommonResult.<List<CostSurveyTemplateUploadData>>error().message("uploadId不能为空");
  297. }
  298. // 根据refId查询数据
  299. QueryWrapper<CostSurveyTemplateUploadData> wrapper = new QueryWrapper<>();
  300. wrapper.eq("type", queryData.getType())
  301. .eq("is_deleted", "0");
  302. if (queryData.getType().equals("3")) {
  303. wrapper.eq("task_id", queryData.getTaskId());
  304. } else {
  305. wrapper.eq("ref_id", queryData.getUploadId());
  306. }
  307. // 如果指定了监审期间记录ID,添加过滤条件
  308. if (StringUtil.isNotEmpty(queryData.getPeriodRecordId())) {
  309. wrapper.eq("period_record_id", queryData.getPeriodRecordId());
  310. }
  311. wrapper.orderByAsc("rowid", "rkey");
  312. List<CostSurveyTemplateUploadData> dataList = costSurveyTemplateUploadDataManager.list(wrapper);
  313. for (CostSurveyTemplateUploadData costSurveyTemplateUploadData : dataList) {
  314. costSurveyTemplateUploadData.setUploadId(costSurveyTemplateUploadData.getRefId());
  315. }
  316. return CommonResult.<List<CostSurveyTemplateUploadData>>ok().value(dataList);
  317. }
  318. /**
  319. * 企业报送-调查表-动态表-新增监审期间记录
  320. *
  321. * @param record 监审期间记录
  322. * @return 保存结果
  323. */
  324. @PostMapping(value = "/addPeriodRecord")
  325. @ApiOperation(value = "新增监审期间记录(动态表使用)", httpMethod = "POST")
  326. public CommonResult<CostAuditPeriodRecord> addPeriodRecord(
  327. @ApiParam(name = "record", value = "监审期间记录", required = true)
  328. @RequestBody CostAuditPeriodRecord record) throws Exception {
  329. if (record == null) {
  330. return CommonResult.<CostAuditPeriodRecord>error().message("记录不能为空");
  331. }
  332. if (StringUtil.isEmpty(record.getTaskId())) {
  333. return CommonResult.<CostAuditPeriodRecord>error().message("taskId不能为空");
  334. }
  335. if (StringUtil.isEmpty(record.getAuditPeriod())) {
  336. return CommonResult.<CostAuditPeriodRecord>error().message("auditPeriod不能为空");
  337. }
  338. // 校验项目监审期间
  339. CostProjectTask task = costProjectTaskManager.getById(record.getTaskId());
  340. CostProjectApproval approval = costProjectApprovalManager.getById(task.getProjectId());
  341. if (approval == null) {
  342. return CommonResult.<CostAuditPeriodRecord>error().message("项目不存在");
  343. }
  344. if (!Arrays.asList(approval.getAuditPeriod().split(",")).contains(record.getAuditPeriod())) {
  345. return CommonResult.<CostAuditPeriodRecord>error().message("项目所属监审期间不存在");
  346. }
  347. // 检查是否已存在相同的期间记录
  348. CostAuditPeriodRecord existRecord = costAuditPeriodRecordManager.getOne(
  349. new QueryWrapper<CostAuditPeriodRecord>()
  350. .eq("type", record.getType())
  351. .eq("task_id", record.getTaskId())
  352. .eq("audit_period", record.getAuditPeriod())
  353. );
  354. if (existRecord != null) {
  355. return CommonResult.<CostAuditPeriodRecord>error().message("该监审期间记录已存在");
  356. }
  357. // 设置填报时间为当前时间
  358. if (record.getReportingTime() == null) {
  359. record.setReportingTime(java.time.LocalDateTime.now());
  360. }
  361. record.setIsDeleted("0");
  362. record.setType(record.getType());
  363. costAuditPeriodRecordManager.save(record);
  364. return CommonResult.<CostAuditPeriodRecord>ok().value(record).message("新增监审期间记录成功");
  365. }
  366. /**
  367. * 企业报送-调查表-动态表-查询监审期间记录列表
  368. *
  369. * @param queryData 查询条件(包含uploadId和auditedUnitId)
  370. * @return 记录列表
  371. */
  372. @PostMapping(value = "/listPeriodRecord")
  373. @ApiOperation(value = "查询监审期间记录列表(动态表使用)", httpMethod = "POST")
  374. public CommonResult<List<CostAuditPeriodRecord>> listPeriodRecord(
  375. @ApiParam(name = "queryData", value = "查询条件", required = true)
  376. @RequestBody CostAuditPeriodRecord queryData) throws Exception {
  377. if (queryData == null || StringUtil.isEmpty(queryData.getUploadId()) || StringUtil.isEmpty(queryData.getAuditedUnitId())) {
  378. return CommonResult.<List<CostAuditPeriodRecord>>ok().value(new ArrayList<>());
  379. }
  380. List<CostAuditPeriodRecord> recordList = costAuditPeriodRecordManager.listByUploadIdAndAuditedUnitId(queryData.getUploadId(), queryData.getAuditedUnitId(), queryData.getType());
  381. return CommonResult.<List<CostAuditPeriodRecord>>ok().value(recordList);
  382. }
  383. /**
  384. * 企业报送-调查表-动态表-删除监审期间记录
  385. *
  386. * @param id 记录ID
  387. * @param type 类型:1-成本调查表 2-财务数据表
  388. * @return 删除结果
  389. */
  390. @PostMapping(value = "/deletePeriodRecord")
  391. @ApiOperation(value = "删除监审期间记录(动态表使用)", httpMethod = "POST")
  392. public CommonResult<String> deletePeriodRecord(
  393. @ApiParam(name = "id", value = "记录ID", required = true)
  394. @RequestParam String id,
  395. @ApiParam(name = "type", value = "类型:1-成本调查表 2-财务数据表", required = true)
  396. @RequestParam(required = true) String type) throws Exception {
  397. if (StringUtil.isEmpty(id)) {
  398. return CommonResult.<String>error().message("id不能为空");
  399. }
  400. CostAuditPeriodRecord record = costAuditPeriodRecordManager.getById(id);
  401. if (record != null) {
  402. record.setIsDeleted("1");
  403. costAuditPeriodRecordManager.updateById(record);
  404. // 同时删除该期间下的所有上传数据
  405. QueryWrapper<CostSurveyTemplateUploadData> wrapper = new QueryWrapper<>();
  406. wrapper.eq("period_record_id", id)
  407. .eq("type", type);
  408. CostSurveyTemplateUploadData updateData = new CostSurveyTemplateUploadData();
  409. updateData.setIsDeleted("1");
  410. costSurveyTemplateUploadDataManager.update(updateData, wrapper);
  411. }
  412. return CommonResult.<String>ok().message("删除监审期间记录成功");
  413. }
  414. /**
  415. * 企业报送-调查表-审核
  416. *
  417. * @return 审核结果
  418. */
  419. @PostMapping(value = "/review")
  420. @ApiOperation(value = "调查表审核", httpMethod = "POST")
  421. public CommonResult<String> review(@RequestBody CostSurveyTemplateUpload costProjectTaskMaterial) {
  422. String id = costProjectTaskMaterial.getId();
  423. String type = costProjectTaskMaterial.getType();
  424. String auditedStatus = costProjectTaskMaterial.getAuditedStatus();
  425. switch (type) {
  426. case "1": {
  427. // 成本调查表审核逻辑
  428. CostSurveyTemplateUpload upload = costSurveyTemplateUploadManager.getById(id);
  429. if (upload != null) {
  430. upload.setAuditedStatus(auditedStatus);
  431. costSurveyTemplateUploadManager.updateById(upload);
  432. }
  433. break;
  434. }
  435. case "2": {
  436. // 财务数据表审核逻辑
  437. CostProjectTaskMaterial material = costProjectTaskMaterialManager.getById(id);
  438. if (material != null) {
  439. material.setAuditedStatus(auditedStatus);
  440. costProjectTaskMaterialManager.updateById(material);
  441. }
  442. break;
  443. }
  444. default:
  445. return CommonResult.<String>error().message("不支持的类型");
  446. }
  447. return CommonResult.<String>ok().message("审核成功");
  448. }
  449. /**
  450. * 导出调查表Excel
  451. *
  452. * @param surveyTemplateId 模板ID
  453. * @param versionId 版本ID(可选)
  454. * @param type 类型:1-成本调查表 2-财务数据表
  455. * @param taskId 任务ID(用于获取立项年限)
  456. * @param response HTTP响应对象
  457. */
  458. @GetMapping(value = "/exportExcel")
  459. @ApiOperation(value = "导出调查表Excel", httpMethod = "GET")
  460. public void exportExcel(
  461. @ApiParam(name = "surveyTemplateId", value = "模板ID", required = true)
  462. @RequestParam(required = true) String surveyTemplateId,
  463. @ApiParam(name = "versionId", value = "版本ID")
  464. @RequestParam(required = false) String versionId,
  465. @ApiParam(name = "type", value = "类型:1-成本调查表 2-财务数据表", required = true)
  466. @RequestParam(required = true) String type,
  467. @ApiParam(name = "taskId", value = "任务ID")
  468. @RequestParam(required = false) String taskId,
  469. HttpServletResponse response) throws Exception {
  470. String templateType = null;
  471. String templateName = null;
  472. // 获取立项年限(仅type=1和2需要)
  473. String[] auditPeriods = null;
  474. if (("1".equals(type) || "2".equals(type)) && StringUtil.isNotEmpty(taskId)) {
  475. CostProjectTask task = costProjectTaskManager.getById(taskId);
  476. if (task != null) {
  477. CostProjectApproval approval = costProjectApprovalManager.getById(task.getProjectId());
  478. if (approval != null && StringUtil.isNotEmpty(approval.getAuditPeriod())) {
  479. auditPeriods = approval.getAuditPeriod().split(",");
  480. }
  481. }
  482. }
  483. switch (type) {
  484. // 成本调查表逻辑
  485. case ("1"): {
  486. CostSurveyTemplate template = costSurveyTemplateManager.get(surveyTemplateId);
  487. if (template == null) {
  488. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到指定的调查模板");
  489. return;
  490. }
  491. templateType = template.getTemplateType();
  492. templateName = template.getSurveyTemplateName();
  493. CostSurveyTemplateVersion currentVersion = StringUtil.isNotEmpty(versionId) ?
  494. costSurveyTemplateVersionManager.get(versionId) : costSurveyTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  495. if (currentVersion == null) {
  496. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到指定的模板版本");
  497. return;
  498. }
  499. List<CostSurveyTemplateHeaders> headersList =
  500. costSurveyTemplateHeadersManager
  501. .listVisibleBySurveyTemplateIdAndVersion(surveyTemplateId, currentVersion.getId())
  502. .stream()
  503. .filter(header -> StringUtil.isEmpty(header.getShowVisible()) || "1".equals(header.getShowVisible()))
  504. .sorted(Comparator.comparing(header -> {
  505. String orderNum = header.getOrderNum();
  506. if (StringUtil.isEmpty(orderNum)) {
  507. return Integer.MAX_VALUE; // 空值排到最后
  508. }
  509. try {
  510. return Integer.parseInt(orderNum.trim());
  511. } catch (NumberFormatException e) {
  512. return Integer.MAX_VALUE; // 无效数字排到最后
  513. }
  514. }))
  515. .collect(Collectors.toList());
  516. if (headersList.isEmpty()) {
  517. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到表头信息");
  518. return;
  519. }
  520. List<CostSurveyTemplateItems> itemsList = costSurveyTemplateItemsDao.selectBySurveyTemplateIdAndVersion(
  521. surveyTemplateId, currentVersion.getId());
  522. Workbook workbook = new XSSFWorkbook();
  523. String sheetName = currentVersion.getSurveyTemplateName() != null ?
  524. currentVersion.getSurveyTemplateName() : "成本调查表";
  525. Sheet sheet = workbook.createSheet(sheetName);
  526. Row headerRow = sheet.createRow(0);
  527. int colIndex = 0;
  528. // 添加原有表头
  529. for (int i = 0; i < headersList.size(); i++) {
  530. headerRow.createCell(colIndex++).setCellValue(headersList.get(i).getFieldName());
  531. }
  532. // 如果是固定表或动态表,在最后添加特殊列
  533. int rowIdColIndex = -1;
  534. int parentIdColIndex = -1;
  535. boolean needExtraColumns = "2".equals(templateType) || "3".equals(templateType);
  536. if (needExtraColumns) {
  537. // 添加 rowId 列(用于标识行)
  538. rowIdColIndex = colIndex;
  539. headerRow.createCell(colIndex++).setCellValue("行ID");
  540. // 添加 parentId 列(用于标识父子关系)
  541. parentIdColIndex = colIndex;
  542. headerRow.createCell(colIndex++).setCellValue("父行ID");
  543. }
  544. // 只有固定表需要添加年限列和备注列
  545. if ("2".equals(templateType) && auditPeriods != null && auditPeriods.length > 0) {
  546. for (String period : auditPeriods) {
  547. headerRow.createCell(colIndex++).setCellValue(period.trim());
  548. }
  549. headerRow.createCell(colIndex++).setCellValue("备注");
  550. }
  551. if (itemsList != null && !itemsList.isEmpty()) {
  552. Map<String, Integer> headerIndexMap = new HashMap<>();
  553. for (int i = 0; i < headersList.size(); i++) {
  554. headerIndexMap.put(headersList.get(i).getId(), i);
  555. }
  556. fillExcelData(sheet, itemsList, headerIndexMap, templateType, type, rowIdColIndex, parentIdColIndex);
  557. }
  558. for (int i = 0; i < headersList.size(); i++) {
  559. sheet.autoSizeColumn(i);
  560. sheet.setColumnWidth(i, Math.max(sheet.getColumnWidth(i), 3000));
  561. }
  562. // 隐藏行ID和父行ID列
  563. if (rowIdColIndex >= 0) {
  564. sheet.setColumnHidden(rowIdColIndex, true);
  565. }
  566. if (parentIdColIndex >= 0) {
  567. sheet.setColumnHidden(parentIdColIndex, true);
  568. }
  569. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  570. response.setCharacterEncoding("utf-8");
  571. String fileName = sheetName + "_" + System.currentTimeMillis() + ".xlsx";
  572. response.setHeader("Content-Disposition", "attachment; filename=" +
  573. URLEncoder.encode(fileName, "UTF-8"));
  574. workbook.write(response.getOutputStream());
  575. workbook.close();
  576. break;
  577. }
  578. // 财务数据表逻辑
  579. case ("2"): {
  580. // 1.获取模板信息
  581. CostSurveyFdTemplate template = costSurveyFdTemplateManager.get(surveyTemplateId);
  582. if (template == null) {
  583. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到指定的财务数据表模板");
  584. return;
  585. }
  586. templateType = template.getTemplateType();
  587. templateName = template.getSurveyTemplateName();
  588. // 2.获取模板版本
  589. CostSurveyFdTemplateVersion currentVersion = StringUtil.isNotEmpty(versionId) ?
  590. costSurveyFdTemplateVersionManager.get(versionId) : costSurveyFdTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  591. if (currentVersion == null) {
  592. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到指定的模板版本");
  593. return;
  594. }
  595. // 3.获取模板表头
  596. List<CostSurveyFdTemplateHeaders> headersList =
  597. costSurveyFdTemplateHeadersManager
  598. .listVisibleBySurveyTemplateIdAndVersion(surveyTemplateId, currentVersion.getId())
  599. .stream()
  600. .filter(header -> StringUtil.isEmpty(header.getShowVisible()) || "1".equals(header.getShowVisible()))
  601. .sorted(Comparator.comparing(header -> {
  602. String orderNum = header.getOrderNum();
  603. if (StringUtil.isEmpty(orderNum)) {
  604. return Integer.MAX_VALUE; // 空值排到最后
  605. }
  606. try {
  607. return Integer.parseInt(orderNum.trim());
  608. } catch (NumberFormatException e) {
  609. return Integer.MAX_VALUE; // 无效数字排到最后
  610. }
  611. }))
  612. .collect(Collectors.toList());
  613. if (headersList.isEmpty()) {
  614. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到表头信息");
  615. return;
  616. }
  617. // 4.获取模板表项
  618. List<CostSurveyFdTemplateItems> itemsList = costSurveyFdTemplateItemsDao.selectBySurveyTemplateIdAndVersion(
  619. surveyTemplateId, currentVersion.getId());
  620. // 5.返回excel
  621. Workbook workbook = new XSSFWorkbook();
  622. String sheetName = currentVersion.getSurveyTemplateName() != null ?
  623. currentVersion.getSurveyTemplateName() : "财务数据表";
  624. Sheet sheet = workbook.createSheet(sheetName);
  625. Row headerRow = sheet.createRow(0);
  626. int colIndex = 0;
  627. // 添加原有表头
  628. for (int i = 0; i < headersList.size(); i++) {
  629. headerRow.createCell(colIndex++).setCellValue(headersList.get(i).getFieldName());
  630. }
  631. // 如果是固定表或动态表,在最后添加特殊列
  632. int rowIdColIndex = -1;
  633. int parentIdColIndex = -1;
  634. boolean needExtraColumns = "2".equals(templateType) || "3".equals(templateType);
  635. if (needExtraColumns) {
  636. // 添加 rowId 列(用于标识行)
  637. rowIdColIndex = colIndex;
  638. headerRow.createCell(colIndex++).setCellValue("行ID");
  639. // 添加 parentId 列(用于标识父子关系)
  640. parentIdColIndex = colIndex;
  641. headerRow.createCell(colIndex++).setCellValue("父行ID");
  642. }
  643. // 只有固定表需要添加年限列和备注列
  644. if ("2".equals(templateType) && auditPeriods != null && auditPeriods.length > 0) {
  645. for (String period : auditPeriods) {
  646. headerRow.createCell(colIndex++).setCellValue(period.trim());
  647. }
  648. headerRow.createCell(colIndex++).setCellValue("备注");
  649. }
  650. if (itemsList != null && !itemsList.isEmpty()) {
  651. Map<String, Integer> headerIndexMap = new HashMap<>();
  652. for (int i = 0; i < headersList.size(); i++) {
  653. headerIndexMap.put(headersList.get(i).getId(), i);
  654. }
  655. fillExcelDataFd(sheet, itemsList, headerIndexMap, templateType, rowIdColIndex, parentIdColIndex);
  656. }
  657. for (int i = 0; i < headersList.size(); i++) {
  658. sheet.autoSizeColumn(i);
  659. sheet.setColumnWidth(i, Math.max(sheet.getColumnWidth(i), 3000));
  660. }
  661. // 隐藏行ID和父行ID列
  662. if (rowIdColIndex >= 0) {
  663. sheet.setColumnHidden(rowIdColIndex, true);
  664. }
  665. if (parentIdColIndex >= 0) {
  666. sheet.setColumnHidden(parentIdColIndex, true);
  667. }
  668. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  669. response.setCharacterEncoding("utf-8");
  670. String fileName = sheetName + "_" + System.currentTimeMillis() + ".xlsx";
  671. response.setHeader("Content-Disposition", "attachment; filename=" +
  672. URLEncoder.encode(fileName, "UTF-8"));
  673. workbook.write(response.getOutputStream());
  674. workbook.close();
  675. break;
  676. }
  677. // 核定表逻辑
  678. case ("3"): {
  679. CostVerifyTemplate template = costVerifyTemplateManager.get(surveyTemplateId);
  680. if (template == null) {
  681. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到指定的核定表模板");
  682. return;
  683. }
  684. List<CostVerifyTemplateHeaders> headersList =
  685. costVerifyTemplateHeadersDao.selectList(
  686. new QueryWrapper<CostVerifyTemplateHeaders>()
  687. .eq("survey_template_id", surveyTemplateId)
  688. )
  689. .stream()
  690. .filter(h -> StringUtil.isEmpty(h.getShowVisible()) || "1".equals(h.getShowVisible()))
  691. .sorted(Comparator.comparing(h -> {
  692. String orderNum = h.getOrderNum();
  693. if (StringUtil.isEmpty(orderNum)) {
  694. return Integer.MAX_VALUE; // 空值排到最后
  695. }
  696. try {
  697. return Integer.parseInt(orderNum.trim());
  698. } catch (NumberFormatException e) {
  699. return Integer.MAX_VALUE; // 无效数字排到最后
  700. }
  701. }))
  702. .collect(Collectors.toList());
  703. if (headersList.isEmpty()) {
  704. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到表头信息");
  705. return;
  706. }
  707. // 查询核定表数据项
  708. List<CostVerifyTemplateItems> itemsList =
  709. costVerifyTemplateItemsDao.selectByVerifyTemplateId(surveyTemplateId, null);
  710. // 添加数据验证日志
  711. System.out.println("核定表导出 - 模板ID: " + surveyTemplateId + ", 表头数量: " + headersList.size() + ", 数据项数量: " + (itemsList != null ? itemsList.size() : 0));
  712. // 在创建 Excel 之前,先补充缺失的表头
  713. if (itemsList != null && !itemsList.isEmpty()) {
  714. // 收集所有数据项中的 headersId
  715. Set<String> allHeadersIds = itemsList.stream()
  716. .map(CostVerifyTemplateItems::getHeadersId)
  717. .filter(StringUtil::isNotEmpty)
  718. .collect(Collectors.toSet());
  719. System.out.println("数据项中的唯一 headersId 数量: " + allHeadersIds.size());
  720. // 检查是否有 headersId 在表头中找不到
  721. Set<String> existingHeaderIds = headersList.stream()
  722. .map(CostVerifyTemplateHeaders::getId)
  723. .collect(Collectors.toSet());
  724. Set<String> missingHeaderIds = new HashSet<>(allHeadersIds);
  725. missingHeaderIds.removeAll(existingHeaderIds);
  726. if (!missingHeaderIds.isEmpty()) {
  727. System.err.println("警告:发现 " + missingHeaderIds.size() + " 个数据项的 headersId 在表头中不存在:");
  728. for (String missingId : missingHeaderIds) {
  729. System.err.println(" - " + missingId);
  730. // 查询这个缺失的表头信息
  731. CostVerifyTemplateHeaders missingHeader = costVerifyTemplateHeadersDao.selectById(missingId);
  732. if (missingHeader != null) {
  733. System.err.println(" 找到表头: " + missingHeader.getFieldName() + ", showVisible: " + missingHeader.getShowVisible());
  734. // 将缺失的表头添加到列表中
  735. headersList.add(missingHeader);
  736. } else {
  737. System.err.println(" 表头不存在于数据库中");
  738. }
  739. }
  740. System.out.println("已补充缺失的表头,新的表头总数: " + headersList.size());
  741. }
  742. }
  743. // 打印表头统计
  744. System.out.println("最终表头列表: 共 " + headersList.size() + " 列");
  745. Workbook workbook = new XSSFWorkbook();
  746. String sheetName = template.getSurveyTemplateName() != null ? template.getSurveyTemplateName() : "核定表";
  747. // Excel sheet 名称不能包含以下字符: : \ / ? * [ ]
  748. // 并且长度不能超过 31 个字符
  749. sheetName = sheetName.replaceAll("[:\\\\/*?\\[\\]]", "_");
  750. if (sheetName.length() > 31) {
  751. sheetName = sheetName.substring(0, 31);
  752. }
  753. System.out.println("Sheet 名称: " + sheetName);
  754. Sheet sheet = workbook.createSheet(sheetName);
  755. // 创建表头行
  756. Row headerRow = sheet.createRow(0);
  757. int colIndex = 0;
  758. // 添加原有表头
  759. for (int i = 0; i < headersList.size(); i++) {
  760. headerRow.createCell(colIndex++).setCellValue(headersList.get(i).getFieldName());
  761. }
  762. // 添加行ID列和父行ID列(固定表需要)
  763. int rowIdColIndex = colIndex;
  764. headerRow.createCell(colIndex++).setCellValue("行ID");
  765. int parentIdColIndex = colIndex;
  766. headerRow.createCell(colIndex++).setCellValue("父行ID");
  767. // 填充数据
  768. if (itemsList != null && !itemsList.isEmpty()) {
  769. Map<String, Integer> headerIndexMap = new HashMap<>();
  770. for (int i = 0; i < headersList.size(); i++) {
  771. headerIndexMap.put(headersList.get(i).getId(), i);
  772. System.out.println("表头映射 - ID: " + headersList.get(i).getId() + ", 列索引: " + i + ", 字段名: " + headersList.get(i).getFieldName());
  773. }
  774. // 打印 items 的 headersId 信息
  775. System.out.println("数据项的 headersId 分布:");
  776. Map<String, Long> headersIdCount = itemsList.stream()
  777. .collect(Collectors.groupingBy(
  778. item -> item.getHeadersId() != null ? item.getHeadersId() : "null",
  779. Collectors.counting()
  780. ));
  781. headersIdCount.forEach((headersId, count) ->
  782. System.out.println(" headersId: " + headersId + ", 数量: " + count)
  783. );
  784. fillExcelDataVerify(sheet, itemsList, headerIndexMap, rowIdColIndex, parentIdColIndex);
  785. }
  786. // 列宽处理
  787. for (int i = 0; i < headersList.size(); i++) {
  788. sheet.autoSizeColumn(i);
  789. sheet.setColumnWidth(i, Math.max(sheet.getColumnWidth(i), 3000));
  790. }
  791. // 隐藏行ID和父行ID列
  792. sheet.setColumnHidden(rowIdColIndex, true);
  793. sheet.setColumnHidden(parentIdColIndex, true);
  794. // 输出
  795. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  796. response.setCharacterEncoding("utf-8");
  797. String fileName = sheetName + "_" + System.currentTimeMillis() + ".xlsx";
  798. response.setHeader("Content-Disposition", "attachment; filename=" +
  799. URLEncoder.encode(fileName, "UTF-8"));
  800. System.out.println("开始写入 Excel 文件到输出流...");
  801. workbook.write(response.getOutputStream());
  802. workbook.close();
  803. System.out.println("Excel 文件写入完成");
  804. break;
  805. }
  806. // 其它不支持
  807. default: {
  808. response.sendError(HttpServletResponse.SC_BAD_REQUEST, "不支持的类型");
  809. break;
  810. }
  811. }
  812. }
  813. /**
  814. * 导入调查表Excel数据
  815. */
  816. @PostMapping(value = "/importExcel")
  817. @ApiOperation(value = "导入调查表Excel数据", httpMethod = "POST")
  818. public CommonResult<String> importExcel(
  819. @ApiParam(name = "file", value = "Excel文件", required = true)
  820. @RequestParam("file") MultipartFile file,
  821. @ApiParam(name = "surveyTemplateId", value = "模板ID", required = true)
  822. @RequestParam(required = true) String surveyTemplateId,
  823. @ApiParam(name = "taskId", value = "任务ID", required = true)
  824. @RequestParam(required = true) String taskId,
  825. @ApiParam(name = "refId", value = "上传记录ID", required = false)
  826. @RequestParam(required = false) String refId,
  827. @ApiParam(name = "materialId", value = "上传记录ID", required = false)
  828. @RequestParam(required = false) String materialId,
  829. @ApiParam(name = "uploadId", value = "上传记录ID", required = false)
  830. @RequestParam(required = false) String uploadId,
  831. @ApiParam(name = "periodRecordId", value = "监审期间记录ID")
  832. @RequestParam(required = false) String periodRecordId,
  833. @ApiParam(name = "type", value = "类型:1-成本调查表 2-财务数据表", required = true)
  834. @RequestParam(required = true) String type) throws Exception {
  835. if (file == null || file.isEmpty()) {
  836. return CommonResult.<String>error().message("上传文件不能为空");
  837. }
  838. String fileName = file.getOriginalFilename();
  839. if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
  840. return CommonResult.<String>error().message("文件格式不正确,请上传Excel文件");
  841. }
  842. String templateType = null;
  843. if (refId == null) {
  844. refId = uploadId;
  845. }
  846. if (refId == null) {
  847. refId = materialId;
  848. }
  849. switch (type) {
  850. // 成本调查表逻辑
  851. case "1": {
  852. CostSurveyTemplate template = costSurveyTemplateManager.get(surveyTemplateId);
  853. if (template == null) {
  854. return CommonResult.<String>error().message("未找到指定的调查模板");
  855. }
  856. templateType = template.getTemplateType();
  857. CostSurveyTemplateVersion currentVersion = costSurveyTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  858. if (currentVersion == null) {
  859. return CommonResult.<String>error().message("未找到启用的模板版本");
  860. }
  861. List<CostSurveyTemplateHeaders> headersList = costSurveyTemplateHeadersManager.listVisibleBySurveyTemplateIdAndVersion(
  862. surveyTemplateId, currentVersion.getId()).stream()
  863. .filter(header -> StringUtil.isEmpty(header.getShowVisible()) || "1".equals(header.getShowVisible()))
  864. .collect(Collectors.toList());
  865. if (headersList.isEmpty()) {
  866. return CommonResult.<String>error().message("未找到表头信息");
  867. }
  868. Map<String, CostSurveyTemplateHeaders> headerNameMap = headersList.stream()
  869. .filter(header -> StringUtil.isNotEmpty(header.getFieldName()))
  870. .collect(Collectors.toMap(
  871. header -> header.getFieldName().trim(),
  872. header -> header
  873. ));
  874. Workbook workbook = null;
  875. try (InputStream inputStream = file.getInputStream()) {
  876. workbook = fileName.endsWith(".xlsx") ?
  877. new XSSFWorkbook(inputStream) : new HSSFWorkbook(inputStream);
  878. Sheet sheet = workbook.getSheetAt(0);
  879. if (sheet == null || sheet.getPhysicalNumberOfRows() < 2) {
  880. return CommonResult.<String>error().message("Excel文件没有数据");
  881. }
  882. Row headerRow = sheet.getRow(0);
  883. if (headerRow == null) {
  884. return CommonResult.<String>error().message("Excel文件第一行为空");
  885. }
  886. // 检查是否有特殊列:行ID、父行ID、年限列、备注列
  887. Integer rowIdColumnIndex = null; // 行ID列索引
  888. Integer parentIdColumnIndex = null; // 父行ID列索引
  889. Map<String, Integer> auditPeriodColumnMap = new HashMap<>(); // 年限->列索引
  890. Integer remarkColumnIndex = null; // 备注列索引
  891. Map<Integer, CostSurveyTemplateHeaders> columnIndexMap = new HashMap<>();
  892. for (int i = 0; i < headerRow.getLastCellNum(); i++) {
  893. Cell cell = headerRow.getCell(i);
  894. if (cell != null) {
  895. String cellValue = getCellStringValue(cell);
  896. if (StringUtil.isNotEmpty(cellValue)) {
  897. String trimmedValue = cellValue.trim();
  898. if ("行ID".equals(trimmedValue)) {
  899. rowIdColumnIndex = i;
  900. } else if ("父行ID".equals(trimmedValue)) {
  901. parentIdColumnIndex = i;
  902. } else if ("备注".equals(trimmedValue)) {
  903. remarkColumnIndex = i;
  904. } else if (trimmedValue.matches("\\d{4}")) {
  905. // 如果是4位数字,认为是年限列
  906. auditPeriodColumnMap.put(trimmedValue, i);
  907. } else {
  908. CostSurveyTemplateHeaders header = headerNameMap.get(trimmedValue);
  909. if (header != null) {
  910. columnIndexMap.put(i, header);
  911. }
  912. }
  913. }
  914. }
  915. }
  916. if (columnIndexMap.isEmpty()) {
  917. return CommonResult.<String>error().message("Excel表头与模板表头不匹配");
  918. }
  919. // 固定表和动态表必须包含行ID和父行ID列
  920. if ("2".equals(templateType) || "3".equals(templateType)) {
  921. if (rowIdColumnIndex == null) {
  922. return CommonResult.<String>error().message("固定表/动态表导入失败:Excel文件缺少【行ID】列。请使用系统导出的模板,不要删除隐藏列。");
  923. }
  924. if (parentIdColumnIndex == null) {
  925. return CommonResult.<String>error().message("固定表/动态表导入失败:Excel文件缺少【父行ID】列。请使用系统导出的模板,不要删除隐藏列。");
  926. }
  927. }
  928. List<CostSurveyTemplateUploadData> dataList = new ArrayList<>();
  929. Map<String, Integer> rowIdToExcelRowMap = new HashMap<>(); // rowId -> Excel行号映射
  930. int dataRowCount = 0;
  931. for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
  932. Row dataRow = sheet.getRow(rowIndex);
  933. if (dataRow == null || isEmptyRow(dataRow)) continue;
  934. // 确定当前行的rowId
  935. String currentRowId;
  936. if (rowIdColumnIndex != null) {
  937. // 如果有行ID列,必须使用Excel中的行ID
  938. Cell rowIdCell = dataRow.getCell(rowIdColumnIndex);
  939. String excelRowId = getCellStringValue(rowIdCell);
  940. if (StringUtil.isNotEmpty(excelRowId)) {
  941. currentRowId = excelRowId.trim();
  942. } else {
  943. // 固定表和动态表都不支持新增行,行ID不能为空
  944. // 如果为空则使用行号作为兜底
  945. currentRowId = "row_" + rowIndex;
  946. }
  947. } else {
  948. // 没有行ID列,使用原有逻辑
  949. currentRowId = "1".equals(templateType) ?
  950. "row_" + System.currentTimeMillis() : "row_" + rowIndex;
  951. }
  952. // 记录 rowId 到 Excel 行号的映射(Excel行号从1开始,加上表头行,所以是 rowIndex + 1)
  953. rowIdToExcelRowMap.put(currentRowId, rowIndex + 1);
  954. // 读取父行ID
  955. String parentRowId = null;
  956. if (parentIdColumnIndex != null) {
  957. Cell parentIdCell = dataRow.getCell(parentIdColumnIndex);
  958. parentRowId = getCellStringValue(parentIdCell);
  959. if (StringUtil.isNotEmpty(parentRowId)) {
  960. parentRowId = parentRowId.trim();
  961. }
  962. }
  963. // 读取原有字段数据(包括空值)
  964. for (Map.Entry<Integer, CostSurveyTemplateHeaders> entry : columnIndexMap.entrySet()) {
  965. Cell cell = dataRow.getCell(entry.getKey());
  966. String cellValue = getCellStringValue(cell);
  967. // 即使是空值也要添加,保证数据完整性
  968. CostSurveyTemplateUploadData uploadData = createUploadData(
  969. surveyTemplateId, taskId, uploadId, currentRowId,
  970. entry.getValue(), cellValue != null ? cellValue : "", periodRecordId, type);
  971. uploadData.setUploadId(uploadId);
  972. uploadData.setRefId(refId);
  973. dataList.add(uploadData);
  974. }
  975. // 读取年限列数据(包括空值)
  976. for (Map.Entry<String, Integer> periodEntry : auditPeriodColumnMap.entrySet()) {
  977. String period = periodEntry.getKey();
  978. Integer colIndex = periodEntry.getValue();
  979. Cell cell = dataRow.getCell(colIndex);
  980. String cellValue = getCellStringValue(cell);
  981. // 即使是空值也要添加,保证数据完整性
  982. CostSurveyTemplateUploadData periodData = new CostSurveyTemplateUploadData();
  983. periodData.setSurveyTemplateId(surveyTemplateId);
  984. periodData.setTaskId(taskId);
  985. periodData.setUploadId(uploadId);
  986. periodData.setRefId(refId);
  987. periodData.setType(type);
  988. periodData.setRowid(currentRowId);
  989. periodData.setRkey(period); // 年限,如:2024
  990. periodData.setRvalue(cellValue != null ? cellValue.trim() : "");
  991. if (StringUtil.isNotEmpty(periodRecordId)) {
  992. periodData.setPeriodRecordId(periodRecordId);
  993. }
  994. periodData.setIsDeleted("0");
  995. dataList.add(periodData);
  996. }
  997. // 读取备注列数据(包括空值)
  998. if (remarkColumnIndex != null) {
  999. Cell remarkCell = dataRow.getCell(remarkColumnIndex);
  1000. String remarkValue = getCellStringValue(remarkCell);
  1001. // 即使是空值也要添加,保证数据完整性
  1002. CostSurveyTemplateUploadData remarkData = new CostSurveyTemplateUploadData();
  1003. remarkData.setSurveyTemplateId(surveyTemplateId);
  1004. remarkData.setTaskId(taskId);
  1005. remarkData.setRefId(refId);
  1006. remarkData.setUploadId(uploadId);
  1007. remarkData.setType(type);
  1008. remarkData.setRowid(currentRowId);
  1009. remarkData.setRkey("remark");
  1010. remarkData.setRvalue(remarkValue != null ? remarkValue.trim() : "");
  1011. if (StringUtil.isNotEmpty(periodRecordId)) {
  1012. remarkData.setPeriodRecordId(periodRecordId);
  1013. }
  1014. remarkData.setIsDeleted("0");
  1015. dataList.add(remarkData);
  1016. }
  1017. dataRowCount++;
  1018. }
  1019. if (dataList.isEmpty()) {
  1020. return CommonResult.<String>error().message("Excel文件中没有有效数据");
  1021. }
  1022. verifyImportData(dataList, type, surveyTemplateId, rowIdToExcelRowMap);
  1023. costSurveyTemplateUploadDataManager.saveData(dataList);
  1024. // 更新上传状态
  1025. CostSurveyTemplateUpload upload = costSurveyTemplateUploadManager.getById(uploadId);
  1026. if (upload != null) {
  1027. upload.setIsUpload("1");
  1028. costSurveyTemplateUploadManager.updateById(upload);
  1029. }
  1030. return CommonResult.<String>ok().message("导入成功,共导入 " + dataRowCount + " 行数据");
  1031. } catch (Exception e) {
  1032. return CommonResult.<String>error().message("导入失败:" + e.getMessage());
  1033. } finally {
  1034. if (workbook != null) {
  1035. workbook.close();
  1036. }
  1037. }
  1038. }
  1039. // 财务数据表逻辑
  1040. case "2": {
  1041. CostSurveyFdTemplate template = costSurveyFdTemplateManager.get(surveyTemplateId);
  1042. if (template == null) {
  1043. return CommonResult.<String>error().message("未找到指定的财务数据表模板");
  1044. }
  1045. templateType = template.getTemplateType();
  1046. CostSurveyFdTemplateVersion currentVersion = costSurveyFdTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  1047. if (currentVersion == null) {
  1048. return CommonResult.<String>error().message("未找到启用的模板版本");
  1049. }
  1050. // 使用已有的manager方法替代新接口
  1051. List<CostSurveyFdTemplateHeaders> headersList = costSurveyFdTemplateHeadersManager.listVisibleBySurveyTemplateIdAndVersion(
  1052. surveyTemplateId, currentVersion.getId()).stream()
  1053. .filter(header -> StringUtil.isEmpty(header.getShowVisible()) || "1".equals(header.getShowVisible()))
  1054. .collect(Collectors.toList());
  1055. if (headersList.isEmpty()) {
  1056. return CommonResult.<String>error().message("未找到表头信息");
  1057. }
  1058. Map<String, CostSurveyFdTemplateHeaders> headerNameMap = headersList.stream()
  1059. .filter(header -> StringUtil.isNotEmpty(header.getFieldName()))
  1060. .collect(Collectors.toMap(
  1061. header -> header.getFieldName().trim(),
  1062. header -> header
  1063. ));
  1064. Workbook workbook = null;
  1065. try (InputStream inputStream = file.getInputStream()) {
  1066. workbook = fileName.endsWith(".xlsx") ?
  1067. new XSSFWorkbook(inputStream) : new HSSFWorkbook(inputStream);
  1068. Sheet sheet = workbook.getSheetAt(0);
  1069. if (sheet == null || sheet.getPhysicalNumberOfRows() < 2) {
  1070. return CommonResult.<String>error().message("Excel文件没有数据");
  1071. }
  1072. Row headerRow = sheet.getRow(0);
  1073. if (headerRow == null) {
  1074. return CommonResult.<String>error().message("Excel文件第一行为空");
  1075. }
  1076. // 检查是否有立项年限列、备注列、行ID列和父行ID列
  1077. Map<String, Integer> auditPeriodColumnMap = new HashMap<>(); // 年限->列索引
  1078. Integer remarkColumnIndex = null; // 备注列索引
  1079. Integer rowIdColumnIndex = null; // 行ID列索引
  1080. Integer parentIdColumnIndex = null; // 父行ID列索引
  1081. Map<Integer, CostSurveyFdTemplateHeaders> columnIndexMap = new HashMap<>();
  1082. for (int i = 0; i < headerRow.getLastCellNum(); i++) {
  1083. Cell cell = headerRow.getCell(i);
  1084. if (cell != null) {
  1085. String cellValue = getCellStringValue(cell);
  1086. if (StringUtil.isNotEmpty(cellValue)) {
  1087. String trimmedValue = cellValue.trim();
  1088. if ("备注".equals(trimmedValue)) {
  1089. remarkColumnIndex = i;
  1090. } else if ("行ID".equals(trimmedValue)) {
  1091. rowIdColumnIndex = i;
  1092. } else if ("父行ID".equals(trimmedValue)) {
  1093. parentIdColumnIndex = i;
  1094. } else if (trimmedValue.matches("\\d{4}")) {
  1095. // 如果是4位数字,认为是年限列
  1096. auditPeriodColumnMap.put(trimmedValue, i);
  1097. } else {
  1098. CostSurveyFdTemplateHeaders header = headerNameMap.get(trimmedValue);
  1099. if (header != null) {
  1100. columnIndexMap.put(i, header);
  1101. }
  1102. }
  1103. }
  1104. }
  1105. }
  1106. if (columnIndexMap.isEmpty()) {
  1107. return CommonResult.<String>error().message("Excel表头与模板表头不匹配");
  1108. }
  1109. List<CostSurveyTemplateUploadData> dataList = new ArrayList<>();
  1110. Map<String, Integer> rowIdToExcelRowMap = new HashMap<>(); // rowId -> Excel行号映射
  1111. int dataRowCount = 0;
  1112. for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
  1113. Row dataRow = sheet.getRow(rowIndex);
  1114. if (dataRow == null || isEmptyRow(dataRow)) continue;
  1115. // 确定当前行的rowId
  1116. String currentRowId;
  1117. if (rowIdColumnIndex != null) {
  1118. // 如果有行ID列,必须使用Excel中的行ID
  1119. Cell rowIdCell = dataRow.getCell(rowIdColumnIndex);
  1120. String excelRowId = getCellStringValue(rowIdCell);
  1121. if (StringUtil.isNotEmpty(excelRowId)) {
  1122. currentRowId = excelRowId.trim();
  1123. } else {
  1124. // 固定表和动态表都不支持新增行,行ID不能为空
  1125. // 如果为空则使用行号作为兜底
  1126. currentRowId = "row_" + rowIndex;
  1127. }
  1128. } else {
  1129. // 没有行ID列,使用原有逻辑
  1130. currentRowId = "1".equals(templateType) ?
  1131. "row_" + System.currentTimeMillis() : "row_" + rowIndex;
  1132. }
  1133. // 记录 rowId 到 Excel 行号的映射(Excel行号从1开始,加上表头行,所以是 rowIndex + 1)
  1134. rowIdToExcelRowMap.put(currentRowId, rowIndex + 1);
  1135. // 读取父行ID
  1136. String parentRowId = null;
  1137. if (parentIdColumnIndex != null) {
  1138. Cell parentIdCell = dataRow.getCell(parentIdColumnIndex);
  1139. parentRowId = getCellStringValue(parentIdCell);
  1140. if (StringUtil.isNotEmpty(parentRowId)) {
  1141. parentRowId = parentRowId.trim();
  1142. }
  1143. }
  1144. // 读取原有字段数据(包括空值)
  1145. for (Map.Entry<Integer, CostSurveyFdTemplateHeaders> entry : columnIndexMap.entrySet()) {
  1146. Cell cell = dataRow.getCell(entry.getKey());
  1147. String cellValue = getCellStringValue(cell);
  1148. // 即使是空值也要添加,保证数据完整性
  1149. CostSurveyTemplateUploadData uploadData = createUploadData(
  1150. surveyTemplateId, taskId, uploadId, currentRowId,
  1151. entry.getValue(), cellValue != null ? cellValue : "", periodRecordId, type);
  1152. dataList.add(uploadData);
  1153. }
  1154. // 读取年限列数据(包括空值)
  1155. for (Map.Entry<String, Integer> periodEntry : auditPeriodColumnMap.entrySet()) {
  1156. String period = periodEntry.getKey();
  1157. Integer colIndex = periodEntry.getValue();
  1158. Cell cell = dataRow.getCell(colIndex);
  1159. String cellValue = getCellStringValue(cell);
  1160. // 即使是空值也要添加,保证数据完整性
  1161. CostSurveyTemplateUploadData periodData = new CostSurveyTemplateUploadData();
  1162. periodData.setSurveyTemplateId(surveyTemplateId);
  1163. periodData.setTaskId(taskId);
  1164. periodData.setRefId(refId);
  1165. periodData.setUploadId(uploadId);
  1166. periodData.setType(type);
  1167. periodData.setRowid(currentRowId);
  1168. periodData.setRkey(period); // 年限,如:2024
  1169. periodData.setRvalue(cellValue != null ? cellValue.trim() : "");
  1170. if (StringUtil.isNotEmpty(periodRecordId)) {
  1171. periodData.setPeriodRecordId(periodRecordId);
  1172. }
  1173. periodData.setIsDeleted("0");
  1174. dataList.add(periodData);
  1175. }
  1176. // 读取备注列数据(包括空值)
  1177. if (remarkColumnIndex != null) {
  1178. Cell remarkCell = dataRow.getCell(remarkColumnIndex);
  1179. String remarkValue = getCellStringValue(remarkCell);
  1180. // 即使是空值也要添加,保证数据完整性
  1181. CostSurveyTemplateUploadData remarkData = new CostSurveyTemplateUploadData();
  1182. remarkData.setSurveyTemplateId(surveyTemplateId);
  1183. remarkData.setTaskId(taskId);
  1184. remarkData.setRefId(refId);
  1185. remarkData.setUploadId(uploadId);
  1186. remarkData.setType(type);
  1187. remarkData.setRowid(currentRowId);
  1188. remarkData.setRkey("remark");
  1189. remarkData.setRvalue(remarkValue != null ? remarkValue.trim() : "");
  1190. if (StringUtil.isNotEmpty(periodRecordId)) {
  1191. remarkData.setPeriodRecordId(periodRecordId);
  1192. }
  1193. remarkData.setIsDeleted("0");
  1194. dataList.add(remarkData);
  1195. }
  1196. }
  1197. dataRowCount++;
  1198. if (dataList.isEmpty()) {
  1199. return CommonResult.<String>error().message("Excel文件中没有有效数据");
  1200. }
  1201. verifyImportData(dataList, type, surveyTemplateId, rowIdToExcelRowMap);
  1202. costSurveyTemplateUploadDataManager.saveData(dataList);
  1203. CostProjectTaskMaterial material = costProjectTaskMaterialManager.getById(materialId);
  1204. material.setIsUpload("1");
  1205. costProjectTaskMaterialManager.updateById(material);
  1206. return CommonResult.<String>ok().message("导入成功,共导入 " + dataRowCount + " 行数据");
  1207. } catch (Exception e) {
  1208. return CommonResult.<String>error().message("导入失败:" + e.getMessage());
  1209. } finally {
  1210. if (workbook != null) {
  1211. workbook.close();
  1212. }
  1213. }
  1214. }
  1215. // 核定表
  1216. case "3": {
  1217. CostVerifyTemplate template = costVerifyTemplateManager.get(surveyTemplateId);
  1218. if (template == null) {
  1219. return CommonResult.<String>error().message("未找到核定表模板");
  1220. }
  1221. List<CostVerifyTemplateHeaders> headersList =
  1222. costVerifyTemplateHeadersDao.selectList(
  1223. new LambdaQueryWrapper<CostVerifyTemplateHeaders>()
  1224. .eq(CostVerifyTemplateHeaders::getSurveyTemplateId, surveyTemplateId)
  1225. );
  1226. if (headersList.isEmpty()) {
  1227. return CommonResult.<String>error().message("未找到表头信息");
  1228. }
  1229. Map<String, CostVerifyTemplateHeaders> headerNameMap = headersList.stream()
  1230. .filter(h -> StringUtil.isNotEmpty(h.getFieldName()))
  1231. .collect(Collectors.toMap(
  1232. h -> h.getFieldName().trim(),
  1233. h -> h
  1234. ));
  1235. Workbook workbook = null;
  1236. try (InputStream inputStream = file.getInputStream()) {
  1237. workbook = fileName.endsWith(".xlsx") ?
  1238. new XSSFWorkbook(inputStream) : new HSSFWorkbook(inputStream);
  1239. Sheet sheet = workbook.getSheetAt(0);
  1240. if (sheet == null || sheet.getPhysicalNumberOfRows() < 2) {
  1241. return CommonResult.<String>error().message("Excel文件没有数据");
  1242. }
  1243. Row headerRow = sheet.getRow(0);
  1244. if (headerRow == null) {
  1245. return CommonResult.<String>error().message("Excel表头行为空");
  1246. }
  1247. // 识别行ID列和父行ID列
  1248. Integer rowIdColumnIndex = null;
  1249. Integer parentIdColumnIndex = null;
  1250. Map<Integer, CostVerifyTemplateHeaders> columnIndexMap = new HashMap<>();
  1251. for (int i = 0; i < headerRow.getLastCellNum(); i++) {
  1252. Cell cell = headerRow.getCell(i);
  1253. if (cell != null) {
  1254. String cellValue = getCellStringValue(cell);
  1255. if (StringUtil.isNotEmpty(cellValue)) {
  1256. String trimmedValue = cellValue.trim();
  1257. if ("行ID".equals(trimmedValue)) {
  1258. rowIdColumnIndex = i;
  1259. } else if ("父行ID".equals(trimmedValue)) {
  1260. parentIdColumnIndex = i;
  1261. } else {
  1262. CostVerifyTemplateHeaders header = headerNameMap.get(trimmedValue);
  1263. if (header != null) {
  1264. columnIndexMap.put(i, header);
  1265. }
  1266. }
  1267. }
  1268. }
  1269. }
  1270. if (columnIndexMap.isEmpty()) {
  1271. return CommonResult.<String>error().message("Excel表头与模板不匹配");
  1272. }
  1273. // 读取数据行
  1274. List<CostSurveyTemplateUploadData> dataList = new ArrayList<>();
  1275. Map<String, Integer> rowIdToExcelRowMap = new HashMap<>(); // rowId -> Excel行号映射
  1276. int importRowCount = 0;
  1277. for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
  1278. Row dataRow = sheet.getRow(rowIndex);
  1279. if (dataRow == null || isEmptyRow(dataRow)) continue;
  1280. // 确定当前行的rowId
  1281. String currentRowId;
  1282. if (rowIdColumnIndex != null) {
  1283. // 如果有行ID列,必须使用Excel中的行ID
  1284. Cell rowIdCell = dataRow.getCell(rowIdColumnIndex);
  1285. String excelRowId = getCellStringValue(rowIdCell);
  1286. if (StringUtil.isNotEmpty(excelRowId)) {
  1287. currentRowId = excelRowId.trim();
  1288. } else {
  1289. // 固定表不支持新增行,行ID不能为空
  1290. // 如果为空则使用行号作为兜底
  1291. currentRowId = "row_" + rowIndex;
  1292. }
  1293. } else {
  1294. // 没有行ID列,使用行号
  1295. currentRowId = "row_" + rowIndex;
  1296. }
  1297. // 记录 rowId 到 Excel 行号的映射(Excel行号从1开始,加上表头行,所以是 rowIndex + 1)
  1298. rowIdToExcelRowMap.put(currentRowId, rowIndex + 1);
  1299. // 读取父行ID
  1300. String parentRowId = null;
  1301. if (parentIdColumnIndex != null) {
  1302. Cell parentIdCell = dataRow.getCell(parentIdColumnIndex);
  1303. parentRowId = getCellStringValue(parentIdCell);
  1304. if (StringUtil.isNotEmpty(parentRowId)) {
  1305. parentRowId = parentRowId.trim();
  1306. }
  1307. }
  1308. for (Map.Entry<Integer, CostVerifyTemplateHeaders> entry : columnIndexMap.entrySet()) {
  1309. Cell cell = dataRow.getCell(entry.getKey());
  1310. String cellValue = getCellStringValue(cell);
  1311. if (StringUtil.isNotEmpty(cellValue)) {
  1312. CostSurveyTemplateUploadData uploadData = createUploadData(
  1313. surveyTemplateId, taskId, uploadId, currentRowId,
  1314. entry.getValue(), cellValue, periodRecordId, type
  1315. );
  1316. dataList.add(uploadData);
  1317. }
  1318. }
  1319. importRowCount++;
  1320. }
  1321. if (dataList.isEmpty()) {
  1322. return CommonResult.<String>error().message("Excel文件没有有效数据");
  1323. }
  1324. verifyImportData(dataList, type, surveyTemplateId, rowIdToExcelRowMap);
  1325. // 保存
  1326. costSurveyTemplateUploadDataManager.saveData(dataList);
  1327. return CommonResult.<String>ok().message("导入成功,共导入 " + importRowCount + " 行数据");
  1328. } catch (Exception e) {
  1329. return CommonResult.<String>error().message("导入失败:" + e.getMessage());
  1330. } finally {
  1331. if (workbook != null) workbook.close();
  1332. }
  1333. }
  1334. default:
  1335. return CommonResult.<String>error().message("不支持的类型");
  1336. }
  1337. }
  1338. /**
  1339. * 获取友好的行号显示
  1340. */
  1341. private String getRowDisplay(String rowid, Map<String, Integer> rowIdToExcelRowMap) {
  1342. if (rowIdToExcelRowMap == null) {
  1343. return "行[" + rowid + "]";
  1344. }
  1345. Integer excelRow = rowIdToExcelRowMap.get(rowid);
  1346. return excelRow != null ? "第" + excelRow + "行" : "行[" + rowid + "]";
  1347. }
  1348. // 校验逻辑
  1349. private void verifyImportData(List<CostSurveyTemplateUploadData> dataList, String type, String surveyTemplateId, Map<String, Integer> rowIdToExcelRowMap) throws Exception {
  1350. if (dataList == null || dataList.isEmpty()) {
  1351. return;
  1352. }
  1353. // 获取模板类型(templateType)
  1354. String templateType = getTemplateType(type, surveyTemplateId);
  1355. // 按 rowid 分组数据
  1356. Map<String, Map<String, String>> rowDataMap = new HashMap<>();
  1357. for (CostSurveyTemplateUploadData data : dataList) {
  1358. String rowid = data.getRowid();
  1359. if (StringUtil.isEmpty(rowid)) {
  1360. rowid = "default_row";
  1361. }
  1362. rowDataMap.computeIfAbsent(rowid, k -> new HashMap<>()).put(data.getRkey(), data.getRvalue());
  1363. }
  1364. // 获取表头信息(用于字段校验)
  1365. List<? extends Object> headersList = getTemplateHeaders(type, surveyTemplateId);
  1366. if (headersList == null || headersList.isEmpty()) {
  1367. return;
  1368. }
  1369. // 1. 字段级别校验(必填、类型、长度等)
  1370. // 固定表和动态表都校验所有行的所有字段(排除年限列和备注列)
  1371. for (Map.Entry<String, Map<String, String>> rowEntry : rowDataMap.entrySet()) {
  1372. String rowid = rowEntry.getKey();
  1373. Map<String, String> rowData = rowEntry.getValue();
  1374. validateFieldRulesExcludingPeriods(rowid, rowData, headersList, rowIdToExcelRowMap);
  1375. }
  1376. // 2. 计算公式校验/结构校验(仅针对固定表,动态表不需要)
  1377. if ("2".equals(templateType)) {
  1378. System.out.println("========================================");
  1379. System.out.println("固定表校验:开始结构校验和公式校验");
  1380. validateFixedTableStructure(rowDataMap, headersList, surveyTemplateId, type);
  1381. // 根据类型获取模板项
  1382. List<? extends Object> itemsList = getTemplateItems(type, surveyTemplateId);
  1383. if (itemsList != null && !itemsList.isEmpty()) {
  1384. // 按 rowid 分组模板项
  1385. Map<String, List<? extends Object>> itemsByRowId = groupItemsByRowId(itemsList);
  1386. // 构建全局的 cellCode -> value 映射(用于公式计算,因为公式可能引用其他行的单元格)
  1387. // 注意:cellCode是行的标识(如A1、A2、Q1),不是列的标识
  1388. // 对于年限列,需要构建 cellCode_年限 -> 值 的映射(如 A1_2024 -> "1")
  1389. Map<String, String> globalCellCodeMap = new HashMap<>();
  1390. // 先构建 rowid -> cellCode 的映射
  1391. Map<String, String> rowidToCellCodeMap = new HashMap<>();
  1392. for (Map.Entry<String, List<? extends Object>> entry : itemsByRowId.entrySet()) {
  1393. String rowid = entry.getKey();
  1394. List<? extends Object> items = entry.getValue();
  1395. if (items != null && !items.isEmpty()) {
  1396. String cellCode = getItemCellCode(items.get(0));
  1397. if (StringUtil.isNotEmpty(cellCode)) {
  1398. rowidToCellCodeMap.put(rowid, cellCode);
  1399. }
  1400. }
  1401. }
  1402. // 遍历所有行数据,构建 cellCode_年限 -> 值 的映射
  1403. for (Map.Entry<String, Map<String, String>> rowEntry : rowDataMap.entrySet()) {
  1404. String rowid = rowEntry.getKey();
  1405. Map<String, String> rowData = rowEntry.getValue();
  1406. String cellCode = rowidToCellCodeMap.get(rowid);
  1407. if (StringUtil.isEmpty(cellCode)) {
  1408. // rowid 不匹配说明导入的Excel不是从当前模板导出的,直接报错
  1409. throw new IllegalArgumentException(String.format("行[%s] 的行ID在模板中不存在,请使用系统导出的最新模板文件", rowid));
  1410. }
  1411. // 处理年限列的数据(key是4位数字,如"2024")
  1412. for (Map.Entry<String, String> dataEntry : rowData.entrySet()) {
  1413. String key = dataEntry.getKey();
  1414. String value = dataEntry.getValue();
  1415. if (key.matches("\\d{4}") && StringUtil.isNotEmpty(value)) {
  1416. // key就是年限(如 2024)
  1417. String period = key;
  1418. // 构建 cellCode_年限 的key(如 A1_2024)
  1419. String mapKey = cellCode + "_" + period;
  1420. globalCellCodeMap.put(mapKey, value);
  1421. }
  1422. }
  1423. }
  1424. // 收集所有的年限(年限的key是4位数字,如"2024")
  1425. Set<String> periods = new HashSet<>();
  1426. for (Map<String, String> rowData : rowDataMap.values()) {
  1427. for (String key : rowData.keySet()) {
  1428. if (key.matches("\\d{4}")) {
  1429. periods.add(key);
  1430. }
  1431. }
  1432. }
  1433. // 校验每一行的计算公式(针对每个年限分别校验)
  1434. for (Map.Entry<String, Map<String, String>> rowEntry : rowDataMap.entrySet()) {
  1435. String rowid = rowEntry.getKey();
  1436. Map<String, String> rowData = rowEntry.getValue();
  1437. // 获取该行对应的模板项
  1438. List<? extends Object> rowItems = itemsByRowId.get(rowid);
  1439. if (rowItems == null || rowItems.isEmpty()) {
  1440. continue;
  1441. }
  1442. // 针对每个年限分别校验(直接抛异常,不需要检查errors)
  1443. for (String period : periods) {
  1444. validateRowFormulasForPeriod(rowid, rowData, rowItems, globalCellCodeMap, period, type,rowIdToExcelRowMap);
  1445. }
  1446. }
  1447. }
  1448. }
  1449. }
  1450. /**
  1451. * 固定表结构校验:行列数量必须匹配
  1452. */
  1453. private void validateFixedTableStructure(Map<String, Map<String, String>> rowDataMap,
  1454. List<? extends Object> headersList,
  1455. String surveyTemplateId, String type) {
  1456. // 1. 获取模板定义的行数
  1457. List<? extends Object> templateItems = getTemplateItems(type, surveyTemplateId);
  1458. if (templateItems == null || templateItems.isEmpty()) {
  1459. throw new IllegalArgumentException("固定表模板未定义任何数据行,无法导入");
  1460. }
  1461. // 按 rowid 分组模板项,得到模板定义的行数
  1462. Set<String> templateRowIds = templateItems.stream()
  1463. .map(item -> getItemRowId(item))
  1464. .filter(StringUtil::isNotEmpty)
  1465. .collect(Collectors.toSet());
  1466. int expectedRowCount = templateRowIds.size();
  1467. int actualRowCount = rowDataMap.size();
  1468. // 2. 校验行数是否匹配
  1469. if (actualRowCount != expectedRowCount) {
  1470. throw new IllegalArgumentException(String.format("固定表行数不匹配!模板定义:%d 行,导入数据:%d 行。固定表不允许增加或减少行。",
  1471. expectedRowCount, actualRowCount));
  1472. }
  1473. // 3. 校验每一行的字段是否完整
  1474. Set<String> expectedFields = headersList.stream()
  1475. .map(header -> getHeaderFieldEname(header))
  1476. .filter(StringUtil::isNotEmpty)
  1477. .collect(Collectors.toSet());
  1478. for (Map.Entry<String, Map<String, String>> rowEntry : rowDataMap.entrySet()) {
  1479. String rowid = rowEntry.getKey();
  1480. Map<String, String> rowData = rowEntry.getValue();
  1481. Set<String> actualFields = rowData.keySet();
  1482. // 检查是否有多余的字段(排除年限列和备注列)
  1483. Set<String> extraFields = new HashSet<>(actualFields);
  1484. extraFields.removeAll(expectedFields);
  1485. // 移除年限列(4位数字的字段,如"2024")和备注列(remark)
  1486. extraFields.removeIf(field -> field.matches("\\d{4}") || "remark".equals(field));
  1487. if (!extraFields.isEmpty()) {
  1488. throw new IllegalArgumentException(String.format("行[%s] 包含模板中未定义的字段:%s。固定表不允许添加额外字段。",
  1489. rowid, String.join(", ", extraFields)));
  1490. }
  1491. // 检查是否缺少必需的字段(只检查必填字段)
  1492. for (Object headerObj : headersList) {
  1493. String fieldEname = getHeaderFieldEname(headerObj);
  1494. String fieldName = getHeaderFieldName(headerObj);
  1495. String isRequired = getHeaderIsRequired(headerObj);
  1496. // 如果是必填字段但导入数据中没有这个字段
  1497. if ("1".equals(isRequired) && !actualFields.contains(fieldEname)) {
  1498. throw new IllegalArgumentException(String.format("行[%s] 缺少必填字段:%s(%s)。固定表必须包含所有必填字段。",
  1499. rowid, fieldEname, fieldName));
  1500. }
  1501. }
  1502. }
  1503. }
  1504. /**
  1505. * 获取模板类型
  1506. */
  1507. private String getTemplateType(String type, String surveyTemplateId) {
  1508. switch (type) {
  1509. case "1": {
  1510. // 成本调查表
  1511. CostSurveyTemplate template = costSurveyTemplateManager.get(surveyTemplateId);
  1512. return template != null ? template.getTemplateType() : null;
  1513. }
  1514. case "2": {
  1515. // 财务数据表
  1516. CostSurveyFdTemplate template = costSurveyFdTemplateManager.get(surveyTemplateId);
  1517. return template != null ? template.getTemplateType() : null;
  1518. }
  1519. case "3":
  1520. // 核定表(核定表没有 templateType 字段,默认返回 null)
  1521. return null;
  1522. default:
  1523. return null;
  1524. }
  1525. }
  1526. /**
  1527. * 获取表头信息
  1528. */
  1529. private List<? extends Object> getTemplateHeaders(String type, String surveyTemplateId) {
  1530. switch (type) {
  1531. case "1": {
  1532. // 成本调查表
  1533. CostSurveyTemplateVersion version = costSurveyTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  1534. if (version != null) {
  1535. return costSurveyTemplateHeadersManager.listVisibleBySurveyTemplateIdAndVersion(surveyTemplateId, version.getId());
  1536. }
  1537. break;
  1538. }
  1539. case "2": {
  1540. // 财务数据表
  1541. CostSurveyFdTemplateVersion version = costSurveyFdTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  1542. if (version != null) {
  1543. return costSurveyFdTemplateHeadersManager.listVisibleBySurveyTemplateIdAndVersion(surveyTemplateId, version.getId());
  1544. }
  1545. break;
  1546. }
  1547. case "3":
  1548. // 核定表
  1549. return costVerifyTemplateHeadersDao.selectList(
  1550. new QueryWrapper<CostVerifyTemplateHeaders>()
  1551. .eq("survey_template_id", surveyTemplateId)
  1552. );
  1553. default:
  1554. break;
  1555. }
  1556. return new ArrayList<>();
  1557. }
  1558. /**
  1559. * 字段级别校验
  1560. */
  1561. private void validateFieldRulesExcludingPeriods(String rowid, Map<String, String> rowData,
  1562. List<? extends Object> headersList, Map<String, Integer> rowIdToExcelRowMap) {
  1563. String rowDisplay = getRowDisplay(rowid, rowIdToExcelRowMap);
  1564. System.out.println("========================================");
  1565. System.out.println("开始校验" + rowDisplay + "的字段(排除年限列)");
  1566. System.out.println("表头数量: " + headersList.size());
  1567. System.out.println("行数据: " + rowData);
  1568. for (Object headerObj : headersList) {
  1569. String fieldEname = getHeaderFieldEname(headerObj);
  1570. String fieldName = getHeaderFieldName(headerObj);
  1571. String fieldType = getHeaderFieldType(headerObj);
  1572. Integer fieldTypelen = getHeaderFieldTypelen(headerObj);
  1573. Integer fieldTypenointlen = getHeaderFieldTypenointlen(headerObj);
  1574. String isRequired = getHeaderIsRequired(headerObj);
  1575. String isDict = getHeaderIsDict(headerObj);
  1576. String dictCode = getHeaderDictCode(headerObj);
  1577. // 获取用户输入的值
  1578. String value = rowData.get(fieldEname);
  1579. System.out.println(" 校验字段: " + fieldEname + "(" + fieldName + "), 值: " + value +
  1580. ", 必填: " + isRequired + ", 类型: " + fieldType + ", 字典: " + isDict);
  1581. // 1. 必填校验(支持 "1" 和 "true" 两种格式)
  1582. if (("1".equals(isRequired) || "true".equalsIgnoreCase(isRequired)) && StringUtil.isEmpty(value)) {
  1583. System.out.println(" ❌ 必填校验失败!");
  1584. throw new IllegalArgumentException(String.format("%s 字段[%s(%s)]为必填项,不能为空", rowDisplay, fieldEname, fieldName));
  1585. }
  1586. // 如果值为空且非必填,跳过后续校验
  1587. if (StringUtil.isEmpty(value)) {
  1588. continue;
  1589. }
  1590. // 2. 字典校验(支持 "1" 和 "true" 两种格式)
  1591. if (("1".equals(isDict) || "true".equalsIgnoreCase(isDict)) && StringUtil.isNotEmpty(dictCode)) {
  1592. validateDictValue(rowid, fieldEname, fieldName, value, dictCode);
  1593. }
  1594. // 3. 字段类型校验
  1595. if (StringUtil.isNotEmpty(fieldType)) {
  1596. validateFieldType(rowid, fieldEname, fieldName, value, fieldType);
  1597. }
  1598. // 4. 字段长度校验
  1599. if (fieldTypelen != null && fieldTypelen > 0) {
  1600. validateFieldLength(rowid, fieldEname, fieldName, value, fieldType, fieldTypelen, fieldTypenointlen);
  1601. }
  1602. }
  1603. // 注意:年限列(如2024、2025)和备注列(remark)不在headersList中,
  1604. // 它们存储在rowData中但不会被上面的循环处理,因此自动被排除
  1605. }
  1606. /**
  1607. * 字典校验
  1608. */
  1609. private void validateDictValue(String rowid, String fieldEname, String fieldName,
  1610. String value, String dictCode) {
  1611. if (StringUtil.isEmpty(value) || StringUtil.isEmpty(dictCode)) {
  1612. return;
  1613. }
  1614. try {
  1615. // 查询字典数据
  1616. QueryWrapper<CostDictData> wrapper = new QueryWrapper<>();
  1617. wrapper.eq("dict_type", dictCode)
  1618. .eq("status", "0"); // 只查询正常状态的字典项
  1619. List<CostDictData> dictDataList = costDictDataManager.list(wrapper);
  1620. if (dictDataList == null || dictDataList.isEmpty()) {
  1621. throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]的字典配置[%s]不存在或未配置字典项",
  1622. rowid, fieldEname, fieldName, dictCode));
  1623. }
  1624. // 检查值是否在字典允许的范围内(支持多选,用逗号分隔)
  1625. String[] values = value.split(",");
  1626. Set<String> validValues = dictDataList.stream()
  1627. .map(CostDictData::getDictValue)
  1628. .filter(StringUtil::isNotEmpty)
  1629. .collect(Collectors.toSet());
  1630. List<String> invalidValues = new ArrayList<>();
  1631. for (String val : values) {
  1632. String trimmedVal = val.trim();
  1633. if (StringUtil.isNotEmpty(trimmedVal) && !validValues.contains(trimmedVal)) {
  1634. invalidValues.add(trimmedVal);
  1635. }
  1636. }
  1637. if (!invalidValues.isEmpty()) {
  1638. throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]的值[%s]不在字典[%s]允许的范围内,有效值:%s",
  1639. rowid, fieldEname, fieldName,
  1640. String.join(", ", invalidValues),
  1641. dictCode,
  1642. String.join(", ", validValues)));
  1643. }
  1644. } catch (IllegalArgumentException e) {
  1645. throw e;
  1646. } catch (Exception e) {
  1647. throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]字典校验异常:%s",
  1648. rowid, fieldEname, fieldName, e.getMessage()));
  1649. }
  1650. }
  1651. /**
  1652. * 字段类型校验
  1653. */
  1654. private void validateFieldType(String rowDisplay, String fieldEname, String fieldName,
  1655. String value, String fieldType) {
  1656. try {
  1657. switch (fieldType.toLowerCase()) {
  1658. case "int":
  1659. case "integer":
  1660. case "bigint":
  1661. try {
  1662. Long.parseLong(value);
  1663. } catch (NumberFormatException e) {
  1664. throw new IllegalArgumentException(String.format("%s 字段[%s(%s)]类型错误,应为整数,实际值:%s", rowDisplay, fieldEname, fieldName, value));
  1665. }
  1666. break;
  1667. case "decimal":
  1668. case "double":
  1669. case "float":
  1670. case "number":
  1671. try {
  1672. Double.parseDouble(value);
  1673. } catch (NumberFormatException e) {
  1674. throw new IllegalArgumentException(String.format("%s 字段[%s(%s)]类型错误,应为数字,实际值:%s", rowDisplay, fieldEname, fieldName, value));
  1675. }
  1676. break;
  1677. case "date":
  1678. // 日期格式校验(可以根据实际需求调整)
  1679. if (!value.matches("\\d{4}-\\d{2}-\\d{2}")) {
  1680. throw new IllegalArgumentException(String.format("%s 字段[%s(%s)]日期格式错误,应为yyyy-MM-dd,实际值:%s", rowDisplay, fieldEname, fieldName, value));
  1681. }
  1682. break;
  1683. case "datetime":
  1684. // 日期时间格式校验
  1685. if (!value.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
  1686. throw new IllegalArgumentException(String.format("%s 字段[%s(%s)]日期时间格式错误,应为yyyy-MM-dd HH:mm:ss,实际值:%s", rowDisplay, fieldEname, fieldName, value));
  1687. }
  1688. break;
  1689. case "boolean":
  1690. case "bool":
  1691. // 布尔类型校验(支持 true/false, 1/0, 是/否)
  1692. String lowerValue = value.toLowerCase().trim();
  1693. if (!lowerValue.equals("true") && !lowerValue.equals("false") &&
  1694. !lowerValue.equals("1") && !lowerValue.equals("0") &&
  1695. !lowerValue.equals("是") && !lowerValue.equals("否")) {
  1696. throw new IllegalArgumentException(String.format("%s 字段[%s(%s)]类型错误,应为布尔值(true/false, 1/0, 是/否),实际值:%s",
  1697. rowDisplay, fieldEname, fieldName, value));
  1698. }
  1699. break;
  1700. case "varchar":
  1701. case "string":
  1702. case "text":
  1703. // 字符串类型,无需特殊校验
  1704. break;
  1705. default:
  1706. // 未知类型,不校验
  1707. break;
  1708. }
  1709. } catch (IllegalArgumentException e) {
  1710. throw e;
  1711. } catch (Exception e) {
  1712. throw new IllegalArgumentException(String.format("%s 字段[%s(%s)]类型校验异常:%s", rowDisplay, fieldEname, fieldName, e.getMessage()));
  1713. }
  1714. }
  1715. /**
  1716. * 字段长度校验
  1717. */
  1718. private void validateFieldLength(String rowid, String fieldEname, String fieldName, String value,
  1719. String fieldType, Integer fieldTypelen, Integer fieldTypenointlen) {
  1720. try {
  1721. switch (fieldType.toLowerCase()) {
  1722. case "varchar":
  1723. case "string":
  1724. case "text":
  1725. // 字符串长度校验
  1726. if (value.length() > fieldTypelen) {
  1727. throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]长度超限,最大长度:%d,实际长度:%d",
  1728. rowid, fieldEname, fieldName, fieldTypelen, value.length()));
  1729. }
  1730. break;
  1731. case "decimal":
  1732. case "double":
  1733. case "float":
  1734. case "number":
  1735. // 数字精度校验
  1736. try {
  1737. double numValue = Double.parseDouble(value);
  1738. String[] parts = value.split("\\.");
  1739. // 整数部分长度
  1740. int intPartLen = parts[0].replace("-", "").length();
  1741. if (fieldTypenointlen != null && intPartLen > (fieldTypelen - fieldTypenointlen)) {
  1742. throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]整数部分长度超限,最大:%d,实际:%d",
  1743. rowid, fieldEname, fieldName, (fieldTypelen - fieldTypenointlen), intPartLen));
  1744. }
  1745. // 小数部分长度
  1746. if (parts.length > 1 && fieldTypenointlen != null) {
  1747. int decimalPartLen = parts[1].length();
  1748. if (decimalPartLen > fieldTypenointlen) {
  1749. throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]小数位数超限,最大:%d,实际:%d",
  1750. rowid, fieldEname, fieldName, fieldTypenointlen, decimalPartLen));
  1751. }
  1752. }
  1753. } catch (NumberFormatException e) {
  1754. // 类型校验已经处理过了,这里不重复报错
  1755. }
  1756. break;
  1757. default:
  1758. break;
  1759. }
  1760. } catch (IllegalArgumentException e) {
  1761. throw e;
  1762. } catch (Exception e) {
  1763. throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]长度校验异常:%s", rowid, fieldEname, fieldName, e.getMessage()));
  1764. }
  1765. }
  1766. /**
  1767. * 获取表头的字段英文名
  1768. */
  1769. private String getHeaderFieldEname(Object header) {
  1770. if (header instanceof CostSurveyTemplateHeaders) {
  1771. return ((CostSurveyTemplateHeaders) header).getFieldEname();
  1772. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  1773. return ((CostSurveyFdTemplateHeaders) header).getFieldEname();
  1774. } else if (header instanceof CostVerifyTemplateHeaders) {
  1775. return ((CostVerifyTemplateHeaders) header).getFieldEname();
  1776. }
  1777. return null;
  1778. }
  1779. /**
  1780. * 获取表头的字段名
  1781. */
  1782. private String getHeaderFieldName(Object header) {
  1783. if (header instanceof CostSurveyTemplateHeaders) {
  1784. return ((CostSurveyTemplateHeaders) header).getFieldName();
  1785. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  1786. return ((CostSurveyFdTemplateHeaders) header).getFieldName();
  1787. } else if (header instanceof CostVerifyTemplateHeaders) {
  1788. return ((CostVerifyTemplateHeaders) header).getFieldName();
  1789. }
  1790. return null;
  1791. }
  1792. /**
  1793. * 获取表头的字段类型
  1794. */
  1795. private String getHeaderFieldType(Object header) {
  1796. if (header instanceof CostSurveyTemplateHeaders) {
  1797. return ((CostSurveyTemplateHeaders) header).getFieldType();
  1798. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  1799. return ((CostSurveyFdTemplateHeaders) header).getFieldType();
  1800. } else if (header instanceof CostVerifyTemplateHeaders) {
  1801. return ((CostVerifyTemplateHeaders) header).getFieldType();
  1802. }
  1803. return null;
  1804. }
  1805. /**
  1806. * 获取表头的字段长度
  1807. */
  1808. private Integer getHeaderFieldTypelen(Object header) {
  1809. if (header instanceof CostSurveyTemplateHeaders) {
  1810. return ((CostSurveyTemplateHeaders) header).getFieldTypelen();
  1811. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  1812. return ((CostSurveyFdTemplateHeaders) header).getFieldTypelen();
  1813. } else if (header instanceof CostVerifyTemplateHeaders) {
  1814. return ((CostVerifyTemplateHeaders) header).getFieldTypelen();
  1815. }
  1816. return null;
  1817. }
  1818. /**
  1819. * 获取表头的小数位长度
  1820. */
  1821. private Integer getHeaderFieldTypenointlen(Object header) {
  1822. if (header instanceof CostSurveyTemplateHeaders) {
  1823. return ((CostSurveyTemplateHeaders) header).getFieldTypenointlen();
  1824. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  1825. return ((CostSurveyFdTemplateHeaders) header).getFieldTypenointlen();
  1826. } else if (header instanceof CostVerifyTemplateHeaders) {
  1827. return ((CostVerifyTemplateHeaders) header).getFieldTypenointlen();
  1828. }
  1829. return null;
  1830. }
  1831. /**
  1832. * 获取表头的是否必填
  1833. */
  1834. private String getHeaderIsRequired(Object header) {
  1835. if (header instanceof CostSurveyTemplateHeaders) {
  1836. return ((CostSurveyTemplateHeaders) header).getIsRequired();
  1837. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  1838. return ((CostSurveyFdTemplateHeaders) header).getIsRequired();
  1839. } else if (header instanceof CostVerifyTemplateHeaders) {
  1840. return ((CostVerifyTemplateHeaders) header).getIsRequired();
  1841. }
  1842. return null;
  1843. }
  1844. /**
  1845. * 获取表头的是否绑定字典
  1846. */
  1847. private String getHeaderIsDict(Object header) {
  1848. if (header instanceof CostSurveyTemplateHeaders) {
  1849. return ((CostSurveyTemplateHeaders) header).getIsDict();
  1850. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  1851. return ((CostSurveyFdTemplateHeaders) header).getIsDict();
  1852. } else if (header instanceof CostVerifyTemplateHeaders) {
  1853. return ((CostVerifyTemplateHeaders) header).getIsDict();
  1854. }
  1855. return null;
  1856. }
  1857. /**
  1858. * 获取表头的字典编码
  1859. */
  1860. private String getHeaderDictCode(Object header) {
  1861. if (header instanceof CostSurveyTemplateHeaders) {
  1862. return ((CostSurveyTemplateHeaders) header).getDictCode();
  1863. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  1864. return ((CostSurveyFdTemplateHeaders) header).getDictCode();
  1865. } else if (header instanceof CostVerifyTemplateHeaders) {
  1866. return ((CostVerifyTemplateHeaders) header).getDictCode();
  1867. }
  1868. return null;
  1869. }
  1870. /**
  1871. * 根据类型获取模板项
  1872. */
  1873. private List<? extends Object> getTemplateItems(String type, String surveyTemplateId) {
  1874. switch (type) {
  1875. case "1": {
  1876. // 成本调查表
  1877. CostSurveyTemplateVersion version = costSurveyTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  1878. if (version != null) {
  1879. return costSurveyTemplateItemsDao.selectBySurveyTemplateIdAndVersion(surveyTemplateId, version.getId());
  1880. }
  1881. break;
  1882. }
  1883. case "2": {
  1884. // 财务数据表
  1885. CostSurveyFdTemplateVersion version = costSurveyFdTemplateVersionManager.selectCurrentVersion(surveyTemplateId);
  1886. if (version != null) {
  1887. return costSurveyFdTemplateItemsDao.selectBySurveyTemplateIdAndVersion(surveyTemplateId, version.getId());
  1888. }
  1889. break;
  1890. }
  1891. case "3":
  1892. // 核定表
  1893. return costVerifyTemplateItemsDao.selectByVerifyTemplateId(surveyTemplateId, null);
  1894. default:
  1895. break;
  1896. }
  1897. return new ArrayList<>();
  1898. }
  1899. /**
  1900. * 按 rowid 分组模板项
  1901. */
  1902. private Map<String, List<? extends Object>> groupItemsByRowId(List<? extends Object> itemsList) {
  1903. Map<String, List<Object>> result = new HashMap<>();
  1904. for (Object item : itemsList) {
  1905. String rowid = getItemRowId(item);
  1906. if (StringUtil.isEmpty(rowid)) {
  1907. rowid = "default_row";
  1908. }
  1909. result.computeIfAbsent(rowid, k -> new ArrayList<>()).add(item);
  1910. }
  1911. return (Map) result;
  1912. }
  1913. /**
  1914. * 获取 item 的 rowid
  1915. */
  1916. private String getItemRowId(Object item) {
  1917. if (item instanceof CostSurveyTemplateItems) {
  1918. return ((CostSurveyTemplateItems) item).getRowid();
  1919. } else if (item instanceof CostSurveyFdTemplateItems) {
  1920. return ((CostSurveyFdTemplateItems) item).getRowid();
  1921. } else if (item instanceof CostVerifyTemplateItems) {
  1922. return ((CostVerifyTemplateItems) item).getRowid();
  1923. }
  1924. return null;
  1925. }
  1926. /**
  1927. * 校验一行数据的计算公式(针对特定年限)
  1928. */
  1929. private void validateRowFormulasForPeriod(String rowid, Map<String, String> rowData,
  1930. List<? extends Object> rowItems, Map<String, String> globalCellCodeMap,
  1931. String period, String type,Map<String, Integer> rowIdToExcelRowMap) {
  1932. // 找到该行的计算公式(同一行的所有模板项共享同一个公式)
  1933. String calculationFormula = null;
  1934. String cellCode = null;
  1935. for (Object item : rowItems) {
  1936. String formula = getItemCalculationFormula(item);
  1937. if (StringUtil.isNotEmpty(formula)) {
  1938. calculationFormula = formula;
  1939. cellCode = getItemCellCode(item);
  1940. break;
  1941. }
  1942. }
  1943. // 如果该行没有公式,跳过
  1944. if (StringUtil.isEmpty(calculationFormula)) {
  1945. return;
  1946. }
  1947. // 检查公式引用的单元格在当前年限是否有任何一个有值
  1948. // 提取公式中的所有单元格引用(如 A1, A2, A3)
  1949. boolean hasAnyReferencedValue = false;
  1950. java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("[A-Z]+\\d+");
  1951. java.util.regex.Matcher matcher = pattern.matcher(calculationFormula);
  1952. List<String> referencedCellsDebug = new ArrayList<>();
  1953. while (matcher.find()) {
  1954. String referencedCell = matcher.group();
  1955. // 构建 cellCode_年限 的key(如 A1_2024)
  1956. String mapKey = referencedCell + "_" + period;
  1957. String value = globalCellCodeMap.get(mapKey);
  1958. String displayValue = StringUtil.isNotEmpty(value) ? value : "0";
  1959. referencedCellsDebug.add(referencedCell + "=" + displayValue);
  1960. if (StringUtil.isNotEmpty(value)) {
  1961. hasAnyReferencedValue = true;
  1962. }
  1963. }
  1964. // 如果公式引用的单元格在当前年限都没有值,跳过校验
  1965. if (!hasAnyReferencedValue) {
  1966. return;
  1967. }
  1968. // 获取用户输入的汇总值(当前年限)
  1969. String inputValueStr = rowData.get(period);
  1970. try {
  1971. // 将公式中的单元格引用替换为 cellCode_年限 的形式
  1972. // 例如:(A1+A2+A3) -> (A1_2024+A2_2024+A3_2024)
  1973. String formulaWithPeriod = calculationFormula;
  1974. matcher = pattern.matcher(calculationFormula);
  1975. StringBuffer sb = new StringBuffer();
  1976. while (matcher.find()) {
  1977. String referencedCell = matcher.group();
  1978. matcher.appendReplacement(sb, referencedCell + "_" + period);
  1979. }
  1980. matcher.appendTail(sb);
  1981. formulaWithPeriod = sb.toString();
  1982. // 验证公式(使用全局cellCode映射,因为公式可能引用其他行的单元格)
  1983. Double calculatedValue = calculateFormula(formulaWithPeriod, globalCellCodeMap);
  1984. // 用户未填写时默认为0
  1985. Double inputValue = StringUtil.isEmpty(inputValueStr) ? 0.0 : Double.parseDouble(inputValueStr);
  1986. // 输出校验信息
  1987. System.out.println("========================================");
  1988. System.out.println("行[" + rowid + "] [" + period + "年]列公式校验");
  1989. System.out.println("公式: " + calculationFormula);
  1990. System.out.println("引用值: " + String.join(", ", referencedCellsDebug));
  1991. System.out.println("计算结果: " + String.format("%.2f", calculatedValue));
  1992. System.out.println("填写值: " + String.format("%.2f", inputValue));
  1993. // 比较计算值和输入值(允许小数点后2位的误差)
  1994. if (Math.abs(calculatedValue - inputValue) > 0.01) {
  1995. System.out.println("校验结果: ❌ 不匹配");
  1996. System.out.println("========================================");
  1997. // 获取友好的行号显示
  1998. Integer excelRow = rowIdToExcelRowMap.get(rowid);
  1999. String rowDisplay = excelRow != null ? "第" + excelRow + "行" : "行[" + rowid + "]";
  2000. throw new IllegalArgumentException(String.format("%s [%s]列汇总值错误:计算值[%.2f],填写值[%.2f]",
  2001. rowDisplay, period + "年", calculatedValue, inputValue));
  2002. } else {
  2003. System.out.println("校验结果: ✓ 通过");
  2004. System.out.println("========================================");
  2005. }
  2006. } catch (IllegalArgumentException e) {
  2007. // 重新抛出校验错误
  2008. throw e;
  2009. } catch (Exception e) {
  2010. System.out.println("========================================");
  2011. System.out.println("行[" + rowid + "] [" + period + "年]列公式校验");
  2012. System.out.println("公式: " + calculationFormula);
  2013. System.out.println("引用值: " + String.join(", ", referencedCellsDebug));
  2014. System.out.println("校验结果: ❌ 计算错误 - " + e.getMessage());
  2015. System.out.println("========================================");
  2016. throw new IllegalArgumentException(String.format("行[%s] [%s]列计算错误:%s",
  2017. rowid, period + "年", e.getMessage()));
  2018. }
  2019. }
  2020. /**
  2021. * 计算公式
  2022. *
  2023. * @param formula 公式字符串,如 "(A1+A2+A3)"
  2024. * @param cellCodeMap cellCode到值的映射,如 {"A1": "1", "A2": "2", "A3": "3"}
  2025. */
  2026. private Double calculateFormula(String formula, Map<String, String> cellCodeMap) {
  2027. // 将中文括号转换为英文括号,避免解析错误
  2028. formula = formula.replace("(", "(")
  2029. .replace(")", ")")
  2030. .replace("【", "[")
  2031. .replace("】", "]")
  2032. .replace("{", "{")
  2033. .replace("}", "}");
  2034. JEP jep = new JEP();
  2035. jep.setAllowUndeclared(true);
  2036. jep.setImplicitMul(true);
  2037. // 提取公式中所有的单元格引用(如 A1_2024, Q2_2024)
  2038. java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("[A-Z]+\\d+_\\d{4}");
  2039. java.util.regex.Matcher matcher = pattern.matcher(formula);
  2040. Set<String> referencedCells = new HashSet<>();
  2041. while (matcher.find()) {
  2042. referencedCells.add(matcher.group());
  2043. }
  2044. // 为所有引用的单元格设置变量值
  2045. Map<String, Double> variableValues = new HashMap<>();
  2046. for (String cellCode : referencedCells) {
  2047. String value = cellCodeMap.get(cellCode);
  2048. Double numValue;
  2049. if (StringUtil.isNotEmpty(value)) {
  2050. try {
  2051. numValue = Double.parseDouble(value);
  2052. } catch (NumberFormatException e) {
  2053. // 如果值不是数字(如中文、文本等),设置为0
  2054. numValue = 0.0;
  2055. }
  2056. } else {
  2057. // 如果单元格没有值(用户未填写),默认为0
  2058. numValue = 0.0;
  2059. }
  2060. variableValues.put(cellCode, numValue);
  2061. jep.addVariable(cellCode, numValue);
  2062. }
  2063. // 解析并计算公式
  2064. jep.parseExpression(formula);
  2065. if (jep.hasError()) {
  2066. throw new IllegalArgumentException("公式语法错误,请检查模板配置。公式: " + formula + ", 错误: " + jep.getErrorInfo());
  2067. }
  2068. double result = jep.getValue();
  2069. if (Double.isNaN(result)) {
  2070. // 构建详细的错误信息
  2071. StringBuilder debugInfo = new StringBuilder();
  2072. debugInfo.append("计算结果为非数字(NaN),可能存在除以零或无效运算。");
  2073. debugInfo.append("\n公式: ").append(formula);
  2074. debugInfo.append("\n变量值: ");
  2075. for (Map.Entry<String, Double> entry : variableValues.entrySet()) {
  2076. debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
  2077. }
  2078. throw new IllegalArgumentException(debugInfo.toString());
  2079. }
  2080. if (Double.isInfinite(result)) {
  2081. // 构建详细的错误信息
  2082. StringBuilder debugInfo = new StringBuilder();
  2083. debugInfo.append("计算结果溢出(无穷大),可能是除以零或数值过大。");
  2084. debugInfo.append("\n公式: ").append(formula);
  2085. debugInfo.append("\n变量值: ");
  2086. for (Map.Entry<String, Double> entry : variableValues.entrySet()) {
  2087. debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
  2088. }
  2089. throw new IllegalArgumentException(debugInfo.toString());
  2090. }
  2091. return result;
  2092. }
  2093. /**
  2094. * 获取 item 的计算公式
  2095. */
  2096. private String getItemCalculationFormula(Object item) {
  2097. if (item instanceof CostSurveyTemplateItems) {
  2098. return ((CostSurveyTemplateItems) item).getCalculationFormula();
  2099. } else if (item instanceof CostSurveyFdTemplateItems) {
  2100. return ((CostSurveyFdTemplateItems) item).getCalculationFormula();
  2101. } else if (item instanceof CostVerifyTemplateItems) {
  2102. return ((CostVerifyTemplateItems) item).getCalculationFormula();
  2103. }
  2104. return null;
  2105. }
  2106. /**
  2107. * 获取 item 的 cellCode
  2108. */
  2109. private String getItemCellCode(Object item) {
  2110. if (item instanceof CostSurveyTemplateItems) {
  2111. return ((CostSurveyTemplateItems) item).getCellCode();
  2112. } else if (item instanceof CostSurveyFdTemplateItems) {
  2113. return ((CostSurveyFdTemplateItems) item).getCellCode();
  2114. } else if (item instanceof CostVerifyTemplateItems) {
  2115. return ((CostVerifyTemplateItems) item).getCellCode();
  2116. }
  2117. return null;
  2118. }
  2119. // ==================== 私有辅助方法 ====================
  2120. private CostItemData buildCostItemData(List<CostSurveyTemplateItems> items, List<CostSurveyTemplateHeaders> headList) {
  2121. String filename = headList.stream().map(CostSurveyTemplateHeaders::getFieldName).collect(Collectors.joining(","));
  2122. String filenids = headList.stream().map(CostSurveyTemplateHeaders::getId).collect(Collectors.joining(","));
  2123. // 按 orderNum 分组
  2124. Map<Integer, List<CostSurveyTemplateItems>> groupedByHeadersId = items.stream()
  2125. .collect(Collectors.groupingBy(CostSurveyTemplateItems::getOrderNum));
  2126. List<Map<String, String>> result = new ArrayList<>();
  2127. for (Map.Entry<Integer, List<CostSurveyTemplateItems>> entry : groupedByHeadersId.entrySet()) {
  2128. List<CostSurveyTemplateItems> group = entry.getValue();
  2129. if (group.isEmpty()) continue;
  2130. // 主项(可以取第一个)
  2131. CostSurveyTemplateItems mainItem = group.get(0);
  2132. String headersIds = "";
  2133. String itemIds = "";
  2134. // 构造 key-value 映射
  2135. Map<String, String> keyValueMap = new HashMap<>();
  2136. for (CostSurveyTemplateItems item : group) {
  2137. if (item.getRkey() != null && item.getRvalue() != null) {
  2138. keyValueMap.put(item.getRkey(), item.getRvalue());
  2139. headersIds += item.getHeadersId() + ",";
  2140. itemIds += item.getId() + ",";
  2141. }
  2142. }
  2143. if (!StringUtil.isEmpty(headersIds))
  2144. headersIds = headersIds.substring(0, headersIds.length() - 1);
  2145. if (!StringUtil.isEmpty(itemIds))
  2146. itemIds = itemIds.substring(0, itemIds.length() - 1);
  2147. //mainItem 放入到keyValueMap中 除了 key value 这两个字段
  2148. keyValueMap.put("id", mainItem.getId());
  2149. keyValueMap.put("surveyTemplateId", mainItem.getSurveyTemplateId());
  2150. keyValueMap.put("versionId", mainItem.getVersionId());
  2151. keyValueMap.put("headersId", mainItem.getHeadersId());
  2152. keyValueMap.put("cellCode", mainItem.getCellCode());
  2153. keyValueMap.put("calculationFormula", mainItem.getCalculationFormula());
  2154. keyValueMap.put("unit", mainItem.getUnit());
  2155. keyValueMap.put("orderNum", mainItem.getOrderNum().toString());
  2156. keyValueMap.put("jsonStr", mainItem.getJsonStr());
  2157. keyValueMap.put("headersIds", headersIds);
  2158. keyValueMap.put("itemIds", itemIds);
  2159. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  2160. keyValueMap.put("createTime", mainItem.getCreateTime() != null ? mainItem.getCreateTime().format(formatter) : null);
  2161. keyValueMap.put("updateTime", mainItem.getUpdateTime() != null ? mainItem.getUpdateTime().format(formatter) : null);
  2162. keyValueMap.put("createBy", mainItem.getCreateBy());
  2163. keyValueMap.put("updateBy", mainItem.getUpdateBy());
  2164. keyValueMap.put("projectId", mainItem.getProjectId());
  2165. keyValueMap.put("orderText", mainItem.getOrderText());
  2166. keyValueMap.put("calculationTemplateId", mainItem.getCalculationTemplateId());
  2167. keyValueMap.put("rowid", mainItem.getRowid());
  2168. keyValueMap.put("parentid", mainItem.getParentid());
  2169. result.add(keyValueMap);
  2170. }
  2171. CostItemData data = new CostItemData();
  2172. data.setFixedFields(filename);
  2173. data.setItemlist(result);
  2174. data.setFixedFieldids(filenids);
  2175. return data;
  2176. }
  2177. private CostItemData buildCostItemDataFd(List<CostSurveyFdTemplateItems> items, List<CostSurveyFdTemplateHeaders> headList) {
  2178. String filename = headList.stream().map(CostSurveyFdTemplateHeaders::getFieldName).collect(Collectors.joining(","));
  2179. String filenids = headList.stream().map(CostSurveyFdTemplateHeaders::getId).collect(Collectors.joining(","));
  2180. // 按 orderNum 分组
  2181. Map<Integer, List<CostSurveyFdTemplateItems>> groupedByOrderNum = items.stream()
  2182. .collect(Collectors.groupingBy(CostSurveyFdTemplateItems::getOrderNum));
  2183. List<Map<String, String>> result = new ArrayList<>();
  2184. for (Map.Entry<Integer, List<CostSurveyFdTemplateItems>> entry : groupedByOrderNum.entrySet()) {
  2185. List<CostSurveyFdTemplateItems> group = entry.getValue();
  2186. if (group.isEmpty()) continue;
  2187. // 主项(取第一个)
  2188. CostSurveyFdTemplateItems mainItem = group.get(0);
  2189. String headersIds = "";
  2190. String itemIds = "";
  2191. // 构造 key-value 映射
  2192. Map<String, String> keyValueMap = new HashMap<>();
  2193. for (CostSurveyFdTemplateItems item : group) {
  2194. if (item.getRkey() != null && item.getRvalue() != null) {
  2195. keyValueMap.put(item.getRkey(), item.getRvalue());
  2196. headersIds += item.getHeadersId() + ",";
  2197. itemIds += item.getId() + ",";
  2198. }
  2199. }
  2200. if (!StringUtil.isEmpty(headersIds))
  2201. headersIds = headersIds.substring(0, headersIds.length() - 1);
  2202. if (!StringUtil.isEmpty(itemIds))
  2203. itemIds = itemIds.substring(0, itemIds.length() - 1);
  2204. // 将mainItem属性放入keyValueMap
  2205. keyValueMap.put("id", mainItem.getId());
  2206. keyValueMap.put("surveyTemplateId", mainItem.getSurveyTemplateId());
  2207. keyValueMap.put("versionId", mainItem.getVersionId());
  2208. keyValueMap.put("headersId", mainItem.getHeadersId());
  2209. keyValueMap.put("cellCode", mainItem.getCellCode());
  2210. keyValueMap.put("calculationFormula", mainItem.getCalculationFormula());
  2211. keyValueMap.put("unit", mainItem.getUnit());
  2212. keyValueMap.put("orderNum", mainItem.getOrderNum().toString());
  2213. keyValueMap.put("jsonStr", mainItem.getJsonStr());
  2214. keyValueMap.put("headersIds", headersIds);
  2215. keyValueMap.put("itemIds", itemIds);
  2216. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  2217. keyValueMap.put("createTime", mainItem.getCreateTime() != null ? mainItem.getCreateTime().format(formatter) : null);
  2218. keyValueMap.put("updateTime", mainItem.getUpdateTime() != null ? mainItem.getUpdateTime().format(formatter) : null);
  2219. keyValueMap.put("createBy", mainItem.getCreateBy());
  2220. keyValueMap.put("updateBy", mainItem.getUpdateBy());
  2221. keyValueMap.put("projectId", mainItem.getProjectId());
  2222. keyValueMap.put("orderText", mainItem.getOrderText());
  2223. keyValueMap.put("calculationTemplateId", mainItem.getCalculationTemplateId());
  2224. keyValueMap.put("rowid", mainItem.getRowid());
  2225. keyValueMap.put("parentid", mainItem.getParentid());
  2226. result.add(keyValueMap);
  2227. }
  2228. CostItemData data = new CostItemData();
  2229. data.setFixedFields(filename);
  2230. data.setItemlist(result);
  2231. data.setFixedFieldids(filenids);
  2232. return data;
  2233. }
  2234. private void fillExcelData(Sheet sheet, List<CostSurveyTemplateItems> itemsList,
  2235. Map<String, Integer> headerIndexMap, String templateType, String dataType,
  2236. int rowIdColIndex, int parentIdColIndex) {
  2237. if ("1".equals(templateType)) {
  2238. Row dataRow = sheet.createRow(1);
  2239. for (CostSurveyTemplateItems item : itemsList) {
  2240. Integer colIndex = headerIndexMap.get(item.getHeadersId());
  2241. if (colIndex != null) {
  2242. dataRow.createCell(colIndex).setCellValue(item.getRvalue());
  2243. }
  2244. }
  2245. } else {
  2246. // 固定表和动态表:按rowid分组
  2247. Map<String, List<CostSurveyTemplateItems>> itemsByRowId = itemsList.stream()
  2248. .filter(item -> StringUtil.isNotEmpty(item.getRowid()))
  2249. .collect(Collectors.groupingBy(CostSurveyTemplateItems::getRowid));
  2250. // 固定表和动态表都需要按父子关系排序
  2251. List<String> sortedRowIds = sortRowIdsByParentChild(itemsByRowId);
  2252. int rowNum = 1;
  2253. for (String rowId : sortedRowIds) {
  2254. List<CostSurveyTemplateItems> rowItems = itemsByRowId.get(rowId);
  2255. if (rowItems != null && !rowItems.isEmpty()) {
  2256. Row dataRow = sheet.createRow(rowNum++);
  2257. // 填充数据列
  2258. for (CostSurveyTemplateItems item : rowItems) {
  2259. Integer colIndex = headerIndexMap.get(item.getHeadersId());
  2260. if (colIndex != null) {
  2261. dataRow.createCell(colIndex).setCellValue(item.getRvalue());
  2262. }
  2263. }
  2264. // 填充 rowId 和 parentId 列
  2265. if (rowIdColIndex >= 0) {
  2266. dataRow.createCell(rowIdColIndex).setCellValue(rowId);
  2267. }
  2268. if (parentIdColIndex >= 0) {
  2269. String parentId = rowItems.get(0).getParentid();
  2270. if (StringUtil.isNotEmpty(parentId)) {
  2271. dataRow.createCell(parentIdColIndex).setCellValue(parentId);
  2272. }
  2273. }
  2274. }
  2275. }
  2276. }
  2277. }
  2278. /**
  2279. * 按父子关系排序rowId
  2280. */
  2281. private List<String> sortRowIdsByParentChild(Map<String, List<CostSurveyTemplateItems>> itemsByRowId) {
  2282. List<String> result = new ArrayList<>();
  2283. // 构建rowId到parentid的映射
  2284. Map<String, String> rowIdToParentId = new HashMap<>();
  2285. for (Map.Entry<String, List<CostSurveyTemplateItems>> entry : itemsByRowId.entrySet()) {
  2286. String rowId = entry.getKey();
  2287. List<CostSurveyTemplateItems> items = entry.getValue();
  2288. if (!items.isEmpty()) {
  2289. String parentId = items.get(0).getParentid();
  2290. rowIdToParentId.put(rowId, parentId);
  2291. }
  2292. }
  2293. // 找出所有根节点(parentid为空或不存在的)
  2294. Set<String> allRowIds = new HashSet<>(itemsByRowId.keySet());
  2295. List<String> rootRowIds = allRowIds.stream()
  2296. .filter(rowId -> {
  2297. String parentId = rowIdToParentId.get(rowId);
  2298. return StringUtil.isEmpty(parentId) || !allRowIds.contains(parentId);
  2299. })
  2300. .sorted()
  2301. .collect(Collectors.toList());
  2302. // 递归添加节点及其子节点
  2303. for (String rootRowId : rootRowIds) {
  2304. addRowIdWithChildren(rootRowId, rowIdToParentId, allRowIds, result);
  2305. }
  2306. return result;
  2307. }
  2308. /**
  2309. * 递归添加rowId及其所有子节点
  2310. */
  2311. private void addRowIdWithChildren(String rowId, Map<String, String> rowIdToParentId,
  2312. Set<String> allRowIds, List<String> result) {
  2313. result.add(rowId);
  2314. // 找出所有子节点
  2315. List<String> children = allRowIds.stream()
  2316. .filter(id -> rowId.equals(rowIdToParentId.get(id)))
  2317. .sorted()
  2318. .collect(Collectors.toList());
  2319. // 递归处理子节点
  2320. for (String childRowId : children) {
  2321. addRowIdWithChildren(childRowId, rowIdToParentId, allRowIds, result);
  2322. }
  2323. }
  2324. private void fillExcelDataFd(Sheet sheet, List<CostSurveyFdTemplateItems> itemsList,
  2325. Map<String, Integer> headerIndexMap, String templateType,
  2326. int rowIdColIndex, int parentIdColIndex) {
  2327. if ("1".equals(templateType)) {
  2328. // 单表:所有数据在一行
  2329. Row dataRow = sheet.createRow(1);
  2330. for (CostSurveyFdTemplateItems item : itemsList) {
  2331. Integer colIndex = headerIndexMap.get(item.getHeadersId());
  2332. if (colIndex != null) {
  2333. dataRow.createCell(colIndex).setCellValue(item.getRvalue());
  2334. }
  2335. }
  2336. } else {
  2337. // 固定表和动态表:按rowid分组
  2338. Map<String, List<CostSurveyFdTemplateItems>> itemsByRowId = itemsList.stream()
  2339. .filter(item -> StringUtil.isNotEmpty(item.getRowid()))
  2340. .collect(Collectors.groupingBy(CostSurveyFdTemplateItems::getRowid));
  2341. // 固定表和动态表都需要按父子关系排序
  2342. List<String> sortedRowIds = sortRowIdsByParentChildFd(itemsByRowId);
  2343. int rowNum = 1;
  2344. for (String rowId : sortedRowIds) {
  2345. List<CostSurveyFdTemplateItems> rowItems = itemsByRowId.get(rowId);
  2346. if (rowItems != null && !rowItems.isEmpty()) {
  2347. Row dataRow = sheet.createRow(rowNum++);
  2348. // 填充数据列
  2349. for (CostSurveyFdTemplateItems item : rowItems) {
  2350. Integer colIndex = headerIndexMap.get(item.getHeadersId());
  2351. if (colIndex != null) {
  2352. dataRow.createCell(colIndex).setCellValue(item.getRvalue());
  2353. }
  2354. }
  2355. // 填充 rowId 和 parentId 列
  2356. if (rowIdColIndex >= 0) {
  2357. dataRow.createCell(rowIdColIndex).setCellValue(rowId);
  2358. }
  2359. if (parentIdColIndex >= 0) {
  2360. String parentId = rowItems.get(0).getParentid();
  2361. if (StringUtil.isNotEmpty(parentId)) {
  2362. dataRow.createCell(parentIdColIndex).setCellValue(parentId);
  2363. }
  2364. }
  2365. }
  2366. }
  2367. }
  2368. }
  2369. /**
  2370. * 按父子关系排序rowId(财务数据表)
  2371. */
  2372. private List<String> sortRowIdsByParentChildFd(Map<String, List<CostSurveyFdTemplateItems>> itemsByRowId) {
  2373. List<String> result = new ArrayList<>();
  2374. // 构建rowId到parentid的映射
  2375. Map<String, String> rowIdToParentId = new HashMap<>();
  2376. for (Map.Entry<String, List<CostSurveyFdTemplateItems>> entry : itemsByRowId.entrySet()) {
  2377. String rowId = entry.getKey();
  2378. List<CostSurveyFdTemplateItems> items = entry.getValue();
  2379. if (!items.isEmpty()) {
  2380. String parentId = items.get(0).getParentid();
  2381. rowIdToParentId.put(rowId, parentId);
  2382. }
  2383. }
  2384. // 找出所有根节点(parentid为空或不存在的)
  2385. Set<String> allRowIds = new HashSet<>(itemsByRowId.keySet());
  2386. List<String> rootRowIds = allRowIds.stream()
  2387. .filter(rowId -> {
  2388. String parentId = rowIdToParentId.get(rowId);
  2389. return StringUtil.isEmpty(parentId) || !allRowIds.contains(parentId);
  2390. })
  2391. .sorted()
  2392. .collect(Collectors.toList());
  2393. // 递归添加节点及其子节点
  2394. for (String rootRowId : rootRowIds) {
  2395. addRowIdWithChildrenFd(rootRowId, rowIdToParentId, allRowIds, result);
  2396. }
  2397. return result;
  2398. }
  2399. /**
  2400. * 递归添加rowId及其所有子节点(财务数据表)
  2401. */
  2402. private void addRowIdWithChildrenFd(String rowId, Map<String, String> rowIdToParentId,
  2403. Set<String> allRowIds, List<String> result) {
  2404. result.add(rowId);
  2405. // 找出所有子节点
  2406. List<String> children = allRowIds.stream()
  2407. .filter(id -> rowId.equals(rowIdToParentId.get(id)))
  2408. .sorted()
  2409. .collect(Collectors.toList());
  2410. // 递归处理子节点
  2411. for (String childRowId : children) {
  2412. addRowIdWithChildrenFd(childRowId, rowIdToParentId, allRowIds, result);
  2413. }
  2414. }
  2415. /**
  2416. * 按父子关系排序rowId(核定表)
  2417. */
  2418. private List<String> sortRowIdsByParentChildVerify(Map<String, List<CostVerifyTemplateItems>> itemsByRowId) {
  2419. List<String> result = new ArrayList<>();
  2420. // 构建rowId到parentid的映射
  2421. Map<String, String> rowIdToParentId = new HashMap<>();
  2422. for (Map.Entry<String, List<CostVerifyTemplateItems>> entry : itemsByRowId.entrySet()) {
  2423. String rowId = entry.getKey();
  2424. List<CostVerifyTemplateItems> items = entry.getValue();
  2425. if (!items.isEmpty()) {
  2426. String parentId = items.get(0).getParentid();
  2427. rowIdToParentId.put(rowId, parentId);
  2428. }
  2429. }
  2430. // 找出所有根节点(parentid为空或不存在的)
  2431. Set<String> allRowIds = new HashSet<>(itemsByRowId.keySet());
  2432. List<String> rootRowIds = allRowIds.stream()
  2433. .filter(rowId -> {
  2434. String parentId = rowIdToParentId.get(rowId);
  2435. return StringUtil.isEmpty(parentId) || !allRowIds.contains(parentId);
  2436. })
  2437. .sorted()
  2438. .collect(Collectors.toList());
  2439. // 递归添加节点及其子节点
  2440. for (String rootRowId : rootRowIds) {
  2441. addRowIdWithChildrenVerify(rootRowId, rowIdToParentId, allRowIds, result);
  2442. }
  2443. return result;
  2444. }
  2445. /**
  2446. * 递归添加rowId及其所有子节点(核定表)
  2447. */
  2448. private void addRowIdWithChildrenVerify(String rowId, Map<String, String> rowIdToParentId,
  2449. Set<String> allRowIds, List<String> result) {
  2450. result.add(rowId);
  2451. // 找出所有子节点
  2452. List<String> children = allRowIds.stream()
  2453. .filter(id -> rowId.equals(rowIdToParentId.get(id)))
  2454. .sorted()
  2455. .collect(Collectors.toList());
  2456. // 递归处理子节点
  2457. for (String childRowId : children) {
  2458. addRowIdWithChildrenVerify(childRowId, rowIdToParentId, allRowIds, result);
  2459. }
  2460. }
  2461. private void fillExcelDataVerify(Sheet sheet, List<CostVerifyTemplateItems> itemsList,
  2462. Map<String, Integer> headerIndexMap, int rowIdColIndex, int parentIdColIndex) {
  2463. if (itemsList == null || itemsList.isEmpty()) {
  2464. return;
  2465. }
  2466. // 按 rowid 分组
  2467. Map<String, List<CostVerifyTemplateItems>> itemsByRowId = itemsList.stream()
  2468. .filter(item -> StringUtil.isNotEmpty(item.getRowid()))
  2469. .collect(Collectors.groupingBy(CostVerifyTemplateItems::getRowid));
  2470. // 按父子关系排序
  2471. List<String> sortedRowIds = sortRowIdsByParentChildVerify(itemsByRowId);
  2472. int rowNum = 1;
  2473. int totalCellsWritten = 0;
  2474. int cellsSkippedNoMapping = 0;
  2475. for (String rowId : sortedRowIds) {
  2476. List<CostVerifyTemplateItems> rowItems = itemsByRowId.get(rowId);
  2477. if (rowItems != null && !rowItems.isEmpty()) {
  2478. Row dataRow = sheet.createRow(rowNum++);
  2479. // 填充数据列
  2480. for (CostVerifyTemplateItems item : rowItems) {
  2481. Integer colIndex = headerIndexMap.get(item.getHeadersId());
  2482. if (colIndex != null) {
  2483. String cellValue = item.getRvalue();
  2484. if (StringUtil.isNotEmpty(cellValue)) {
  2485. dataRow.createCell(colIndex).setCellValue(cellValue);
  2486. totalCellsWritten++;
  2487. }
  2488. } else {
  2489. cellsSkippedNoMapping++;
  2490. }
  2491. }
  2492. // 填充 rowId 和 parentId 列
  2493. if (rowIdColIndex >= 0) {
  2494. dataRow.createCell(rowIdColIndex).setCellValue(rowId);
  2495. }
  2496. if (parentIdColIndex >= 0) {
  2497. String parentId = rowItems.get(0).getParentid();
  2498. if (StringUtil.isNotEmpty(parentId)) {
  2499. dataRow.createCell(parentIdColIndex).setCellValue(parentId);
  2500. }
  2501. }
  2502. }
  2503. }
  2504. System.out.println("数据填充完成 - 行数: " + (rowNum - 1) + ", 单元格: " + totalCellsWritten + ", 跳过: " + cellsSkippedNoMapping);
  2505. }
  2506. private String getCellStringValue(Cell cell) {
  2507. if (cell == null) return "";
  2508. switch (cell.getCellType()) {
  2509. case STRING:
  2510. return cell.getStringCellValue();
  2511. case NUMERIC:
  2512. if (DateUtil.isCellDateFormatted(cell)) {
  2513. return cell.getDateCellValue().toString();
  2514. } else {
  2515. double numericValue = cell.getNumericCellValue();
  2516. return numericValue == (long) numericValue ?
  2517. String.valueOf((long) numericValue) : String.valueOf(numericValue);
  2518. }
  2519. case BOOLEAN:
  2520. return String.valueOf(cell.getBooleanCellValue());
  2521. case FORMULA:
  2522. try {
  2523. return cell.getStringCellValue();
  2524. } catch (Exception e) {
  2525. try {
  2526. return String.valueOf(cell.getNumericCellValue());
  2527. } catch (Exception ex) {
  2528. return "";
  2529. }
  2530. }
  2531. default:
  2532. return "";
  2533. }
  2534. }
  2535. private boolean isEmptyRow(Row row) {
  2536. for (int colIndex = 0; colIndex < row.getLastCellNum(); colIndex++) {
  2537. Cell cell = row.getCell(colIndex);
  2538. if (cell != null && StringUtil.isNotEmpty(getCellStringValue(cell))) {
  2539. return false;
  2540. }
  2541. }
  2542. return true;
  2543. }
  2544. private CostSurveyTemplateUploadData createUploadData(String surveyTemplateId, String taskId,
  2545. String refId, String rowid,
  2546. Object header,
  2547. String value, String periodRecordId, String type) {
  2548. CostSurveyTemplateUploadData uploadData = new CostSurveyTemplateUploadData();
  2549. uploadData.setSurveyTemplateId(surveyTemplateId);
  2550. uploadData.setTaskId(taskId);
  2551. uploadData.setRefId(refId);
  2552. uploadData.setType(type);
  2553. uploadData.setRowid(rowid);
  2554. String fieldEname = null;
  2555. String fieldName = null;
  2556. if (header instanceof CostSurveyTemplateHeaders) {
  2557. CostSurveyTemplateHeaders h = (CostSurveyTemplateHeaders) header;
  2558. fieldEname = h.getFieldEname();
  2559. fieldName = h.getFieldName();
  2560. } else if (header instanceof CostSurveyFdTemplateHeaders) {
  2561. CostSurveyFdTemplateHeaders h = (CostSurveyFdTemplateHeaders) header;
  2562. fieldEname = h.getFieldEname();
  2563. fieldName = h.getFieldName();
  2564. } else if (header instanceof CostVerifyTemplateHeaders) {
  2565. CostVerifyTemplateHeaders h = (CostVerifyTemplateHeaders) header;
  2566. fieldEname = h.getFieldEname();
  2567. fieldName = h.getFieldName();
  2568. }
  2569. uploadData.setRkey(StringUtil.isNotEmpty(fieldEname) ? fieldEname : fieldName);
  2570. uploadData.setRvalue(value);
  2571. if (StringUtil.isNotEmpty(periodRecordId)) {
  2572. uploadData.setPeriodRecordId(periodRecordId);
  2573. }
  2574. uploadData.setIsDeleted("0");
  2575. return uploadData;
  2576. }
  2577. }