|
@@ -1,15 +1,28 @@
|
|
|
package com.github.jfcloud.gene.sample.service.biz;
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
+import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.hutool.core.date.DatePattern;
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
+import cn.hutool.core.io.FileUtil;
|
|
|
+import cn.hutool.core.io.resource.ResourceUtil;
|
|
|
import cn.hutool.core.lang.Assert;
|
|
|
+import cn.hutool.core.util.RandomUtil;
|
|
|
+import cn.hutool.crypto.digest.DigestUtil;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import com.github.jfcloud.admin.api.sys.dto.message.MessageUserDTO;
|
|
|
+import com.github.jfcloud.common.holder.RequestHolder;
|
|
|
+import com.github.jfcloud.gene.cache.UserIdNameCache;
|
|
|
+import com.github.jfcloud.gene.common.constant.StrConstant;
|
|
|
import com.github.jfcloud.gene.common.util.CustomIdGenerator;
|
|
|
import com.github.jfcloud.gene.common.util.UserUtil;
|
|
|
import com.github.jfcloud.gene.constants.GeneStatusEnum;
|
|
|
+import com.github.jfcloud.gene.file.service.FileInfoService;
|
|
|
+import com.github.jfcloud.gene.file.vo.FileVo;
|
|
|
+import com.github.jfcloud.gene.flow.entity.FlowAudit;
|
|
|
+import com.github.jfcloud.gene.flow.entity.FlowFileVersion;
|
|
|
+import com.github.jfcloud.gene.flow.service.FlowAuditService;
|
|
|
import com.github.jfcloud.gene.flow.service.NotifyService;
|
|
|
import com.github.jfcloud.gene.sample.entity.SampleFood;
|
|
|
import com.github.jfcloud.gene.sample.entity.SampleFoodDetail;
|
|
@@ -23,14 +36,26 @@ import com.github.jfcloud.gene.sample.vo.SampleFoodDetailVo;
|
|
|
import com.github.jfcloud.gene.sample.vo.SampleFoodVo;
|
|
|
import com.github.jfcloud.gene.sample.vo.SampleSubmitVo;
|
|
|
import com.github.jfcloud.gene.sys.service.DBSystemPropertiesService;
|
|
|
+import com.github.jfcloud.gene.util.WordDataService;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.SneakyThrows;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.docx4j.XmlUtils;
|
|
|
+import org.docx4j.dml.spreadsheetdrawing.CTDrawing;
|
|
|
+import org.docx4j.openpackaging.packages.SpreadsheetMLPackage;
|
|
|
+import org.docx4j.openpackaging.parts.DrawingML.Drawing;
|
|
|
+import org.docx4j.openpackaging.parts.PartName;
|
|
|
+import org.docx4j.openpackaging.parts.SpreadsheetML.JaxbSmlPart;
|
|
|
+import org.docx4j.openpackaging.parts.SpreadsheetML.WorksheetPart;
|
|
|
+import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Date;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Set;
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.io.File;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@Slf4j
|
|
@@ -43,6 +68,10 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
|
|
|
private final CommonSampleEditServiceImpl commonSampleEditService;
|
|
|
private final DBSystemPropertiesService systemPropertiesService;
|
|
|
private final NotifyService notifyService;
|
|
|
+ private final WordDataService wordDataService;
|
|
|
+ private final FlowAuditService flowAuditService;
|
|
|
+ private final UserIdNameCache userIdNameCache;
|
|
|
+ private final FileInfoService fileInfoService;
|
|
|
|
|
|
@Override
|
|
|
public void removeBySampleId(Long sampleId) {
|
|
@@ -87,7 +116,218 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
|
|
|
|
|
|
@Override
|
|
|
public void generate(Long sampleId) {
|
|
|
+ SampleInfo sampleInfo = sampleInfoMapper.selectById(sampleId);
|
|
|
+ SampleFoodVo detail = getDetail(sampleId);
|
|
|
+
|
|
|
+ if (sampleInfo.getApplyTime() == null) {
|
|
|
+ sampleInfo.setApplyTime(LocalDateTime.now());
|
|
|
+ }
|
|
|
+ String applyDate = DateUtil.format(sampleInfo.getApplyTime(), DatePattern.CHINESE_DATE_PATTERN);
|
|
|
+
|
|
|
+ try (InputStream stream = ResourceUtil.getStream("officeTemplate/sampleFood.xlsx")) {
|
|
|
+ //替换excel中的变量,勾选框使用字符串代替
|
|
|
+ Map<String, String> mappings = new HashMap<>();
|
|
|
+ mappings.put("送检目的", detail.getPurpose());
|
|
|
+ mappings.put("送检方名称", detail.getSenderName());
|
|
|
+ mappings.put("送检方地址", detail.getSenderAddress());
|
|
|
+ mappings.put("送检方联系人", detail.getSenderContactName());
|
|
|
+ mappings.put("送检方联系电话", detail.getSenderPhone());
|
|
|
+ mappings.put("送检方电子邮箱", detail.getSenderEmail());
|
|
|
+ mappings.put("报告方式", "☐一个样品一个报告 ☐多个样品一个报告");
|
|
|
+ mappings.put("报告类型", "☐电子报告 ☐纸质报告");
|
|
|
+ mappings.put("是否评判结果", "☐是 ☐否");
|
|
|
+ mappings.put("是否以干基计", "☐是 ☐否");
|
|
|
+ mappings.put("其他要求", "☐CNAS ☐CMA ☐保密数据");
|
|
|
+ mappings.put("自定义", "☐自定义");
|
|
|
+ mappings.put("样品照片", "☐是(需要) ☐外观 ☐内容物 ☐其他");
|
|
|
+ mappings.put("样品照片-其他", "");
|
|
|
+ mappings.put("样品照片-否", " ☐否(不需要)");
|
|
|
+ mappings.put("样品储存要求", "☐室温 ☐冷藏(0℃~8℃) ☐冷冻(≤18℃) ☐避光 ☐干燥 ☐其他");
|
|
|
+ mappings.put("样品储存要求-其他", "");
|
|
|
+ mappings.put("样品保留期限", "☐常规样品一个月(默认) ☐新鲜产品一周 ☐冷冻产品三周");
|
|
|
+ mappings.put("非危险性样品", "☐非危险性样品");
|
|
|
+ mappings.put("危险性样品", "☐易燃易爆 ☐腐蚀性 ☐毒性(非剧毒) ☐氧化剂 ☐其他");
|
|
|
+ mappings.put("危险性样品-其他", "");
|
|
|
+ mappings.put("服务时限", "☐标准时间(7个工作日) ☐5个工作日\n\n☐3个工作日 ☐1个工作日\n\n☐加急");
|
|
|
+
|
|
|
+ List<SampleFoodDetailVo> detailList = detail.getDetailList();
|
|
|
+ SampleFoodDetailVo itemVo = CollUtil.isEmpty(detailList) ? new SampleFoodDetailVo() : detailList.get(0);
|
|
|
+ mappings.put("样品名称", itemVo.getSampleName());
|
|
|
+ mappings.put("生产商", itemVo.getManufacturer());
|
|
|
+ mappings.put("生产日期", "");
|
|
|
+ mappings.put("批号", itemVo.getBatchNumber());
|
|
|
+ mappings.put("数量", itemVo.getQuantity());
|
|
|
+ mappings.put("样品状态", "☐固体 ☐半固体\n☐液体 ☐其他");
|
|
|
+ mappings.put("样品状态-其他", "");
|
|
|
+ mappings.put("包装方式", "☐袋装 ☐罐装\n☐瓶装 ☐其他");
|
|
|
+ mappings.put("包装方式-其他", "");
|
|
|
+ mappings.put("检测项目", "常规指标,矿物质,维生素");
|
|
|
+ mappings.put("样品-其他", "");
|
|
|
+
|
|
|
+ mappings.put("常规指标条目", "☐水分 ☐灰分 ☐粗纤维 ☐粗脂肪 ☐粗蛋白 ☐水溶性氯化物 ☐pH ☐酸价 ☐过氧化值 ☐淀粉 ☐挥发性盐基氮 ☐还原糖 ☐SOD ☐总黄酮 ☐Ω-3脂肪酸 ☐Ω-6脂肪酸 ☐混合均匀度 ☐碳水化合物 ☐硬度 ☐不溶性杂质 ☐粘稠度 ☐杂质 ☐容重 ☐镜检 ☐嫩度(剪切力) ☐水分活度 ☐膳食纤维 ☐可溶性膳食纤维 ☐不可溶性膳食纤维 ☐尿素酶活性 ☐其他");
|
|
|
+ mappings.put("常规指标条目-其他", "");
|
|
|
+ mappings.put("矿物质条目", "☐硒 ☐总磷 ☐氟 ☐铜 ☐铁 ☐锌 ☐锰 ☐钙 ☐钾 ☐钠 ☐镁 ☐其他");
|
|
|
+ mappings.put("矿物质条目-其他", "");
|
|
|
+ mappings.put("污染物条目", "☐亚硝酸盐 ☐铅 ☐镉 ☐铬 ☐总砷 ☐汞 ☐游离棉酚 ☐组胺 ☐苯并(a)芘 ☐其他");
|
|
|
+ mappings.put("污染物条目-其他", "");
|
|
|
+ mappings.put("维生素条目", "☐维生素D3 ☐维生素E ☐维生素B1 ☐维生素B6 ☐泛酸 ☐维生素C ☐生物素 ☐维生素A ☐维生素K3 ☐维生素B2 ☐烟酸 ☐维生素B12 ☐叶酸 ☐左旋肉碱 ☐其他");
|
|
|
+ mappings.put("维生素条目-其他", "");
|
|
|
+ mappings.put("真菌毒素条目", "☐黄曲霉毒素B1 ☐玉米赤霉烯酮 ☐T2毒素 ☐脱氧雪腐镰刀菌烯醇 ☐赭曲霉毒素A ☐伏马毒素 ☐其他");
|
|
|
+ mappings.put("真菌毒素条目-其他", "");
|
|
|
+ mappings.put("微生物条目", "☐沙门氏菌 ☐细菌总数 ☐酵母菌 ☐金黄色葡萄球菌 ☐志贺氏菌 ☐霉菌总数 ☐大肠菌群 ☐蜡样芽孢杆菌 ☐阪崎肠杆菌 ☐单核细胞增生李斯特氏菌 ☐其他");
|
|
|
+ mappings.put("微生物条目-其他", "");
|
|
|
+ mappings.put("其他条目", "☐17种氨基酸 ☐牛磺酸 ☐六六六 ☐滴滴涕 ☐BHA ☐BHT ☐TBHQ ☐山梨酸 ☐苯甲酸 ☐其他");
|
|
|
+ mappings.put("其他条目-其他", "");
|
|
|
+ mappings.put("套餐条目", "☐套餐①基础九项 ☐套餐②国标全检 ☐套餐③农业部20号公告全检 ☐套餐④AAFCO套餐全检");
|
|
|
+ mappings.put("自定义条目", "");
|
|
|
+
|
|
|
+ mappings.put("日期", applyDate);
|
|
|
+ //将null替换为空字符串
|
|
|
+ mappings.forEach((key, value) -> {
|
|
|
+ if (value == null) {
|
|
|
+ mappings.put(key, "");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ //加载excel模板,并替换变量
|
|
|
+ SpreadsheetMLPackage opcPackagePkg = SpreadsheetMLPackage.load(stream);
|
|
|
+ JaxbSmlPart<?> smlPart = (JaxbSmlPart) opcPackagePkg.getParts().get(new PartName("/xl/sharedStrings.xml"));
|
|
|
+ smlPart.variableReplace(mappings);
|
|
|
+
|
|
|
+ //查询审核记录,添加签字图片
|
|
|
+ List<FlowAudit> flowAudits = flowAuditService.auditList(sampleId,"sample." + sampleInfo.getType(), true);
|
|
|
+ //项目管理部审核
|
|
|
+ flowAudits.stream()
|
|
|
+ .filter(audit -> GeneStatusEnum.PROJECT_MANAGEMENT.getStatus().equals(audit.getFlowStatus()) && StrConstant.YES.equals(audit.getAuditResult()))
|
|
|
+ .findFirst()
|
|
|
+ .ifPresent(audit -> addImageDrawingPart(opcPackagePkg, audit.getCreateSign(), "G26", 3));
|
|
|
+ //部门负责人审核
|
|
|
+ flowAudits.stream()
|
|
|
+ .filter(audit -> GeneStatusEnum.DEPART_LEADER.getStatus().equals(audit.getFlowStatus()) && StrConstant.YES.equals(audit.getAuditResult()))
|
|
|
+ .findFirst()
|
|
|
+ .ifPresent(audit -> addImageDrawingPart(opcPackagePkg, audit.getCreateSign(), "D26", 2));
|
|
|
+
|
|
|
+ //提交之后,显示送检人签字
|
|
|
+ if (!GeneStatusEnum.SUBMIT_STATUS.contains(sampleInfo.getStatus())) {
|
|
|
+ String signPic = userIdNameCache.getSignPic(detail.getSenderContactId());
|
|
|
+ addImageDrawingPart(opcPackagePkg, signPic, "B26", 1);
|
|
|
+ signPic = userIdNameCache.getSignPic(detail.getReceiverId());
|
|
|
+ addImageDrawingPart(opcPackagePkg, signPic, "I26", 4);
|
|
|
+ }
|
|
|
+
|
|
|
+ //保存为excel
|
|
|
+ String targetFileName = String.format("食品检测部门送检单(详细)-%s-%s.xlsx", applyDate, RandomUtil.randomString(RandomUtil.BASE_CHAR_NUMBER_LOWER, 4));
|
|
|
+ File tmpFile = new File(System.getProperty("user.dir"), targetFileName);
|
|
|
+ opcPackagePkg.save(tmpFile);
|
|
|
+
|
|
|
+ //上传服务器
|
|
|
+ FileVo fileVo = fileInfoService.uploadFileWithFileName(Files.newInputStream(tmpFile.toPath()), targetFileName);
|
|
|
+ HttpServletRequest request = RequestHolder.getRequest();
|
|
|
+ new FlowFileVersion()
|
|
|
+ .setFlowId(sampleInfo.getId())
|
|
|
+ .setFlowStatus(sampleInfo.getStatus())
|
|
|
+ .setFileMd5(DigestUtil.md5Hex(tmpFile))
|
|
|
+ .setFilename(fileVo.getName())
|
|
|
+ .setFilepath(fileVo.getUrl())
|
|
|
+ .setUri(request.getMethod() + " " + request.getRequestURI())
|
|
|
+ .setTemplateName("sampleFood.xlsx")
|
|
|
+ .insert();
|
|
|
+
|
|
|
+ //5s后删除临时文件
|
|
|
+ new Timer().schedule(new TimerTask() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ FileUtil.del(tmpFile);
|
|
|
+ log.info("删除临时文件 {}", tmpFile.getAbsolutePath());
|
|
|
+ }
|
|
|
+ }, 5000);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("宠物食品附件生成失败 id={} error={}", sampleId, e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加图片
|
|
|
+ *
|
|
|
+ * @param imageUrl 图片链接
|
|
|
+ * @param location 图片插入位置,如B23
|
|
|
+ * @param index 图片下标
|
|
|
+ */
|
|
|
+ @SneakyThrows
|
|
|
+ private void addImageDrawingPart(SpreadsheetMLPackage pkg, String imageUrl, String location, int index) {
|
|
|
+ byte[] bytes = wordDataService.getBytes(imageUrl);
|
|
|
+ if (bytes == null) {
|
|
|
+ //获取图片字节数组失败,不添加图片
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //在第一个sheet添加图片
|
|
|
+ WorksheetPart worksheet = pkg.getWorkbookPart().getWorksheet(0);
|
|
|
+
|
|
|
+ // Add anchor XML to worksheet
|
|
|
+ org.xlsx4j.sml.CTDrawing smlCtDrawing = org.xlsx4j.jaxb.Context.getsmlObjectFactory().createCTDrawing();
|
|
|
+ worksheet.getJaxbElement().setDrawing(smlCtDrawing);
|
|
|
+
|
|
|
+ // Create Drawing part and add to sheet
|
|
|
+ Drawing drawing = new Drawing();
|
|
|
+ smlCtDrawing.setId(worksheet.addTargetPart(drawing).getId());
|
|
|
+
|
|
|
+ BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(pkg, drawing, bytes);
|
|
|
+ String imageRelID = imagePart.getSourceRelationships().get(0).getId();
|
|
|
+
|
|
|
+ int col = location.charAt(0) - 'A';
|
|
|
+ int row = Integer.parseInt(location.substring(1)) - 1;
|
|
|
+
|
|
|
+ String openXML = "<xdr:wsDr xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:a14=\"http://schemas.microsoft.com/office/drawing/2010/main\" xmlns:xdr=\"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">"
|
|
|
+ + "<xdr:twoCellAnchor editAs=\"oneCell\">"
|
|
|
+ + "<xdr:from>"
|
|
|
+ + "<xdr:col>" + col + "</xdr:col>"
|
|
|
+ + "<xdr:colOff>0</xdr:colOff>"
|
|
|
+ + "<xdr:row>" + row + "</xdr:row>"
|
|
|
+ + "<xdr:rowOff>0</xdr:rowOff>"
|
|
|
+ + "</xdr:from>"
|
|
|
+ + "<xdr:to>"
|
|
|
+ + "<xdr:col>" + col + "</xdr:col>"
|
|
|
+ + "<xdr:colOff>603250</xdr:colOff>"
|
|
|
+ + "<xdr:row>" + row + "</xdr:row>"
|
|
|
+ + "<xdr:rowOff>796500</xdr:rowOff>"
|
|
|
+ + "</xdr:to>"
|
|
|
+ + "<xdr:pic>"
|
|
|
+ + "<xdr:nvPicPr>"
|
|
|
+ + "<xdr:cNvPr id=\"" + index + "\" name=\"Picture " + index + "\"/>"
|
|
|
+ + "<xdr:cNvPicPr>"
|
|
|
+ + "<a:picLocks noChangeAspect=\"1\"/>"
|
|
|
+ + "</xdr:cNvPicPr>"
|
|
|
+ + "</xdr:nvPicPr>"
|
|
|
+ + "<xdr:blipFill>"
|
|
|
+ + "<a:blip r:embed=\"" + imageRelID + "\">"
|
|
|
+ + "<a:extLst>"
|
|
|
+ + "<a:ext uri=\"{" + UUID.randomUUID() + "}\">"
|
|
|
+ + "<a14:useLocalDpi val=\"0\"/>"
|
|
|
+ + "</a:ext>"
|
|
|
+ + "</a:extLst>"
|
|
|
+ + "</a:blip>"
|
|
|
+ + "<a:stretch>"
|
|
|
+ + "<a:fillRect/>"
|
|
|
+ + "</a:stretch>"
|
|
|
+ + "</xdr:blipFill>"
|
|
|
+ + "<xdr:spPr bwMode=\"auto\">"
|
|
|
+ + "<a:xfrm>"
|
|
|
+ + "<a:off x=\"3794124\" y=\"9294813\"/>"
|
|
|
+ + "<a:ext cx=\"603251\" cy=\"357215\"/>"
|
|
|
+ + "</a:xfrm>"
|
|
|
+ + "<a:prstGeom prst=\"rect\">"
|
|
|
+ + "<a:avLst/>"
|
|
|
+ + "</a:prstGeom>"
|
|
|
+ + "</xdr:spPr>"
|
|
|
+ + "</xdr:pic>"
|
|
|
+ + "<xdr:clientData/>"
|
|
|
+ + "</xdr:twoCellAnchor>"
|
|
|
+ + "</xdr:wsDr>";
|
|
|
|
|
|
+ CTDrawing dmlCtDrawing = (CTDrawing) XmlUtils.unwrap(XmlUtils.unmarshalString(openXML));
|
|
|
+ drawing.setJaxbElement(dmlCtDrawing);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -127,11 +367,12 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
|
|
|
|
|
|
@Override
|
|
|
public SampleFoodVo getDetail(Long id) {
|
|
|
- SampleFood sampleFood = getById(id);
|
|
|
+ SampleFood sampleFood = getOne(new LambdaQueryWrapper<>(SampleFood.class).eq(SampleFood::getSampleId, id));
|
|
|
if (sampleFood == null) {
|
|
|
return null;
|
|
|
}
|
|
|
- List<SampleFoodDetail> detailList = sampleFoodDetailService.list(new LambdaQueryWrapper<>(SampleFoodDetail.class).eq(SampleFoodDetail::getSampleFoodId, id));
|
|
|
+ List<SampleFoodDetail> detailList = sampleFoodDetailService.list(new LambdaQueryWrapper<>(SampleFoodDetail.class)
|
|
|
+ .eq(SampleFoodDetail::getSampleFoodId, sampleFood.getId()));
|
|
|
|
|
|
SampleFoodVo sampleFoodVo = BeanUtil.copyProperties(sampleFood, SampleFoodVo.class);
|
|
|
sampleFoodVo.setDetailList(BeanUtil.copyToList(detailList, SampleFoodDetailVo.class));
|