工单新增提醒与强制办结;新增普通微信支付;添加线索渠道

This commit is contained in:
DB 2024-02-23 18:12:23 +08:00
parent 28759c5d68
commit 7cf5e83e4c
33 changed files with 906 additions and 62 deletions

View File

@ -1,11 +1,13 @@
package com.cpop.core.utils;
import com.cpop.core.base.exception.UtilException;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
@ -30,7 +32,11 @@ public class QuartzUtils {
*/
public String getJobInfo(String name, String group) throws SchedulerException {
TriggerKey triggerKey = new TriggerKey(name, group);
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
Trigger trigger = scheduler.getTrigger(triggerKey);
if (trigger == null) {
throw new UtilException("获取定时任务失败");
}
CronTrigger cronTrigger = (CronTrigger) trigger;
return String.format("time:%s,state:%s", cronTrigger.getCronExpression(),
scheduler.getTriggerState(triggerKey).name());
}
@ -47,15 +53,20 @@ public class QuartzUtils {
public boolean modifyJob(String name, String group, String time) throws SchedulerException {
Date date = null;
TriggerKey triggerKey = new TriggerKey(name, group);
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
String oldTime = cronTrigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(time)) {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time);
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group)
.withSchedule(cronScheduleBuilder).build();
date = scheduler.rescheduleJob(triggerKey, trigger);
Trigger triggerResult = scheduler.getTrigger(triggerKey);
if (triggerResult != null) {
CronTrigger cronTrigger = (CronTrigger) triggerResult;
String oldTime = cronTrigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(time)) {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time);
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group)
.withSchedule(cronScheduleBuilder).build();
date = scheduler.rescheduleJob(triggerKey, trigger);
}
return date != null;
} else {
return false;
}
return date != null;
}
/**
@ -156,4 +167,17 @@ public class QuartzUtils {
"? " + year;
}
/**
* 获取任务执行时间
* @author DB
* @since 2024/2/22
* @param name
* @param group
* @return LocalDateTime
*/
public LocalDateTime getJobExecuteTime(String name, String group) throws SchedulerException {
String jobInfo = getJobInfo(name, group);
String[] split = jobInfo.split(",")[0].replaceAll("time:", "").split(" ");
return LocalDateTime.of(Integer.parseInt(split[6]), Integer.parseInt(split[4]), Integer.parseInt(split[3]), Integer.parseInt(split[2]), Integer.parseInt(split[1]), Integer.parseInt(split[0]));
}
}

View File

@ -10,6 +10,9 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author DB
* @version 1.0.0
@ -74,4 +77,14 @@ public class CallBackEasyLearnController {
easyLearnOrderService.unionPayResult(xmlData);
return WxPayNotifyResponse.success("成功");
}
/**
* 微信支付联合支付普通订单通知
* @return java.lang.String
*/
@ApiOperation("微信支付联合支付普通订单通知")
@PostMapping("/notify/normalUnionPay")
public String normalUnionPay(HttpServletRequest request, HttpServletResponse response){
return easyLearnOrderService.normalUnionPay(request,response);
}
}

View File

@ -9,6 +9,8 @@ import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreUserSignPlanRe
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
@ -125,4 +127,12 @@ public interface EasyLearnOrderService extends IService<EasyLearnOrder> {
* @param phone 手机号
*/
void sendEcppMsmCode(String phone);
/**
* 微信支付联合支付普通订单通知
* @author DB
* @since 2024/2/22
* @return String
*/
String normalUnionPay(HttpServletRequest request, HttpServletResponse response);
}

View File

@ -29,6 +29,8 @@ import com.cpop.pay.framewok.core.request.ecpp.EcppSmsCodeRequest;
import com.cpop.pay.framewok.core.response.ecpp.EcppSmsCodeResponse;
import com.cpop.pay.framewok.handler.ecpp.EcppHandler;
import com.cpop.pay.framewok.handler.wxPay.WxPayHandler;
import com.cpop.pay.framewok.handler.wxPay.WxPayNormalHandler;
import com.cpop.pay.framewok.handler.wxPay.WxUtils;
import com.cpop.pay.framewok.task.WxPayAsyncTask;
import com.cpop.system.business.entity.Store;
import com.cpop.system.business.entity.WxPayScore;
@ -62,12 +64,16 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import static com.cpop.jambox.business.entity.table.BrandExtendTableDef.BRAND_EXTEND;
@ -529,6 +535,16 @@ public class EasyLearnOrderServiceImpl extends ServiceImpl<EasyLearnOrderMapper,
*/
private final String UNION_PAY_TEST_NOTIFY_URL = "https://test.cpopsz.com/Cpop-Oam/callback/easyLearn/notify/unionPay";
/**
* 联合支付回调地址本地内网穿透
*/
private final String UNION_PAY_DEV_NORMAL_NOTIFY_URL = "https://frp-bid.top:60778/Cpop-Oam/callback/easyLearn/notify/normalUnionPay";
/**
* 联合支付回调地址测试地址
*/
private final String UNION_PAY_TEST_NORMAL_NOTIFY_URL = "https://test.cpopsz.com/Cpop-Oam/callback/easyLearn/notify/normalUnionPay";
/**
* 放心学统一支付
* @author DB
@ -544,6 +560,7 @@ public class EasyLearnOrderServiceImpl extends ServiceImpl<EasyLearnOrderMapper,
//分布式锁进行幂等处理
Lock userIdLock = redisService.distributedLock(JamboxRedisConstant.ONCE_PAY_LOCK_USER_PAY + bo.getCustomerPhone());
if (userIdLock.tryLock()) {
WxPayService wxPayService = null;
try {
//创建订单
EasyLearnOrder easyLearnOrder = BeanUtils.mapToClass(bo, EasyLearnOrder.class);
@ -573,22 +590,34 @@ public class EasyLearnOrderServiceImpl extends ServiceImpl<EasyLearnOrderMapper,
payNotifyUrl = UNION_PAY_DEV_NOTIFY_URL;
}
//获取商户信息
WxPayService wxPayService = wxPayHandler.getWxPayService(null, wxPayHandler.getSubMchId(easyLearnOrder.getBrandId(), easyLearnOrder.getStoreId()), payNotifyUrl);
WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
//需要分账
if (easyLearnOrder.getRate() != null && easyLearnOrder.getTotalAmount().scaleByPowerOfTen(2).intValue() >= Math.ceil(1 / easyLearnOrder.getRate())) {
wxPayService = wxPayHandler.getWxPayService(null, wxPayHandler.getSubMchId(easyLearnOrder.getBrandId(), easyLearnOrder.getStoreId()), payNotifyUrl);
if (StringUtils.isBlank(wxPayService.getConfig().getSubMchId())){
if (StringUtils.equals("prod", SpringUtils.getActiveProfile())) {
//联合支付
payNotifyUrl = SpringUtils.getBean(WxPayConfiguration.class).getProperties().getEasyLearnUnionPayNormalNotifyUrl();
} else if (StringUtils.equals("test", SpringUtils.getActiveProfile())){
payNotifyUrl = UNION_PAY_TEST_NORMAL_NOTIFY_URL;
} else {
payNotifyUrl = UNION_PAY_DEV_NORMAL_NOTIFY_URL;
}
return SpringUtils.getBean(WxPayNormalHandler.class).createOrder(easyLearnOrder.getId(),bo.getOpenId(), easyLearnOrder.getTotalPayAmount(), "联合支付", IpUtils.getHostIp(), payNotifyUrl);
} else {
WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
//需要分账
orderRequest.setProfitSharing("Y");
if (easyLearnOrder.getRate() != null && easyLearnOrder.getTotalAmount().scaleByPowerOfTen(2).intValue() >= Math.ceil(1 / easyLearnOrder.getRate())) {
//需要分账
orderRequest.setProfitSharing("Y");
}
orderRequest.setSpbillCreateIp(IpUtils.getHostIp())
.setOpenid(bo.getOpenId())
//商品描述
.setBody("联合支付")
.setOutTradeNo(easyLearnOrder.getId())
//元转分
.setTotalFee(easyLearnOrder.getTotalAmount().scaleByPowerOfTen(2).intValue())
.setTradeType("JSAPI");
return wxPayService.createOrder(orderRequest);
}
orderRequest.setSpbillCreateIp(IpUtils.getHostIp())
.setOpenid(bo.getOpenId())
//商品描述
.setBody("联合支付")
.setOutTradeNo(easyLearnOrder.getId())
//元转分
.setTotalFee(easyLearnOrder.getTotalAmount().scaleByPowerOfTen(2).intValue())
.setTradeType("JSAPI");
return wxPayService.createOrder(orderRequest);
} catch (Exception e) {
throw new ServiceException(e.getMessage());
} finally {
@ -1005,4 +1034,60 @@ public class EasyLearnOrderServiceImpl extends ServiceImpl<EasyLearnOrderMapper,
}
/**
* 微信支付联合支付普通订单通知
* @author DB
* @since 2024/2/22
* @return String
*/
@Override
public String normalUnionPay(HttpServletRequest request, HttpServletResponse response) {
WxPayNormalHandler wxPayNormalHandler = SpringUtils.getBean(WxPayNormalHandler.class);
// 预先设定返回的 response 类型为 xml
response.setHeader("Content-type", "application/xml");
WxUtils wxUtils = SpringUtils.getBean(WxUtils.class);
// 读取参数解析Xml为map
Map<String, String> map = null;
Response okResponse = null;
try {
map = wxUtils.transferXmlToMap(wxUtils.readRequest(request));
// 转换为有序 map判断签名是否正确
boolean isSignSuccess = wxUtils.checkSign(new TreeMap<String, Object>(map), "JamBox20220329174000000000000002");
if (isSignSuccess) {
// 签名校验成功说明是微信服务器发出的数据
String orderId = map.get("out_trade_no");
//获取订单信息
EasyLearnOrder easyLearnOrder = this.getById(orderId);
JSONObject jsonObject = new JSONObject();
EasyLearnPayPayEnum easyLearnPayPayEnum = EasyLearnPayPayEnum.getEasyLearnPayPayEnum(easyLearnOrder.getOrderType());
jsonObject.put("wxOrderId", easyLearnOrder.getId());
jsonObject.put("unionPayOutOrderId", easyLearnOrder.getUnionPayOutOrderId());
jsonObject.put("easyLearnUnionPay", easyLearnPayPayEnum.toString());
String url;
if (StringUtils.equals("prod", SpringUtils.getActiveProfile())) {
url = UNION_PAY_PROD_JAMBOX_NOTIFY_URL;
} else if (StringUtils.equals("test", SpringUtils.getActiveProfile())) {
url = UNION_PAY_TEST_JAMBOX_NOTIFY_URL;
} else {
url = UNION_PAY_DEV_JAMBOX_NOTIFY_URL;
}
//通知晖哥
okResponse = HttpUtils.sendOkHttpPost(url, jsonObject.toJSONString());
//更新订单
this.updateChain().set(EASY_LEARN_ORDER.OUT_ORDER_NO, map.get("transaction_id"))
.set(EASY_LEARN_ORDER.ORDER_STATUS, 1)
.where(EASY_LEARN_ORDER.ID.eq(orderId)).update();
return wxPayNormalHandler.success();
} else {
// 签名校验失败可能不是微信服务器发出的数据
return wxPayNormalHandler.fail();
}
} catch (Exception e) {
return wxPayNormalHandler.fail();
} finally {
assert okResponse != null;
okResponse.close();
}
}
}

View File

@ -3,4 +3,6 @@ wx:
#支付通知地址
easy-learn-once-pay-notify-url: https://oamapi.cpopsz.com/Cpop-Oam/callback/easyLearn/notify/oncePay
#放心学联合支付
easy-learn-union-pay-notify-url: https://oamapi.cpopsz.com/Cpop-Oam/callback/easyLearn/notify/unionPay
easy-learn-union-pay-notify-url: https://oamapi.cpopsz.com/Cpop-Oam/callback/easyLearn/notify/unionPay
#放心学普通支付
easy-learn-union-pay-normal-notify-url: https://oamapi.cpopsz.com/Cpop-Oam/callback/easyLearn/notify/normalUnionPay

View File

@ -137,7 +137,7 @@ public class CpopWxPayTests {
public void getOrderInfo() throws WxPayException {
WxPayConfig config = wxPayService.getConfig();
config.setSubMchId("1661323640");
WxPayOrderQueryResult wxPayOrderQueryResult = wxPayService.queryOrder("4200002096202402199975156147", null);
WxPayOrderQueryResult wxPayOrderQueryResult = wxPayService.queryOrder("4200002123202312279635916930", null);
System.out.println(JSONObject.toJSONString(wxPayOrderQueryResult));
}
@ -150,7 +150,7 @@ public class CpopWxPayTests {
@Test
public void getSharingInfo() throws WxPayException {
ProfitSharingQueryRequest profitSharingQueryRequest = new ProfitSharingQueryRequest();
profitSharingQueryRequest.setOutOrderNo("1736629608413073417").setTransactionId("4200002111202402019716818254");
profitSharingQueryRequest.setOutOrderNo("1736629608413073417").setTransactionId("4200002123202312279635916930");
profitSharingQueryRequest.setSubMchId("1661323640");
ProfitSharingQueryResult result = wxPayService.getProfitSharingService().profitSharingQuery(profitSharingQueryRequest);
System.out.println(result.getResultCode());

View File

@ -328,8 +328,8 @@ public class CpopEasyLearnTest {
@Test
public void queryOrder() throws WxPayException {
WxPayService wxPayService = wxPayHandler.getWxPayService(null, "1661323640");
WxPayOrderQueryResult wxPayOrderQueryResult = wxPayService.queryOrder("4200002111202401103536519415",null);
WxPayService wxPayService = wxPayHandler.getWxPayService(null, "1666928264");
WxPayOrderQueryResult wxPayOrderQueryResult = wxPayService.queryOrder("4200002130202402224927801847",null);
System.out.println(JSONObject.toJSONString(wxPayOrderQueryResult));
}

View File

@ -5,11 +5,16 @@ import com.cpop.core.utils.SpringUtils;
import com.cpop.oam.business.entity.Task;
import com.cpop.oam.business.service.TaskService;
import com.cpop.oam.business.service.TaskStaffGroupService;
import com.cpop.oam.framework.enums.QuartzEnums;
import org.junit.jupiter.api.Test;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.lang.reflect.Array;
import java.time.LocalDateTime;
import java.util.Arrays;
import static com.cpop.oam.business.entity.table.TaskStaffGroupTableDef.TASK_STAFF_GROUP;
/**
@ -63,4 +68,10 @@ public class CpopQrtzTest {
.update();
}
@Test
public void getJobInfo() throws SchedulerException {
LocalDateTime jobExecuteTime = quartzUtils.getJobExecuteTime(QuartzEnums.WORK_ORDER_OVERTIME_TASK.getName() + "118089232185462784", QuartzEnums.WORK_ORDER_OVERTIME_TASK.getGroup());
System.out.println(jobExecuteTime);
}
}

View File

@ -0,0 +1,29 @@
package com.cpop.oam.business.bo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author DB
* @version 1.0.0
* @since 2024-02-21 18:27
*/
@Data
@Accessors(chain = true)
@ApiModel(value = "AgreeOrRejectEnforceFinishBo对象")
public class AgreeOrRejectEnforceFinishBo {
/**
* 工单id
*/
@ApiModelProperty(value = "工单id")
private String id;
/**
* 同意或拒绝
*/
@ApiModelProperty(value = "同意或拒绝")
private Boolean agreeOrReject;
}

View File

@ -53,8 +53,9 @@ public class BackstageClueController {
@ApiParam(value = "品牌") @RequestParam(value = "brandId", required = false) String brandId,
@ApiParam(value = "校区") @RequestParam(value = "storeId", required = false) String storeId,
@ApiParam(value = "负责人或手机号") @RequestParam(value = "chargeOrPhone", required = false) String chargeOrPhone,
@ApiParam(value = "员工id") @RequestParam(value = "staffId", required = false) String staffId) {
Page<CluePageVo> pageVo = clueService.getPersonCluePage(clueStatus, city, brandId, storeId, chargeOrPhone, staffId);
@ApiParam(value = "员工id") @RequestParam(value = "staffId", required = false) String staffId,
@ApiParam(value = "签约月份") @RequestParam(value = "signMonth", required = false) String signMonth) {
Page<CluePageVo> pageVo = clueService.getPersonCluePage(clueStatus, city, brandId, storeId, chargeOrPhone, staffId, signMonth);
return R.ok(pageVo);
}
@ -98,11 +99,12 @@ public class BackstageClueController {
@GetMapping("/getClueFollowUpRecord")
public R<List<ClueFollowUpRecordVo>> getClueFollowUpRecord(@ApiParam(value = "线索id") @RequestParam(value = "clueId") String clueId) {
List<ClueFollowUpRecordVo> vos = clueRecordService.listAs(QueryWrapper.create()
.select(CLUE_RECORD.CREATE_TIME,CLUE_RECORD.RECORD_CONTENT,CLUE_RECORD.RECORD_FILE_URL)
.select(CLUE_RECORD.CREATE_TIME, CLUE_RECORD.RECORD_CONTENT, CLUE_RECORD.RECORD_FILE_URL)
.select(STAFF.NAME.as(ClueFollowUpRecordVo::getStaffName))
.leftJoin(STAFF).on(STAFF.ID.eq(CLUE_RECORD.RECORD_STAFF_ID))
.where(CLUE_RECORD.RECORD_TYPE.eq(2)
.and(CLUE_RECORD.CLUE_ID.eq(clueId))),
.and(CLUE_RECORD.CLUE_ID.eq(clueId)))
.orderBy(CLUE_RECORD.CREATE_TIME.desc()),
ClueFollowUpRecordVo.class);
return R.ok(vos);
}
@ -118,8 +120,9 @@ public class BackstageClueController {
@GetMapping("/getClueRecordList")
public R<List<ClueRecordVo>> getClueRecordList(@ApiParam(value = "线索id") @RequestParam(value = "clueId") String clueId) {
List<ClueRecordVo> vos = clueRecordService.listAs(QueryWrapper.create()
.select(CLUE_RECORD.CREATE_TIME, CLUE_RECORD.RECORD_CONTENT)
.and(CLUE_RECORD.CLUE_ID.eq(clueId)),
.select(CLUE_RECORD.CREATE_TIME, CLUE_RECORD.RECORD_CONTENT,CLUE_RECORD.RECORD_TYPE)
.and(CLUE_RECORD.CLUE_ID.eq(clueId))
.orderBy(CLUE_RECORD.CREATE_TIME.desc()),
ClueRecordVo.class);
return R.ok(vos);
}

View File

@ -2,6 +2,7 @@ package com.cpop.oam.business.controller.backstage;
import com.cpop.core.base.R;
import com.cpop.jambox.business.vo.BrandListVo;
import com.cpop.oam.business.bo.AgreeOrRejectEnforceFinishBo;
import com.cpop.oam.business.bo.PauseWorkOrderBo;
import com.cpop.oam.business.bo.TaskWorkOrderBo;
import com.cpop.oam.business.bo.TaskWorkOrderRecordBo;
@ -217,4 +218,18 @@ public class BackstageTaskWorkOrderController {
Page<TaskWorkOrderStatPageVo> page = taskWorkOrderService.getWorkOrderStatPage(staffId, startDate, endDate);
return R.ok(page);
}
/**
* 同意与拒绝强制办结
* @author DB
* @since 2024/2/21
* @param bo 请求参数
* @return R<Void>
*/
@ApiOperation("工单模块-工单提交-同意与拒绝强制办结")
@PutMapping("/agreeOrRejectEnforceFinish")
public R<Void> agreeOrRejectEnforceFinish(@RequestBody @Validated AgreeOrRejectEnforceFinishBo bo) {
taskWorkOrderService.agreeOrRejectEnforceFinish(bo);
return R.ok();
}
}

View File

@ -3,6 +3,7 @@ package com.cpop.oam.business.entity;
import com.cpop.core.base.entity.BaseEntity;
import com.cpop.core.base.entity.BaseInsertListener;
import com.cpop.core.base.entity.BaseUpdateListener;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.Table;
@ -74,6 +75,16 @@ public class Clue extends BaseEntity implements Serializable {
*/
private Integer clueStatus;
/**
* 最后接收时间
*/
private LocalDateTime lastReceiptTime;
/**
* 最后接收员工id
*/
private String lastReceiptStaffId;
/**
* 是否删除(0否1是)
*/

View File

@ -45,7 +45,7 @@ public class Task extends BaseEntity implements Serializable {
private String responsibleStaffId;
/**
* 任务状态(-1:失败;0:待审核;1:待接受;2:进行中;3:待测试;5:暂停;7:接收超时;8:待部署;9:已归档;10:审核不通过)
* 任务状态(-1:失败;0:待审核;1:待接受;2:进行中;3:待测试;5:暂停;6:强制办结;7:接收超时;8:待部署;9:已归档;10:审核不通过)
*/
private Integer taskStatus;

View File

@ -88,6 +88,21 @@ public class TaskWorkOrder extends BaseEntity implements Serializable {
*/
private String enforceFinishReason;
/**
* 强制办结员工id
*/
private String enforceFinishStaffId;
/**
* 强制办结时间
*/
private LocalDateTime enforceFinishTime;
/**
* 强制办结剩余时间
*/
private Integer enforceFinishSurplusTime;
/**
* 是否删除(0否1是)
*/

View File

@ -24,7 +24,7 @@ public interface ClueService extends IService<Clue> {
* @since 2024/2/19
* @return R<Page<SignGoalPageVo>>
*/
Page<CluePageVo> getPersonCluePage(Integer clueStatus, String city, String brandId, String storeId, String chargeOrPhone, String staffId);
Page<CluePageVo> getPersonCluePage(Integer clueStatus, String city, String brandId, String storeId, String chargeOrPhone, String staffId, String signMonth);
/**
* 个人签约目标

View File

@ -1,12 +1,9 @@
package com.cpop.oam.business.service;
import com.cpop.oam.business.bo.EnforceFinishWorkOrderBo;
import com.cpop.oam.business.bo.*;
import com.cpop.oam.business.vo.*;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
import com.cpop.oam.business.bo.PauseWorkOrderBo;
import com.cpop.oam.business.bo.TaskWorkOrderBo;
import com.cpop.oam.business.bo.TaskWorkOrderRecordBo;
import com.cpop.oam.business.entity.TaskWorkOrder;
import java.time.LocalDate;
@ -131,4 +128,12 @@ public interface TaskWorkOrderService extends IService<TaskWorkOrder> {
* @param bo 请求参数
*/
void enforceFinishWorkOrder(EnforceFinishWorkOrderBo bo);
/**
* 工单模块-工单提交-同意与拒绝强制办结
* @author DB
* @since 2024/2/21
* @param bo 请求参数
*/
void agreeOrRejectEnforceFinish(AgreeOrRejectEnforceFinishBo bo);
}

View File

@ -400,6 +400,7 @@ public class BusinessServiceImpl extends ServiceImpl<BusinessMapper, Business> i
StoreSign storeSign = new StoreSign();
storeSign.setStoreId(id);
storeSign.setSignStaffId(loginUserInfo.getString("id"));
storeSign.setSignMonth(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")));
StoreSignService storeSignService = SpringUtils.getBean(StoreSignService.class);
storeSignService.save(storeSign);
SpringUtils.getBean(StoreService.class).updateChain()

View File

@ -28,15 +28,18 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import static com.cpop.oam.business.entity.table.CluePutOffTableDef.CLUE_PUT_OFF;
import static com.cpop.oam.business.entity.table.ClueRecordTableDef.CLUE_RECORD;
import static com.cpop.oam.business.entity.table.ClueTableDef.CLUE;
import static com.cpop.oam.business.entity.table.ClueUpdateTableDef.CLUE_UPDATE;
import static com.cpop.oam.business.entity.table.SignAreaTableDef.SIGN_AREA;
import static com.cpop.oam.business.entity.table.SignGoalTableDef.SIGN_GOAL;
import static com.cpop.oam.business.entity.table.StaffTableDef.STAFF;
import static com.cpop.system.business.entity.table.BrandTableDef.BRAND;
import static com.cpop.system.business.entity.table.StoreSignTableDef.STORE_SIGN;
import static com.cpop.system.business.entity.table.StoreTableDef.STORE;
import static com.mybatisflex.core.query.QueryMethods.*;
@ -56,7 +59,7 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
* @return R<Page<SignGoalPageVo>>
*/
@Override
public Page<CluePageVo> getPersonCluePage(Integer clueStatus, String city, String brandId, String storeId, String chargeOrPhone, String staffId) {
public Page<CluePageVo> getPersonCluePage(Integer clueStatus, String city, String brandId, String storeId, String chargeOrPhone, String staffId, String signMonth) {
PageDomain pageDomain = SqlUtils.getInstance().getPageDomain();
QueryWrapper queryWrapper = QueryWrapper.create();
if (clueStatus != null){
@ -78,6 +81,7 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
.select(CLUE.CITY,CLUE.ID,CLUE.CREATE_TIME,CLUE.DEV_STAFF_ID,CLUE.LAST_FOLLOW_UP_TIME,CLUE.LAST_FOLLOW_UP_CONTENT,CLUE.RECEIPT_TIME,CLUE.STORE_ID)
.select(STORE.STORE_NAME,STORE.STORE_ADDR,STORE.PERSON_CHARGE,STORE.PHONE)
.select(BRAND.BRAND_NAME)
.select(STORE_SIGN.EXPIRE_DATE)
// 跟进间隔
.select(dateDiff(now(), CLUE.LAST_FOLLOW_UP_TIME).as(CluePageVo::getFollowUpInterval))
// 签约时间
@ -85,6 +89,7 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
.from(CLUE)
.leftJoin(STORE).on(STORE.ID.eq(CLUE.STORE_ID))
.leftJoin(BRAND).on(BRAND.ID.eq(STORE.BRAND_ID))
.leftJoin(STORE_SIGN).on(STORE_SIGN.STORE_ID.eq(STORE.ID))
//地区
.and(CLUE.CITY.eq(city))
//品牌
@ -92,6 +97,7 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
//校区
.and(CLUE.STORE_ID.eq(storeId))
.and(STORE.PERSON_CHARGE.likeLeft(chargeOrPhone).or(STORE.PHONE.eq(chargeOrPhone)))
.and(STORE_SIGN.SIGN_MONTH.eq(signMonth))
.orderBy(changeOrderColumn(pageDomain)),
CluePageVo.class,
//开发员工
@ -156,6 +162,9 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
queryWrapper.and(SIGN_GOAL.STAFF_ID.eq(loginUserInfo.getString("id")));
}
SignGoalService signGoalService = SpringUtils.getBean(SignGoalService.class);
if (StringUtils.isBlank(signMonth)) {
signMonth = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"));
}
String[] split = signMonth.split("-");
SignGoal signGoal = signGoalService.getOne(queryWrapper.where(SIGN_GOAL.YEAR.eq(Integer.parseInt(split[0]))));
PersonSignGoalVo vo = new PersonSignGoalVo();
@ -284,6 +293,7 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
.select(STAFF.NAME.as(ClueUpdateVo.ClueUpdateRecordVo::getStaffName))
.leftJoin(STAFF).on(STAFF.ID.eq(CLUE_UPDATE.UPDATE_STAFF_ID))
.where(CLUE_UPDATE.CLUE_ID.eq(clueId))
.orderBy(CLUE_UPDATE.CREATE_TIME.desc())
.listAs(ClueUpdateVo.ClueUpdateRecordVo.class);
clueUpdateVo.setRecordVos(recordVos);
//获取现有校区数据
@ -328,7 +338,9 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
@Override
public List<CluePutOffVo> getCluePutOffRecord(String clueId) {
return SpringUtils.getBean(CluePutOffService.class).getMapper().selectListByQueryAs(QueryWrapper.create()
.select(CLUE_PUT_OFF.PUT_OFF_DATE, CLUE_PUT_OFF.PUT_OFF_REASON, CLUE_PUT_OFF.CREATE_TIME, CLUE_PUT_OFF.PUT_OFF_STAFF_ID, CLUE_PUT_OFF.AUDIT_STAFF_ID, CLUE_PUT_OFF.AUDIT_STATUS),
.select(CLUE_PUT_OFF.PUT_OFF_DATE, CLUE_PUT_OFF.PUT_OFF_REASON, CLUE_PUT_OFF.CREATE_TIME, CLUE_PUT_OFF.PUT_OFF_STAFF_ID, CLUE_PUT_OFF.AUDIT_STAFF_ID, CLUE_PUT_OFF.AUDIT_STATUS,
CLUE_PUT_OFF.CREATE_TIME,CLUE_PUT_OFF.PUT_OFF_FILE_URL)
.orderBy(CLUE_PUT_OFF.CREATE_TIME.desc()),
CluePutOffVo.class,
//延迟员工
item -> item.field(CluePutOffVo::getPutOffStaffName)
@ -367,8 +379,10 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
if (clue.getClueStatus() == 1) {
throw new ServiceException("当前线索已签约,不允许脱离");
}
clue.setClueStatus(0).setResponsibleStaffId(null);
this.updateById(clue);
this.updateChain().set(CLUE.CLUE_STATUS, 0)
.set(CLUE.RESPONSIBLE_STAFF_ID, null)
.where(CLUE.ID.eq(clue.getId()))
.update();
JSONObject loginUserInfo = SecurityUtils.getInstance().getLoginUserInfo();
ClueRecord clueRecord = new ClueRecord();
clueRecord.setClueId(id)
@ -406,7 +420,7 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
}
return this.mapper.paginateAs(Page.of(pageDomain.getPageNum(),pageDomain.getPageSize()),
queryWrapper
.select(CLUE.CITY,CLUE.ID,CLUE.CREATE_TIME,CLUE.DEV_STAFF_ID,CLUE.LAST_FOLLOW_UP_TIME,CLUE.LAST_FOLLOW_UP_CONTENT,CLUE.RECEIPT_TIME,CLUE.STORE_ID)
.select(CLUE.CITY,CLUE.ID,CLUE.CREATE_TIME,CLUE.DEV_STAFF_ID,CLUE.LAST_FOLLOW_UP_TIME,CLUE.LAST_FOLLOW_UP_CONTENT,CLUE.RECEIPT_TIME,CLUE.STORE_ID,CLUE.LAST_RECEIPT_TIME,CLUE.LAST_RECEIPT_STAFF_ID)
.select(STORE.STORE_NAME,STORE.STORE_ADDR,STORE.PERSON_CHARGE,STORE.PHONE)
.select(BRAND.BRAND_NAME)
// 跟进间隔
@ -437,6 +451,17 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
} else {
return null;
}
}),
//最后领取员工
item -> item.field(CluePageVo::getLastReceiptStaffName)
.queryWrapper(lastReceiptStaff -> {
if (StringUtils.isNotBlank(lastReceiptStaff.getLastReceiptStaffId())) {
return queryChain().select(STAFF.NAME.as(CluePageVo::getDevStaffName))
.from(STAFF)
.where(STAFF.ID.eq(lastReceiptStaff.getLastReceiptStaffId()));
} else {
return null;
}
}));
}
@ -465,7 +490,7 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
}
return this.mapper.paginateAs(Page.of(pageDomain.getPageNum(),pageDomain.getPageSize()),
queryWrapper
.select(CLUE.CITY,CLUE.ID,CLUE.CREATE_TIME,CLUE.DEV_STAFF_ID,CLUE.LAST_FOLLOW_UP_TIME,CLUE.LAST_FOLLOW_UP_CONTENT,CLUE.RECEIPT_TIME,CLUE.STORE_ID)
.select(CLUE.CITY,CLUE.ID,CLUE.CREATE_TIME,CLUE.DEV_STAFF_ID,CLUE.LAST_FOLLOW_UP_TIME,CLUE.LAST_FOLLOW_UP_CONTENT,CLUE.RECEIPT_TIME,CLUE.STORE_ID,CLUE.LAST_RECEIPT_TIME,CLUE.LAST_RECEIPT_STAFF_ID)
.select(STORE.STORE_NAME,STORE.STORE_ADDR,STORE.PERSON_CHARGE,STORE.PHONE)
.select(BRAND.BRAND_NAME)
// 跟进间隔
@ -496,6 +521,17 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
} else {
return null;
}
}),
//最后领取员工
item -> item.field(CluePageVo::getLastReceiptStaffName)
.queryWrapper(lastReceiptStaff -> {
if (StringUtils.isNotBlank(lastReceiptStaff.getLastReceiptStaffId())) {
return queryChain().select(STAFF.NAME.as(CluePageVo::getDevStaffName))
.from(STAFF)
.where(STAFF.ID.eq(lastReceiptStaff.getLastReceiptStaffId()));
} else {
return null;
}
}));
}
@ -518,7 +554,9 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
clue.setResponsibleStaffId(loginUserInfo.getString("id"))
.setReceiptTime(now)
.setLastFollowUpTime(now)
.setLastFollowUpContent(content);
.setLastFollowUpContent(content)
.setLastReceiptStaffId(loginUserInfo.getString("id"))
.setLastReceiptTime(now);
this.updateById(clue);
//插入记录
ClueRecord clueRecord = new ClueRecord();

View File

@ -48,7 +48,7 @@ public class SignGoalServiceImpl extends ServiceImpl<SignGoalMapper, SignGoal> i
PageDomain pageDomain = SqlUtils.getInstance().getPageDomain();
return this.mapper.paginateAs(Page.of(pageDomain.getPageNum(),pageDomain.getPageSize()),
QueryWrapper.create()
.select(SIGN_GOAL.ID,SIGN_GOAL.YEAR,SIGN_GOAL.JAN_GOAL,SIGN_GOAL.JAN_FINISH,SIGN_GOAL.FEB_GOAL,SIGN_GOAL.FEB_FINISH,SIGN_GOAL.MAR_GOAL,SIGN_GOAL.MAR_FINISH,
.select(SIGN_GOAL.ID,SIGN_GOAL.STAFF_ID,SIGN_GOAL.YEAR,SIGN_GOAL.JAN_GOAL,SIGN_GOAL.JAN_FINISH,SIGN_GOAL.FEB_GOAL,SIGN_GOAL.FEB_FINISH,SIGN_GOAL.MAR_GOAL,SIGN_GOAL.MAR_FINISH,
SIGN_GOAL.APR_GOAL,SIGN_GOAL.APR_FINISH,SIGN_GOAL.MAY_GOAL,SIGN_GOAL.MAY_FINISH,SIGN_GOAL.JUN_GOAL,SIGN_GOAL.JUN_FINISH,SIGN_GOAL.JUL_GOAL,
SIGN_GOAL.JUL_FINISH,SIGN_GOAL.AUG_GOAL,SIGN_GOAL.AUG_FINISH,SIGN_GOAL.SEP_GOAL,SIGN_GOAL.SEP_FINISH,SIGN_GOAL.OCT_GOAL,SIGN_GOAL.OCT_FINISH,
SIGN_GOAL.NOV_GOAL,SIGN_GOAL.NOV_FINISH,SIGN_GOAL.DEC_GOAL,SIGN_GOAL.DEC_FINISH)

View File

@ -811,7 +811,7 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
.select(TASK.ID, TASK.TASK_CONTENT, TASK.TASK_STATUS)
.select(TASK_STAFF_GROUP.GRADE_POINT, TASK_STAFF_GROUP.REMARK)
.leftJoin(TASK_STAFF_GROUP).on(TASK_STAFF_GROUP.TASK_ID.eq(TASK.ID))
.where(TASK.TASK_STATUS.in(2, 3, 5, 7, 8, 9))
.where(TASK.TASK_STATUS.in(2, 3, 5, 6, 7, 8, 9))
, TaskIndividualGpaDetailVo.class);
TaskIndividualGpaVo vo = new TaskIndividualGpaVo();
if (individualGpas.isEmpty()) {

View File

@ -7,6 +7,7 @@ import com.cpop.common.utils.StringUtils;
import com.cpop.common.utils.bean.BeanUtils;
import com.cpop.core.base.entity.PageDomain;
import com.cpop.core.base.exception.ServiceException;
import com.cpop.core.base.exception.UtilException;
import com.cpop.core.service.RedisService;
import com.cpop.core.utils.QuartzUtils;
import com.cpop.core.utils.SecurityUtils;
@ -101,7 +102,7 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
.on(STORE.ID.eq(TASK_WORK_ORDER.STORE_ID))
// 工单
.where(TASK.TASK_TYPE.eq(2))
.and(TASK.TASK_STATUS.in(1, 2, 4, 5, 7)),
.and(TASK.TASK_STATUS.in(1, 2, 4, 5, 6, 7)),
TaskWorkOrderReceiveDealPauseDto.class,
// 提交人
item -> item.field(TaskWorkOrderReceiveDealPauseDto::getRecordStaffName)
@ -139,6 +140,7 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
taskWorkOrderReceiveDealPauseVo.getReceiveList().addAll(receiveList);
}
break;
case 6:
// 进行中
case 2:
// 逾期
@ -334,7 +336,7 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
QuartzEnums acceptEnums = QuartzEnums.WORK_ORDER_FINISH_OVERTIME_REMIND_TASK;
// 开始
if (status == 0) {
LocalDateTime localDateTime = dateTime.plusMinutes(105);
LocalDateTime localDateTime = dateTime.plusMinutes(150);
ZoneId zoneId = ZoneId.systemDefault();
String cron = quartzUtils.convertToCron(Date.from(localDateTime.atZone(zoneId).toInstant()));
// 通过JobBuilder构建JobDetail实例JobDetail规定其job只能是实现Job接口的实例
@ -350,7 +352,7 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
.build();
scheduler.scheduleJob(jobDetail, cronTrigger);
} else if (status == 1) {
LocalDateTime localDateTime = dateTime.plusMinutes(105);
LocalDateTime localDateTime = dateTime.plusMinutes(150);
ZoneId zoneId = ZoneId.systemDefault();
String cron = quartzUtils.convertToCron(Date.from(localDateTime.atZone(zoneId).toInstant()));
//修改
@ -410,15 +412,16 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
* @param workOrderId 工单id
* @param isUpdate 是否更新
* @param dateTime 开始时间
* @param minute 延迟多少分钟
* @author DB
* @since 2023-11-29 16:09:02
*/
private void startOrUpdateWorkOrderOvertimeTask(String workOrderId, Boolean isUpdate, LocalDateTime dateTime) {
private void startOrUpdateWorkOrderOvertimeTask(String workOrderId, Boolean isUpdate, LocalDateTime dateTime,Integer minute) {
// 基于表达式构建触发器
QuartzUtils quartzUtils = SpringUtils.getBean(QuartzUtils.class);
try {
//实际小时
LocalDateTime localDateTime = dateTime.plusHours(2);
//实际小时
LocalDateTime localDateTime = dateTime.plusMinutes(minute);
ZoneId zoneId = ZoneId.systemDefault();
String cron = quartzUtils.convertToCron(Date.from(localDateTime.atZone(zoneId).toInstant()));
if (isUpdate) {
@ -447,6 +450,19 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
}
}
/**
* 生成工单超时任务
*
* @param workOrderId 工单id
* @param isUpdate 是否更新
* @param dateTime 开始时间
* @author DB
* @since 2023-11-29 16:09:02
*/
private void startOrUpdateWorkOrderOvertimeTask(String workOrderId, Boolean isUpdate, LocalDateTime dateTime) {
startOrUpdateWorkOrderOvertimeTask(workOrderId, isUpdate, dateTime, 180);
}
/**
* 工单模块-工单提交-工单记录列表
*
@ -743,7 +759,7 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
.select(SYS_USER.PHONE_NUMBER.as(StaffInfoVo::getPhoneNumber))
.from(STAFF)
.leftJoin(SYS_USER)
.on(SYS_USER.ID.eq(STAFF.ID))
.on(SYS_USER.ID.eq(STAFF.USER_ID))
.where(STAFF.ID.in(staffIds)),
StaffInfoVo.class)
.stream().collect(Collectors.toMap(StaffInfoVo::getId, item -> item));
@ -800,7 +816,7 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
.on(BRAND.ID.eq(TASK_WORK_ORDER.BRAND_ID))
.leftJoin(STORE)
.on(STORE.ID.eq(TASK_WORK_ORDER.STORE_ID))
.where(TASK.TASK_STATUS.in(1, 2, 5, 7)),
.where(TASK.TASK_STATUS.in(1, 2, 4, 5, 7)),
TaskWorkOrderPersonVo.class,
// 提交人
item -> item.field(TaskWorkOrderPersonVo::getRecordStaffName)
@ -943,7 +959,148 @@ public class TaskWorkOrderServiceImpl extends ServiceImpl<TaskWorkOrderMapper, T
* @param bo 请求参数
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void enforceFinishWorkOrder(EnforceFinishWorkOrderBo bo) {
throw new ServiceException("功能未实现");
// 获取工单信息
TaskWorkOrder taskWorkOrder = this.getById(bo.getId());
JSONObject loginUserInfo = SecurityUtils.getInstance().getLoginUserInfo();
taskWorkOrder.setEnforceFinishStaffId(loginUserInfo.getString("id"));
LocalDateTime finishTime = LocalDateTime.now();
taskWorkOrder.setEnforceFinishTime(finishTime);
taskWorkOrder.setEnforceFinishReason(bo.getEnforceFinishReason());
// 读取工单办结定时任务
QuartzUtils quartzUtils = SpringUtils.getBean(QuartzUtils.class);
try {
LocalDateTime jobExecuteTime = quartzUtils.getJobExecuteTime(QuartzEnums.WORK_ORDER_OVERTIME_TASK.getName() + bo.getId(), QuartzEnums.WORK_ORDER_OVERTIME_TASK.getGroup());
Duration duration = Duration.between(finishTime, jobExecuteTime);
Long minutes = duration.toMinutes();
taskWorkOrder.setEnforceFinishSurplusTime(minutes.intValue());
} catch (SchedulerException e) {
throw new UtilException(e);
}
// 设置工单强制办结
TaskService taskService = SpringUtils.getBean(TaskService.class);
Task task = taskService.getOne(QueryWrapper.create().where(TASK.ID.eq(taskWorkOrder.getTaskId())));
//检查工单接受时间距离现在是否大于120分钟
Duration between = Duration.between(task.getTaskReceiptTime(), finishTime);
long minutes = between.toMinutes();
if (minutes - 120 <= 0) {
throw new ServiceException("强制办结工单的接受时间距离现在必须大于120分钟");
}
//设置强制办结
task.setTaskItem(1).setTaskStatus(6);
taskService.updateById(task);
// 更新工单
this.updateById(taskWorkOrder);
// 插入工单暂停记录
TaskWorkOrderRecord taskWorkOrderRecord = new TaskWorkOrderRecord();
String finishTimeString = "提交工单办结申请,时间为:" + finishTime.format(DateTimeFormatter.ofPattern(DateUtils.YYYY_MM_DD_HH_MM_SS));
// 读取员工信息
taskWorkOrderRecord.setRecordStaffId(loginUserInfo.getString("id"))
.setTaskWorkOrderId(bo.getId())
.setRecordText(finishTimeString);
// 插入记录
SpringUtils.getBean(TaskWorkOrderRecordService.class).save(taskWorkOrderRecord);
// 负责人手机号
StaffService staffService = SpringUtils.getBean(StaffService.class);
Set<String> staffIds = new HashSet<>();
staffIds.add(task.getResponsibleStaffId());
staffIds.add(task.getRecordStaffId());
Map<String, StaffInfoVo> staffMap = staffService.listAs(QueryWrapper.create()
.select(STAFF.ID, STAFF.NAME)
.select(SYS_USER.PHONE_NUMBER.as(StaffInfoVo::getPhoneNumber))
.from(STAFF)
.leftJoin(SYS_USER)
.on(SYS_USER.ID.eq(STAFF.USER_ID))
.where(STAFF.ID.in(staffIds)),
StaffInfoVo.class)
.stream().collect(Collectors.toMap(StaffInfoVo::getId, item -> item));
List<String> phoneList = new ArrayList<>();
phoneList.add(staffMap.get(task.getResponsibleStaffId()).getPhoneNumber());
// 记录人手机号
phoneList.add(staffMap.get(task.getRecordStaffId()).getPhoneNumber());
// 通知记录
try {
SpringUtils.getBean(WebHookSendHandler.class).webHookSendText(WebHookKeyConstant.ORDER_INFO_BOT, phoneList, task.getTaskContent() + "\n" + finishTimeString, false);
} catch (IOException e) {
throw new ServiceException("发送消息通知失败!");
}
// 删除工单完结超时任务
try {
SpringUtils.getBean(QuartzUtils.class).deleteJob(QuartzEnums.WORK_ORDER_OVERTIME_TASK.getName() + bo.getId(), QuartzEnums.WORK_ORDER_OVERTIME_TASK.getGroup());
// 删除工单暂停恢复任务
startOrRemoveWorkOrderPauseRecover(bo.getId(), false, null);
// 删除工单完结超时任务
workOrderFinishOverTimeRemindTask(bo.getId(), 2, null);
} catch (SchedulerException e) {
throw new ServiceException("删除工单完结超时任务失败");
}
}
/**
* 工单模块-工单提交-同意与拒绝强制办结
* @author DB
* @since 2024/2/21
* @param bo 请求参数
*/
@Override
public void agreeOrRejectEnforceFinish(AgreeOrRejectEnforceFinishBo bo) {
TaskWorkOrder workOrder = this.getById(bo.getId());
TaskService taskService = SpringUtils.getBean(TaskService.class);
Task task = taskService.getById(workOrder.getTaskId());
JSONObject loginUserInfo = SecurityUtils.getInstance().getLoginUserInfo();
String finishString;
//同意
if (bo.getAgreeOrReject()){
task.setTaskStatus(9);
task.setTaskItem(3);
taskService.updateById(task);
workOrder.setFinishStaffId(loginUserInfo.getString("id"));
workOrder.setFinishTime(workOrder.getEnforceFinishTime());
this.updateById(workOrder);
finishString = "\n工单办结申请通过,办结时间为:" + workOrder.getFinishTime().format(DateTimeFormatter.ofPattern(DateUtils.YYYY_MM_DD_HH_MM_SS));
} else {
//拒绝继续计时
task.setTaskStatus(2);
task.setTaskItem(0);
taskService.updateById(task);
LocalDateTime now = LocalDateTime.now();
finishString = "\n工单办结申请未通过,工单到期时间为:" + now.plusMinutes(workOrder.getEnforceFinishSurplusTime().longValue()).format(DateTimeFormatter.ofPattern(DateUtils.YYYY_MM_DD_HH_MM_SS)) +
"\n工单到期剩余时间为:" + workOrder.getEnforceFinishSurplusTime() + "分钟";
//重新发起工单超时任务
startOrUpdateWorkOrderOvertimeTask(workOrder.getId(), false, now, workOrder.getEnforceFinishSurplusTime());
}
// 插入工单暂停记录
TaskWorkOrderRecord taskWorkOrderRecord = new TaskWorkOrderRecord();
// 读取员工信息
taskWorkOrderRecord.setRecordStaffId(loginUserInfo.getString("id"))
.setTaskWorkOrderId(workOrder.getId())
.setRecordText(finishString);
// 插入记录
SpringUtils.getBean(TaskWorkOrderRecordService.class).save(taskWorkOrderRecord);
// 负责人手机号
StaffService staffService = SpringUtils.getBean(StaffService.class);
Set<String> staffIds = new HashSet<>();
staffIds.add(task.getResponsibleStaffId());
staffIds.add(task.getRecordStaffId());
Map<String, StaffInfoVo> staffMap = staffService.listAs(QueryWrapper.create()
.select(STAFF.ID, STAFF.NAME)
.select(SYS_USER.PHONE_NUMBER.as(StaffInfoVo::getPhoneNumber))
.from(STAFF)
.leftJoin(SYS_USER)
.on(SYS_USER.ID.eq(STAFF.USER_ID))
.where(STAFF.ID.in(staffIds)),
StaffInfoVo.class)
.stream().collect(Collectors.toMap(StaffInfoVo::getId, item -> item));
List<String> phoneList = new ArrayList<>();
phoneList.add(staffMap.get(task.getResponsibleStaffId()).getPhoneNumber());
// 记录人手机号
phoneList.add(staffMap.get(task.getRecordStaffId()).getPhoneNumber());
// 通知记录
try {
SpringUtils.getBean(WebHookSendHandler.class).webHookSendText(WebHookKeyConstant.ORDER_INFO_BOT, phoneList, task.getTaskContent() + "\n" + finishString, false);
} catch (IOException e) {
throw new ServiceException("发送消息通知失败!");
}
}
}

View File

@ -136,4 +136,29 @@ public class CluePageVo {
@ApiModelProperty(value = "签约状态(0:待签约;1:已签约;2:警告线索)")
private Integer clueStatus;
/**
* 到期日期
*/
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private LocalDate expireDate;
/**
* 最后接收时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@ApiModelProperty(value = "最后接收时间")
private String lastReceiptTime;
/**
* 最后接收员工id
*/
@ApiModelProperty(value = "最后接收员工id")
private String lastReceiptStaffId;
/**
* 最后接收员工姓名
*/
@ApiModelProperty(value = "最后接收员工姓名")
private String lastReceiptStaffName;
}

View File

@ -1,5 +1,6 @@
package com.cpop.oam.business.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -7,6 +8,7 @@ import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* @author DB
@ -60,4 +62,17 @@ public class CluePutOffVo {
*/
@ApiModelProperty(value = "延期原因")
private String putOffReason;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 延期附件
*/
@ApiModelProperty(value = "延期附件")
private String putOffFileUrl;
}

View File

@ -30,6 +30,12 @@ public class SignGoalPageVo {
@ApiModelProperty(value = "员工名")
private String staffName;
/**
* 员工Id
*/
@ApiModelProperty(value = "员工Id")
private String staffId;
/**
* 签约区域
*/

View File

@ -72,5 +72,5 @@ public class TaskWorkOrderPersonVo {
* 工单状态
*/
@ApiModelProperty(value = "工单状态")
private Integer workOrderStatus;
private Integer taskStatus;
}

View File

@ -153,6 +153,12 @@ public class TaskWorkOrderReceiveDealPauseVo {
@ApiModelProperty(value = "任务接收用时/min")
private Integer receivingTime;
/**
* 任务状态(-1:失败;0:待审核;1:待接受;2:进行中;3:待测试;5:暂停;7:接收超时;8:待部署;9:待归档;10:审核不通过)
*/
@ApiModelProperty(value = "任务状态(-1:失败;0:待审核;1:待接受;2:进行中;3:待测试;5:暂停;7:接收超时;8:待部署;9:待归档;10:审核不通过)")
private Integer taskStatus;
}
/**

View File

@ -24,6 +24,11 @@
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jdom/jdom2 -->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -100,4 +100,9 @@ public class WxPayProperties {
* 先学后付用户支付成功通知地址
*/
private String learnNowPayLaterServiceOrderNotifyUrl;
/**
* 普通模式下的商户号
*/
private String easyLearnUnionPayNormalNotifyUrl;
}

View File

@ -5,6 +5,8 @@ import com.cpop.common.utils.ServletUtils;
import com.cpop.common.utils.StringUtils;
import com.cpop.core.base.exception.ServiceException;
import com.cpop.core.utils.SecurityUtils;
import com.cpop.core.utils.SpringUtils;
import com.cpop.pay.framewok.config.wxPay.WxPayConfiguration;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
@ -44,7 +46,7 @@ public class WxPayHandler {
}
String brandId = loginUserInfo.getString("brandId");
Row brand = Db.selectOneByQuery("cp_sys_brand", QueryWrapper.create().where("id = ?", brandId));
if (brand.isEmpty()) {
if (brand.isEmpty() || StringUtils.isBlank(brand.getString("wxMchId"))) {
throw new ServiceException("当前品牌暂未开通微信支付,请联系相关客服");
}
//检查是否开启分账
@ -121,7 +123,7 @@ public class WxPayHandler {
if (brand != null && StringUtils.isNotBlank(brand.getString("wxMchId"))){
wxMchId = brand.getString("wxMchId");
}else {
wxMchId = "1661323640";
wxMchId = null;
}
}
} else {
@ -130,7 +132,7 @@ public class WxPayHandler {
if (brand != null && StringUtils.isNotBlank(brand.getString("wxMchId"))){
wxMchId = brand.getString("wxMchId");
}else {
wxMchId = "1661323640";
wxMchId = null;
}
}
return wxMchId;
@ -152,4 +154,5 @@ public class WxPayHandler {
log.info("微信支付分回调ID:{}",request.getHeader("Request-ID"));
return signatureHeader;
}
}

View File

@ -0,0 +1,65 @@
package com.cpop.pay.framewok.handler.wxPay;
import com.cpop.core.utils.SpringUtils;
import com.cpop.pay.framewok.config.wxPay.WxPayConfiguration;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @author DB
* @version 1.0.0
* @since 2024-02-22 20:10
*/
@Component
public class WxPayNormalHandler {
@Autowired
private WxUtils wxUtils;
public <T> T createOrder(String orderId,String openId, BigDecimal price, String body, String ipAddress,String notifyUrl) throws IOException {
SortedMap<String, Object> parameters = new TreeMap<String, Object>();
parameters.put("appid", "wx20853d18c455e874");
parameters.put("mch_id", "1618436087");
parameters.put("openid", openId);
// 默认 "WEB"
parameters.put("device_info", "WEB");
parameters.put("body", body);
// 32 位随机字符串
parameters.put("nonce_str", wxUtils.gen32RandomString());
parameters.put("notify_url", notifyUrl);
parameters.put("out_trade_no", orderId);
parameters.put("total_fee", price.multiply(BigDecimal.valueOf(100)).intValue());
// parameters.put("total_fee", 1); // 测试时将支付金额设置为 1 分钱
parameters.put("spbill_create_ip", ipAddress);
parameters.put("trade_type", "JSAPI");
// sign 必须在最后
parameters.put("sign", wxUtils.createSign(parameters, "JamBox20220329174000000000000002"));
// 执行 HTTP 请求获取接收的字符串一段 XML
String result = wxUtils.executeHttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder", parameters);
return wxUtils.createSign2(result, "JamBox20220329174000000000000002");
}
public String fail() {
return "<xml>\n" +
" <return_code><![CDATA[FAIL]]></return_code>\n" +
" <return_msg><![CDATA[]]></return_msg>\n" +
"</xml>";
}
public String success() {
return "<xml>\n" +
" <return_code><![CDATA[SUCCESS]]></return_code>\n" +
" <return_msg><![CDATA[OK]]></return_msg>\n" +
"</xml>";
}
}

View File

@ -0,0 +1,283 @@
package com.cpop.pay.framewok.handler.wxPay;
import com.cpop.common.utils.StringUtils;
import com.cpop.core.base.exception.UtilException;
import com.github.binarywang.wxpay.bean.order.WxPayAppOrderResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.util.SignUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.stereotype.Component;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
* @author DB
* @version 1.0.0
* @since 2024-02-22 20:13
*/
@Component
public class WxUtils {
/**
* 执行 POST 方法的 HTTP 请求
*
* @param url
* @param parameters
* @return
* @throws IOException
*/
public String executeHttpPost(String url, SortedMap<String, Object> parameters) throws IOException {
HttpClient client = HttpClients.createDefault();
HttpPost request = new HttpPost(url);
request.setHeader("Content-type", "application/xml");
request.setHeader("Accept", "application/xml");
request.setEntity(new StringEntity(transferMapToXml(parameters), "UTF-8"));
HttpResponse response = client.execute(request);
return readResponse(response);
}
/**
* 第一次签名
*
* @param parameters 数据为服务器生成下单时必须的字段排序签名
* @param key
* @return
*/
public String createSign(SortedMap<String, Object> parameters, String key) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序升序
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + key);
return encodeMD5(sb.toString());
}
/**
* 第二次签名
*
* @param result 数据为微信返回给服务器的数据XML String再次签名后传回给客户端APP使用
* @param key 密钥
* @return
* @throws IOException
*/
public <T> T createSign2(String result, String key) throws IOException {
SortedMap<String, Object> map = new TreeMap<>(transferXmlToMap(result));
if (StringUtils.equals(map.get("result_code").toString(),"FAIL")){
throw new UtilException(map.get("err_code_des").toString());
}
//wxJava获取签名
String signType = WxPayConstants.SignType.MD5;
String appid = map.get("appid").toString();
WxPayMpOrderResult payResult = WxPayMpOrderResult.builder()
.appId(appid)
.timeStamp(String.valueOf(System.currentTimeMillis() / 1000))
.nonceStr(map.get("nonce_str").toString())
.packageValue("prepay_id=" + map.get("prepay_id"))
.signType(signType)
.build();
payResult.setPaySign(SignUtils.createSign(payResult, signType, key, null));
return (T) payResult;
}
/**
* 验证签名是否正确
*
* @return boolean
* @throws Exception
*/
public boolean checkSign(SortedMap<String, Object> parameters, String key) throws Exception {
String signWx = parameters.get("sign").toString();
if (signWx == null) return false;
parameters.remove("sign"); // 需要去掉原 map 中包含的 sign 字段再进行签名
String signMe = createSign(parameters, key);
return signWx.equals(signMe);
}
/**
* 读取 request body 内容作为字符串
*
* @param request
* @return
* @throws IOException
*/
public String readRequest(HttpServletRequest request) throws IOException {
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String str;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((str = in.readLine()) != null) {
sb.append(str);
}
in.close();
inputStream.close();
return sb.toString();
}
/**
* 读取 response body 内容为字符串
*/
public String readResponse(HttpResponse response) throws IOException {
BufferedReader in = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
String result = new String();
String line;
while ((line = in.readLine()) != null) {
result += line;
}
return result;
}
/**
* Map 转化为 XML
*
* @param map
* @return
*/
public String transferMapToXml(SortedMap<String, Object> map) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (String key : map.keySet()) {
sb.append("<").append(key).append(">")
.append(map.get(key))
.append("</").append(key).append(">");
}
return sb.append("</xml>").toString();
}
/**
* XML 转化为 map
*
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public Map transferXmlToMap(String strxml) throws IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = null;
try {
doc = builder.build(in);
} catch (JDOMException e) {
throw new IOException(e.getMessage()); // 统一转化为 IO 异常输出
}
// 解析 DOM
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if (children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
return m;
}
// 辅助 transferXmlToMap 方法递归提取子节点数据
private String getChildrenText(List<Element> children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator<Element> it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List<Element> list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
/**
* 生成 32 位随机字符串包含数字字母大小写
*
* @return
*/
public String gen32RandomString() {
char[] dict = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < 32; i++) {
sb.append(String.valueOf(dict[(int) (Math.random() * 36)]));
}
return sb.toString();
}
/**
* MD5 签名
*
* @param str
* @return 签名后的字符串信息
*/
public String encodeMD5(String str) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] inputByteArray = (str).getBytes();
messageDigest.update(inputByteArray);
byte[] resultByteArray = messageDigest.digest();
return byteArrayToHex(resultByteArray);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
// 辅助 encodeMD5 方法实现
private String byteArrayToHex(byte[] byteArray) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] resultCharArray = new char[byteArray.length * 2];
int index = 0;
for (byte b : byteArray) {
resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
resultCharArray[index++] = hexDigits[b & 0xf];
}
// 字符数组组合成字符串返回
return new String(resultCharArray);
}
}

View File

@ -66,6 +66,11 @@ public class StoreSign extends BaseEntity implements Serializable {
*/
private String runOffReason;
/**
* 签约月份
*/
private String signMonth;
/**
* 逻辑删除0否1是
*/

View File

@ -51,6 +51,7 @@
<cos_api.version>5.6.155</cos_api.version>
<spring-test.version>5.3.31</spring-test.version>
<hutool.version>5.8.23</hutool.version>
<jdom2.version>2.0.6.1</jdom2.version>
</properties>
<dependencyManagement>
@ -226,6 +227,12 @@
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jdom/jdom2 -->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>${jdom2.version}</version>
</dependency>
</dependencies>
</dependencyManagement>