工资单pdf和签名
This commit is contained in:
parent
12480f8306
commit
9ab80de441
|
|
@ -0,0 +1,15 @@
|
|||
package com.engine.salary.constant;
|
||||
|
||||
import weaver.general.BaseBean;
|
||||
|
||||
public class HrmSalaryPayrollConf {
|
||||
|
||||
public static final BaseBean baseBean = new BaseBean();
|
||||
|
||||
public static final String GEN_PDF = baseBean.getPropValue("hrmSalaryPayroll", "genPdf");
|
||||
public static final String HAS_SIGN = baseBean.getPropValue("hrmSalaryPayroll", "hasSign");
|
||||
public static final String TO_PDF_TOOL_PATH = baseBean.getPropValue("hrmSalaryPayroll", "toPdfToolPath");
|
||||
public static final String GEN_PATH = baseBean.getPropValue("hrmSalaryPayroll", "genPath");
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.cloudstore.dev.api.bean.MessageBean;
|
|||
import com.cloudstore.dev.api.bean.MessageType;
|
||||
import com.cloudstore.dev.api.util.Util_Message;
|
||||
import com.engine.salary.annotation.SalaryFormulaVar;
|
||||
import com.engine.salary.constant.HrmSalaryPayrollConf;
|
||||
import com.engine.salary.constant.SalaryArchiveConstant;
|
||||
import com.engine.salary.constant.SalaryBillConstant;
|
||||
import com.engine.salary.constant.SalaryTemplateSalaryItemSetGroupConstant;
|
||||
|
|
@ -26,6 +27,7 @@ import com.google.common.collect.Lists;
|
|||
import com.google.common.collect.Maps;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -33,14 +35,10 @@ import weaver.common.MessageUtil;
|
|||
import weaver.conn.RecordSet;
|
||||
import weaver.email.EmailWorkRunnable;
|
||||
import weaver.file.ImageFileManager;
|
||||
import weaver.general.BaseBean;
|
||||
import weaver.hrm.company.SubCompanyComInfo;
|
||||
import weaver.hrm.resource.ResourceComInfo;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
|
@ -268,7 +266,6 @@ public class SalaryBillBO {
|
|||
|
||||
public static void sendEmail(Map<String, Object> e, SalaryBillSendDTO salaryBillSendParam) {
|
||||
String content = genPayrollHtmlContent(e, salaryBillSendParam);
|
||||
genPdf(e, salaryBillSendParam);
|
||||
// 消息接收者
|
||||
String receivers = Optional.ofNullable(e.get("email")).orElse("").toString();
|
||||
String title = getBillTitle(salaryBillSendParam.getSalaryTemplate().getTheme(), salaryBillSendParam.getSalaryDate(), Long.valueOf(e.get("employeeId").toString()));
|
||||
|
|
@ -280,18 +277,50 @@ public class SalaryBillBO {
|
|||
|
||||
public static void genPdf(Map<String, Object> e, SalaryBillSendDTO salaryBillSendParam) {
|
||||
String content = genPayrollHtmlContent(e, salaryBillSendParam);
|
||||
BaseBean baseBean = new BaseBean();
|
||||
boolean genPdf = "1".equals(baseBean.getPropValue("hrmSalaryPayroll", "genPdf"));
|
||||
boolean hasSign = "1".equals(baseBean.getPropValue("hrmSalaryPayroll", "hasSign"));
|
||||
String genPath = baseBean.getPropValue("hrmSalaryPayroll", "genPath");
|
||||
String toPdfTool = new BaseBean().getPropValue("hrmSalaryPayroll", "toPdfToolPath");
|
||||
//生成html
|
||||
String yyyyMM = SalaryDateUtil.getFormatYearMonth(salaryBillSendParam.getSalaryDate());
|
||||
|
||||
Object id = e.getOrDefault("id", 1L);
|
||||
String htmlPath = genPath + id + ".html";
|
||||
String htmlPath = HrmSalaryPayrollConf.GEN_PATH + yyyyMM + File.separator + id + ".html";
|
||||
|
||||
FileUtil.del(htmlPath);
|
||||
File touch = FileUtil.touch(htmlPath);
|
||||
FileUtil.appendUtf8String(content, touch);
|
||||
String pdfPath = genPath + id + ".pdf";
|
||||
HtmlToPdf.convert(toPdfTool, htmlPath, pdfPath);
|
||||
|
||||
String pdfPath = HrmSalaryPayrollConf.GEN_PATH + yyyyMM + File.separator + id + ".pdf";
|
||||
FileUtil.del(pdfPath);
|
||||
HtmlToPdf.convert(HrmSalaryPayrollConf.TO_PDF_TOOL_PATH, htmlPath, pdfPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并pdff
|
||||
*
|
||||
* @param pdfPath 最终合并的pdf
|
||||
* @param filesToMerge 待合并的pdf
|
||||
*/
|
||||
public static void mergePdf(String pdfPath, List<String> filesToMerge) {
|
||||
|
||||
// 创建PDF合并工具实例
|
||||
PDFMergerUtility merger = new PDFMergerUtility();
|
||||
|
||||
// 遍历要合并的PDF文件列表
|
||||
for (String file : filesToMerge) {
|
||||
if (FileUtil.isFile(file)) {
|
||||
try {
|
||||
merger.addSource(file); // 将每个文件添加到合并工具
|
||||
} catch (FileNotFoundException e) {
|
||||
log.error("PDF合并失败1", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 设置合并后的目标文件
|
||||
merger.setDestinationFileName(pdfPath);
|
||||
try {
|
||||
// 执行合并操作
|
||||
FileUtil.del(pdfPath);
|
||||
merger.mergeDocuments();
|
||||
} catch (IOException e) {
|
||||
log.error("PDF合并失败2", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -318,12 +347,14 @@ public class SalaryBillBO {
|
|||
}
|
||||
baos.flush();
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
byte[] data = baos.toByteArray();
|
||||
try {
|
||||
is.close();
|
||||
baos.close();
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
|
@ -517,19 +548,22 @@ public class SalaryBillBO {
|
|||
} else {
|
||||
buildMailMain(emailContent, e, salaryBillSendParam);
|
||||
}
|
||||
//签章
|
||||
RecordSet rs = new RecordSet();
|
||||
rs.execute("select * from DocSignature where hrmresid=" + e.getOrDefault("employeeId", "1") + " order by markid");
|
||||
if (rs.next()) {
|
||||
int imagefileid = rs.getInt("imagefileid");
|
||||
InputStream imageInputStream = ImageFileManager.getInputStreamById(imagefileid);
|
||||
byte[] data = readInputStream(imageInputStream);
|
||||
String imageBase64 = "data:image/jpeg;base64," + Base64Utils.encodeToString(data);
|
||||
emailContent.append("<div style='width: 100%;margin-top: 16px;text-align: right;line-height: 1.5715;font-family: PingFangSC-Regular;font-size: 12px;color: #111111;'>\n" +
|
||||
" <img style='width: 140px;height: 70px;' src='" + imageBase64 + "'>" +
|
||||
" </div>");
|
||||
}
|
||||
|
||||
//签章
|
||||
boolean hasSign = "1".equals(HrmSalaryPayrollConf.HAS_SIGN);
|
||||
if (hasSign) {
|
||||
RecordSet rs = new RecordSet();
|
||||
rs.execute("select * from DocSignature where hrmresid=" + e.getOrDefault("employeeId", "1") + " order by markid");
|
||||
if (rs.next()) {
|
||||
int imagefileid = rs.getInt("imagefileid");
|
||||
InputStream imageInputStream = ImageFileManager.getInputStreamById(imagefileid);
|
||||
byte[] data = readInputStream(imageInputStream);
|
||||
String imageBase64 = "data:image/jpeg;base64," + Base64Utils.encodeToString(data);
|
||||
emailContent.append("<div style='width: 100%;margin-top: 16px;text-align: right;line-height: 1.5715;font-family: PingFangSC-Regular;font-size: 12px;color: #111111;'>\n" +
|
||||
" <img style='width: 140px;height: 70px;' src='" + imageBase64 + "'>" +
|
||||
" </div>");
|
||||
}
|
||||
}
|
||||
emailContent.append("</div>");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.engine.salary.entity.salaryBill.param;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 工资单导出pdf参数
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SalaryExportPdfParam {
|
||||
|
||||
/**
|
||||
* 工资单发放Id
|
||||
*/
|
||||
private Long salarySendId;
|
||||
|
||||
/**
|
||||
* 主键id
|
||||
*/
|
||||
private Long id;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.engine.salary.service;
|
||||
|
||||
import com.engine.salary.entity.salaryBill.param.SalaryExportPdfParam;
|
||||
import com.engine.salary.entity.salaryBill.param.SalarySendGrantParam;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -42,6 +43,9 @@ public interface SalaryBillService {
|
|||
*/
|
||||
void feedBackSalaryBill(Long salaryInfoId);
|
||||
|
||||
|
||||
String exportPdf(SalaryExportPdfParam param);
|
||||
|
||||
/**
|
||||
* 工资单撤回
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.engine.core.impl.Service;
|
|||
import com.engine.salary.biz.SalarySendBiz;
|
||||
import com.engine.salary.biz.SalarySendInfoBiz;
|
||||
import com.engine.salary.cache.SalaryCacheKey;
|
||||
import com.engine.salary.constant.HrmSalaryPayrollConf;
|
||||
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
|
||||
import com.engine.salary.entity.progress.ProgressDTO;
|
||||
import com.engine.salary.entity.salaryBill.bo.SalaryBillBO;
|
||||
|
|
@ -15,6 +16,7 @@ import com.engine.salary.entity.salaryBill.dto.SalaryBillSendDTO;
|
|||
import com.engine.salary.entity.salaryBill.dto.SalaryBillWatermarkDTO;
|
||||
import com.engine.salary.entity.salaryBill.dto.SalarySendInfoListDTO;
|
||||
import com.engine.salary.entity.salaryBill.dto.SalaryTemplateSalaryItemSetListDTO;
|
||||
import com.engine.salary.entity.salaryBill.param.SalaryExportPdfParam;
|
||||
import com.engine.salary.entity.salaryBill.param.SalarySendGrantParam;
|
||||
import com.engine.salary.entity.salaryBill.param.SalarySendInfoQueryParam;
|
||||
import com.engine.salary.entity.salaryBill.po.SalarySendInfoPO;
|
||||
|
|
@ -31,6 +33,7 @@ import com.engine.salary.mapper.salarybill.SalarySendInfoMapper;
|
|||
import com.engine.salary.mapper.salarybill.SalarySendMapper;
|
||||
import com.engine.salary.service.*;
|
||||
import com.engine.salary.util.JsonUtil;
|
||||
import com.engine.salary.util.SalaryDateUtil;
|
||||
import com.engine.salary.util.SalaryEntityUtil;
|
||||
import com.engine.salary.util.SalaryI18nUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
|
|
@ -45,6 +48,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import weaver.hrm.User;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
|
@ -129,7 +133,7 @@ public class SalaryBillServiceImpl extends Service implements SalaryBillService
|
|||
* 工资单发放 start
|
||||
**********************************************************************/
|
||||
@Override
|
||||
public Map<String, Object> grant(SalarySendGrantParam param) {
|
||||
public Map<String, Object> grant(SalarySendGrantParam param) {
|
||||
// 1.检查和获取工资单发放
|
||||
SalarySendPO salarySend = checkAndGetSalarySend(param.getSalarySendId());
|
||||
// // 已经冻结不能操作
|
||||
|
|
@ -219,6 +223,9 @@ public class SalaryBillServiceImpl extends Service implements SalaryBillService
|
|||
// 3.发送消息:先修改数据再发消息,避免出错后无法撤回
|
||||
List<Long> successIds = sendMessage(enableSendList, salaryBillSendParam);
|
||||
|
||||
//生成pdf
|
||||
genPdf(salaryBillSendParam, enableSendList);
|
||||
|
||||
// 4.发放
|
||||
grantSendInfo(successIds, salarySend, salaryTemplate, salaryBillSendParam);
|
||||
|
||||
|
|
@ -232,8 +239,6 @@ public class SalaryBillServiceImpl extends Service implements SalaryBillService
|
|||
+ SalaryI18nUtil.getI18nLabel(134808, "失败条数") + "[" + (total - successCount) + "]";
|
||||
// 发送进度完成
|
||||
getProgressService(user).finish(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + salarySend.getId(), true, messsage);
|
||||
// log.info("工资单发送组装耗时:{}毫秒;工资单发送消息中心耗时:{}毫秒;工资单数据更改总耗时:{}毫秒;工资单发送总耗时:{}毫秒;工资单云桥图片地址:{}", l3 - l2, l4 - l3, l5 - l4, System.currentTimeMillis() - l,
|
||||
// salaryBillSendParam == null ? "" : salaryBillSendParam.getPicUrl());
|
||||
} catch (Exception e) {
|
||||
log.info("发送出错:{}", e.getMessage(), e);
|
||||
// 发送进度失败
|
||||
|
|
@ -242,6 +247,32 @@ public class SalaryBillServiceImpl extends Service implements SalaryBillService
|
|||
}
|
||||
}
|
||||
|
||||
private void genPdf(SalaryBillSendDTO salaryBillSendParam, List<Map<String, Object>> enableSendList) {
|
||||
boolean genPdf = "1".equals(HrmSalaryPayrollConf.GEN_PDF);
|
||||
if (genPdf) {
|
||||
LocalRunnable localRunnable = new LocalRunnable() {
|
||||
@Override
|
||||
public void execute() {
|
||||
//生成工资单pdf
|
||||
enableSendList.forEach(e -> {
|
||||
SalaryBillBO.genPdf(e, salaryBillSendParam);
|
||||
});
|
||||
|
||||
//合并工资单pdf
|
||||
//1、先获取所有工资单
|
||||
Long id = salaryBillSendParam.getSalarySend().getId();
|
||||
List<SalarySendInfoPO> salarySendInfos = getSalarySendInfoMapper().listSome(SalarySendInfoPO.builder().salarySendId(id).sendStatus(1).build());
|
||||
//2、工资单pdf转为路径
|
||||
String yyyyMM = SalaryDateUtil.getFormatYearMonth(salaryBillSendParam.getSalaryDate());
|
||||
List<String> filesToMerge = salarySendInfos.stream().map(po -> HrmSalaryPayrollConf.GEN_PATH + yyyyMM + File.separator + po.getId() + ".pdf").collect(Collectors.toList());
|
||||
String pdfPath = HrmSalaryPayrollConf.GEN_PATH + yyyyMM + File.separator + id + ".pdf";
|
||||
SalaryBillBO.mergePdf(pdfPath, filesToMerge);
|
||||
}
|
||||
};
|
||||
ThreadPoolUtil.fixedPoolExecute(ModulePoolEnum.OTHER, "salaryBillGenPdf", localRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建发送参数
|
||||
*
|
||||
|
|
@ -301,7 +332,7 @@ public class SalaryBillServiceImpl extends Service implements SalaryBillService
|
|||
// 工资单水印文本动态变量
|
||||
List<String> wmTextFieldIds = SalaryBillBO.getWmTextFieldIds(domain, salaryBillWatermark);
|
||||
// 邮件水印模板
|
||||
boolean isEnableEmail = salaryTemplate.getEmailStatus().equals(SalaryTemplateWhetherEnum.TRUE.getValue());
|
||||
boolean isEnableEmail = salaryTemplate.getEmailStatus().equals(SalaryTemplateWhetherEnum.TRUE.getValue());
|
||||
String emailWmContentTemplate = SalaryBillBO.buildEmailWmContentTemplate(isEnableEmail, salaryBillWatermark);
|
||||
|
||||
return SalaryBillSendDTO.builder()
|
||||
|
|
@ -428,7 +459,7 @@ public class SalaryBillServiceImpl extends Service implements SalaryBillService
|
|||
@Override
|
||||
public void confirmSalaryBill(Long salaryInfoId) {
|
||||
SalarySendInfoPO sendInfoPO = getSalarySendInfoMapper().getById(salaryInfoId);
|
||||
if(ObjectUtils.isEmpty(sendInfoPO)){
|
||||
if (ObjectUtils.isEmpty(sendInfoPO)) {
|
||||
throw new SalaryRunTimeException("工资单不存在或已被删除!");
|
||||
}
|
||||
sendInfoPO.setBillConfirmStatus(BillConfimStatusEnum.CONFIRMED.getValue());
|
||||
|
|
@ -439,7 +470,7 @@ public class SalaryBillServiceImpl extends Service implements SalaryBillService
|
|||
@Override
|
||||
public void feedBackSalaryBill(Long salaryInfoId) {
|
||||
SalarySendInfoPO sendInfoPO = getSalarySendInfoMapper().getById(salaryInfoId);
|
||||
if(ObjectUtils.isEmpty(sendInfoPO)){
|
||||
if (ObjectUtils.isEmpty(sendInfoPO)) {
|
||||
throw new SalaryRunTimeException("工资单不存在或已被删除!");
|
||||
}
|
||||
sendInfoPO.setBillConfirmStatus(BillConfimStatusEnum.FEEDBACK.getValue());
|
||||
|
|
@ -447,6 +478,20 @@ public class SalaryBillServiceImpl extends Service implements SalaryBillService
|
|||
getSalarySendInfoMapper().updateIgnoreNull(sendInfoPO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String exportPdf(SalaryExportPdfParam param) {
|
||||
SalarySendPO salarySend = checkAndGetSalarySend(param.getSalarySendId());
|
||||
String yearMonth = SalaryDateUtil.getFormatYearMonth(salarySend.getSalaryMonth());
|
||||
String path = HrmSalaryPayrollConf.GEN_PATH + File.separator + yearMonth + File.separator + "%s" + ".pdf";
|
||||
Long id = param.getId();
|
||||
if (id == null) {
|
||||
path = String.format(path, param.getSalarySendId());
|
||||
} else {
|
||||
path = String.format(path, id);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getSendInfoList(Long sendId, List<Long> ids) {
|
||||
|
||||
SalarySendPO salarySend = getSalarySendMapper().getById(sendId);
|
||||
|
|
|
|||
|
|
@ -29,8 +29,11 @@ import javax.ws.rs.core.Context;
|
|||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -521,6 +524,47 @@ public class SalaryBillController {
|
|||
return Response.ok(output).header("Content-disposition", "attachment;filename=" + fileName).header("Cache-Control", "no-cache").build();
|
||||
}
|
||||
|
||||
// @GET
|
||||
// @Path("/exportPdf")
|
||||
// @Produces({"application/pdf"})
|
||||
// public Response getPDF(@Context HttpServletRequest request, @Context HttpServletResponse response) {
|
||||
//
|
||||
// File f = new File("D:\\gzd\\2023-02\\1695104948592.pdf");
|
||||
// return Response.ok(f, "application/pdf").build();
|
||||
//
|
||||
// }
|
||||
|
||||
@GET
|
||||
@Path("/exportPdf")
|
||||
public Response downloadPdfFile(@Context HttpServletRequest request, @Context HttpServletResponse response) {
|
||||
User user = HrmUserVarify.getUser(request, response);
|
||||
SalaryExportPdfParam salaryExportPdfParam = new SalaryExportPdfParam();
|
||||
String id = request.getParameter("id");
|
||||
if(StringUtils.isNotBlank(id)){
|
||||
salaryExportPdfParam.setId(Long.valueOf(id));
|
||||
}
|
||||
String salarySendId = request.getParameter("salarySendId");
|
||||
if(StringUtils.isNotBlank(salarySendId)){
|
||||
salaryExportPdfParam.setSalarySendId(Long.valueOf(salarySendId));
|
||||
}
|
||||
|
||||
StreamingOutput fileStream = new StreamingOutput() {
|
||||
@Override
|
||||
public void write(java.io.OutputStream output) throws IOException, WebApplicationException
|
||||
{
|
||||
String pdfPath = getSalarySendWrapper(user).exportPdf(salaryExportPdfParam);
|
||||
java.nio.file.Path path = Paths.get(pdfPath);
|
||||
byte[] data = Files.readAllBytes(path);
|
||||
output.write(data);
|
||||
output.flush();
|
||||
}
|
||||
};
|
||||
return Response
|
||||
.ok(fileStream, MediaType.APPLICATION_OCTET_STREAM)
|
||||
.header("content-disposition","attachment; filename = myfile.pdf")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的工资单列表
|
||||
*
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.cloudstore.eccom.result.WeaResultMsg;
|
|||
import com.engine.common.util.ServiceUtil;
|
||||
import com.engine.core.impl.Service;
|
||||
import com.engine.salary.constant.SalaryItemConstant;
|
||||
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
|
||||
import com.engine.salary.entity.salaryBill.dto.*;
|
||||
import com.engine.salary.entity.salaryBill.param.*;
|
||||
import com.engine.salary.entity.salaryBill.po.SalarySendPO;
|
||||
|
|
@ -698,4 +697,8 @@ public class SalarySendWrapper extends Service implements SalarySendWrapperProxy
|
|||
}
|
||||
getSalaryBillService(user).feedBackSalaryBill(salaryInfoId);
|
||||
}
|
||||
|
||||
public String exportPdf(SalaryExportPdfParam param) {
|
||||
return getSalaryBillService(user).exportPdf(param);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue