去除审核流程
parent
778690d8b6
commit
f93f1cc765
|
|
@ -21,7 +21,7 @@ import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.google.common.base.Strings;
|
// import com.google.common.base.Strings;
|
||||||
import com.njzscloud.common.core.ex.Exceptions;
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
import com.njzscloud.common.mp.support.PageParam;
|
import com.njzscloud.common.mp.support.PageParam;
|
||||||
import com.njzscloud.common.mp.support.PageResult;
|
import com.njzscloud.common.mp.support.PageResult;
|
||||||
|
|
@ -65,19 +65,21 @@ import com.njzscloud.supervisory.station.service.StationManageService;
|
||||||
import com.njzscloud.supervisory.sys.auth.pojo.result.MyResult;
|
import com.njzscloud.supervisory.sys.auth.pojo.result.MyResult;
|
||||||
import com.njzscloud.supervisory.sys.dict.pojo.DictItemEntity;
|
import com.njzscloud.supervisory.sys.dict.pojo.DictItemEntity;
|
||||||
import com.njzscloud.supervisory.sys.dict.service.DictItemService;
|
import com.njzscloud.supervisory.sys.dict.service.DictItemService;
|
||||||
import com.njzscloud.supervisory.sys.role.pojo.entity.RoleEntity;
|
// import com.njzscloud.supervisory.sys.role.pojo.entity.RoleEntity;
|
||||||
import com.njzscloud.supervisory.sys.role.service.RoleService;
|
// import com.njzscloud.supervisory.sys.role.service.RoleService;
|
||||||
import com.njzscloud.supervisory.sys.stationletter.constant.WarnCategory;
|
import com.njzscloud.supervisory.sys.stationletter.constant.WarnCategory;
|
||||||
import com.njzscloud.supervisory.sys.user.pojo.entity.UserEntity;
|
// import com.njzscloud.supervisory.sys.user.pojo.entity.UserEntity;
|
||||||
import com.njzscloud.supervisory.sys.user.pojo.result.SysUserRoleEntity;
|
// import com.njzscloud.supervisory.sys.user.pojo.result.SysUserRoleEntity;
|
||||||
import com.njzscloud.supervisory.sys.user.service.UserRoleService;
|
// import com.njzscloud.supervisory.sys.user.service.UserRoleService;
|
||||||
import com.njzscloud.supervisory.sys.user.service.UserService;
|
// import com.njzscloud.supervisory.sys.user.service.UserService;
|
||||||
import com.njzscloud.supervisory.voicebox.service.CloudVoiceboxService;
|
import com.njzscloud.supervisory.voicebox.service.CloudVoiceboxService;
|
||||||
import com.njzscloud.supervisory.wxPay.contant.TempType;
|
// import com.njzscloud.supervisory.wxPay.contant.TempType;
|
||||||
|
// import com.njzscloud.supervisory.wxPay.dto.RefundRequestDto;
|
||||||
|
// import com.njzscloud.supervisory.wxPay.param.TemplateMessageParam;
|
||||||
import com.njzscloud.supervisory.wxPay.dto.RefundRequestDto;
|
import com.njzscloud.supervisory.wxPay.dto.RefundRequestDto;
|
||||||
import com.njzscloud.supervisory.wxPay.param.TemplateMessageParam;
|
|
||||||
import com.njzscloud.supervisory.wxPay.service.PaymentService;
|
import com.njzscloud.supervisory.wxPay.service.PaymentService;
|
||||||
import com.njzscloud.supervisory.wxPay.service.WechatTemplateMessageService;
|
// import com.njzscloud.supervisory.wxPay.service.WechatTemplateMessageService;
|
||||||
|
// import com.njzscloud.supervisory.wxPay.service.WechatTemplateMessageService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
@ -114,13 +116,13 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
private final MoneyAccountService moneyAccountService;
|
private final MoneyAccountService moneyAccountService;
|
||||||
private final MoneyChangeDetailService moneyChangeDetailService;
|
private final MoneyChangeDetailService moneyChangeDetailService;
|
||||||
private final PaymentService paymentService;
|
private final PaymentService paymentService;
|
||||||
private final WechatTemplateMessageService wechatTemplateMessageService;
|
// private final WechatTemplateMessageService wechatTemplateMessageService;
|
||||||
private final BizDriverService bizDriverService;
|
// private final BizDriverService bizDriverService;
|
||||||
private final BizCompanyService bizCompanyService;
|
private final BizCompanyService bizCompanyService;
|
||||||
private final RoleService roleService;
|
// private final RoleService roleService;
|
||||||
private final UserRoleService userRoleService;
|
// private final UserRoleService userRoleService;
|
||||||
private final UserService userService;
|
// private final UserService userService;
|
||||||
private final BizTruckService bizTruckService;
|
// private final BizTruckService bizTruckService;
|
||||||
private final DiscountManageService discountManageService;
|
private final DiscountManageService discountManageService;
|
||||||
private final HsoaService hsoaService;
|
private final HsoaService hsoaService;
|
||||||
private final DictItemService dictItemService;
|
private final DictItemService dictItemService;
|
||||||
|
|
@ -136,7 +138,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
Long stationId = addOrderInfoParam.getStationId();
|
Long stationId = addOrderInfoParam.getStationId();
|
||||||
BizCompanyEntity stationInfo = bizCompanyService.getById(stationId);
|
BizCompanyEntity stationInfo = bizCompanyService.getById(stationId);
|
||||||
Assert.notNull(stationInfo, () -> Exceptions.clierr("站点信息错误"));
|
Assert.notNull(stationInfo, () -> Exceptions.clierr("站点信息错误"));
|
||||||
Assert.isTrue(Integer.valueOf(1).equals(stationInfo.getStationType()), () -> Exceptions.clierr("系统暂未对接,预约失败"));
|
// Assert.isTrue(Integer.valueOf(1).equals(stationInfo.getStationType()), () -> Exceptions.clierr("系统暂未对接,预约失败"));
|
||||||
Assert.isFalse(this.exists(Wrappers.<OrderInfoEntity>lambdaQuery().eq(OrderInfoEntity::getSn, addOrderInfoParam.getSn())), () -> Exceptions.exception("订单创建失败,订单号重复"));
|
Assert.isFalse(this.exists(Wrappers.<OrderInfoEntity>lambdaQuery().eq(OrderInfoEntity::getSn, addOrderInfoParam.getSn())), () -> Exceptions.exception("订单创建失败,订单号重复"));
|
||||||
AddOrderCargoPlaceParam cargoPlace = addOrderInfoParam.getCargoPlace();
|
AddOrderCargoPlaceParam cargoPlace = addOrderInfoParam.getCargoPlace();
|
||||||
long cargoPlaceId = orderCargoPlaceService.add(cargoPlace);
|
long cargoPlaceId = orderCargoPlaceService.add(cargoPlace);
|
||||||
|
|
@ -182,7 +184,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
BizCompanyEntity transCompanyEntity = baseMapper.getTransInfo(transCompanyId);
|
BizCompanyEntity transCompanyEntity = baseMapper.getTransInfo(transCompanyId);
|
||||||
cloudVoiceboxService.play(transCompanyEntity.getUserId(), "您有新的待分配订单,请及时处理");
|
cloudVoiceboxService.play(transCompanyEntity.getUserId(), "您有新的待分配订单,请及时处理");
|
||||||
// 通知清运公司
|
// 通知清运公司
|
||||||
try {
|
/* try {
|
||||||
TemplateMessageParam param = new TemplateMessageParam();
|
TemplateMessageParam param = new TemplateMessageParam();
|
||||||
param.setUserId(transCompanyEntity.getUserId());
|
param.setUserId(transCompanyEntity.getUserId());
|
||||||
param.setTempType(TempType.TRANS_COMPANY.getVal());
|
param.setTempType(TempType.TRANS_COMPANY.getVal());
|
||||||
|
|
@ -192,7 +194,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
wechatTemplateMessageService.sendTemplateMessage(param);
|
wechatTemplateMessageService.sendTemplateMessage(param);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("通知失败", e);
|
log.error("通知失败", e);
|
||||||
}
|
} */
|
||||||
} else {
|
} else {
|
||||||
cloudVoiceboxService.play(1L, "您有新的待分配订单,请及时处理");
|
cloudVoiceboxService.play(1L, "您有新的待分配订单,请及时处理");
|
||||||
}
|
}
|
||||||
|
|
@ -715,7 +717,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
OrderCategory orderCategory = detail.getOrderCategory();
|
OrderCategory orderCategory = detail.getOrderCategory();
|
||||||
if (orderCategory == OrderCategory.DuanBoRu) return;
|
if (orderCategory == OrderCategory.DuanBoRu) return;
|
||||||
// auditStatus为市待审核状态通知市,通过状态通知司机
|
// auditStatus为市待审核状态通知市,通过状态通知司机
|
||||||
try {
|
/* try {
|
||||||
TemplateMessageParam param = new TemplateMessageParam();
|
TemplateMessageParam param = new TemplateMessageParam();
|
||||||
param.setUserId(detail.getDriverUserId());
|
param.setUserId(detail.getDriverUserId());
|
||||||
param.setSn(detail.getSn());
|
param.setSn(detail.getSn());
|
||||||
|
|
@ -752,7 +754,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("通知失败", e);
|
log.error("通知失败", e);
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
|
@ -888,7 +890,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
.setOrderStatus(OrderStatus.DaiJieDan)
|
.setOrderStatus(OrderStatus.DaiJieDan)
|
||||||
);
|
);
|
||||||
// 通知司机
|
// 通知司机
|
||||||
try {
|
/* try {
|
||||||
BizDriverEntity driverEntity = bizDriverService.getById(driverId);
|
BizDriverEntity driverEntity = bizDriverService.getById(driverId);
|
||||||
TemplateMessageParam param = new TemplateMessageParam();
|
TemplateMessageParam param = new TemplateMessageParam();
|
||||||
param.setUserId(driverEntity.getUserId());
|
param.setUserId(driverEntity.getUserId());
|
||||||
|
|
@ -902,7 +904,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
wechatTemplateMessageService.sendTemplateMessage(param);
|
wechatTemplateMessageService.sendTemplateMessage(param);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("通知失败", e);
|
log.error("通知失败", e);
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean assignmentTrafficCompany(AssignmentOrderParam assignmentOrderParam,
|
private boolean assignmentTrafficCompany(AssignmentOrderParam assignmentOrderParam,
|
||||||
|
|
@ -922,7 +924,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
// OrderInfoEntity orderInfoEntity = this.getById(orderInfoId);
|
// OrderInfoEntity orderInfoEntity = this.getById(orderInfoId);
|
||||||
// settleForTransCompany(orderInfoEntity, 0);
|
// settleForTransCompany(orderInfoEntity, 0);
|
||||||
// 通知清运公司
|
// 通知清运公司
|
||||||
try {
|
/* try {
|
||||||
TemplateMessageParam param = new TemplateMessageParam();
|
TemplateMessageParam param = new TemplateMessageParam();
|
||||||
BizCompanyEntity transCompanyEntity = baseMapper.getTransInfo(transCompanyId);
|
BizCompanyEntity transCompanyEntity = baseMapper.getTransInfo(transCompanyId);
|
||||||
param.setUserId(transCompanyEntity.getUserId());
|
param.setUserId(transCompanyEntity.getUserId());
|
||||||
|
|
@ -935,7 +937,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
wechatTemplateMessageService.sendTemplateMessage(param);
|
wechatTemplateMessageService.sendTemplateMessage(param);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("通知失败", e);
|
log.error("通知失败", e);
|
||||||
}
|
} */
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -948,71 +950,59 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
long userId = SecurityUtil.currentUserId();
|
long userId = SecurityUtil.currentUserId();
|
||||||
Long driverId_ = baseMapper.getDriverId(userId);
|
Long driverId_ = baseMapper.getDriverId(userId);
|
||||||
Assert.isTrue(driverId_ != null && driverId == driverId_, () -> Exceptions.clierr("您没有权限确认此单"));
|
Assert.isTrue(driverId_ != null && driverId == driverId_, () -> Exceptions.clierr("您没有权限确认此单"));
|
||||||
// baseMapper.busyTruck(truckId, Boolean.TRUE);
|
|
||||||
OrderCargoPlaceEntity cargoPlaceEntity = orderCargoPlaceService.getById(orderInfo.getCargoPlaceId());
|
|
||||||
String area = cargoPlaceEntity.getArea();
|
|
||||||
BizAuditConfigEntity bizAuditConfigEntity = bizAuditConfigService.getOne(Wrappers.lambdaQuery(BizAuditConfigEntity.class).eq(BizAuditConfigEntity::getArea, area));
|
|
||||||
|
|
||||||
AuditStatus auditStatus = AuditStatus.DaiShenHe;
|
Boolean driverHasInProgress = baseMapper.hasInProgressOrder(driverId,
|
||||||
if (bizAuditConfigEntity != null) {
|
Arrays.asList(OrderStatus.QingYunZhong.getVal(), OrderStatus.YiJinChang.getVal(), OrderStatus.YiChuChang.getVal()));
|
||||||
String areaRole = bizAuditConfigEntity.getAreaRole();
|
Assert.isFalse(driverHasInProgress, () -> Exceptions.clierr("您有正在进行中的订单,不能接单"));
|
||||||
String cityRole = bizAuditConfigEntity.getCityRole();
|
|
||||||
if (areaRole != null && cityRole != null) {
|
baseMapper.busyDriver(driverId, Boolean.TRUE);
|
||||||
auditStatus = AuditStatus.QuDaiShenHe;
|
baseMapper.busyTruck(truckId, Boolean.TRUE);
|
||||||
} else if (areaRole == null && cityRole != null) {
|
|
||||||
auditStatus = AuditStatus.ShiDaiShenHe;
|
String certificateSn = SnUtil.next(CERTIFICATE_SN_CODE);
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updateById(new OrderInfoEntity()
|
this.updateById(new OrderInfoEntity()
|
||||||
.setId(orderInfo.getId())
|
.setId(orderInfo.getId())
|
||||||
.setDriverConfirmTime(LocalDateTime.now())
|
.setDriverConfirmTime(LocalDateTime.now())
|
||||||
.setAuditStatus(auditStatus)
|
.setAuditStatus(AuditStatus.TongGuo)
|
||||||
.setTruckId(truckId)
|
.setTruckId(truckId)
|
||||||
.setOrderStatus(OrderStatus.YiJieDan)
|
.setOrderStatus(OrderStatus.QingYunZhong)
|
||||||
|
.setCertificateSn(certificateSn)
|
||||||
|
.setCheckStatus(CheckStatus.WeiKanLiao)
|
||||||
|
.setTransTime(LocalDateTime.now())
|
||||||
);
|
);
|
||||||
try {
|
|
||||||
// 通知审核人
|
Assert.notNull(truckId, () -> Exceptions.clierr("订单未分配车辆"));
|
||||||
RoleEntity roleEntity = new RoleEntity();
|
BizTruckEntity truckInfo = baseMapper.getTruckInfo(truckId);
|
||||||
if (null != bizAuditConfigEntity && !Strings.isNullOrEmpty(bizAuditConfigEntity.getAreaRole())
|
Assert.notNull(truckInfo, () -> Exceptions.clierr("车辆不存在"));
|
||||||
&& AuditStatus.QuDaiShenHe.equals(auditStatus)) {
|
|
||||||
roleEntity = roleService.getOne(Wrappers.lambdaQuery(RoleEntity.class).eq(
|
String gpsId = truckInfo.getGps();
|
||||||
RoleEntity::getRoleCode, bizAuditConfigEntity.getAreaRole()));
|
String licensePlate = truckInfo.getLicensePlate();
|
||||||
} else if (null != bizAuditConfigEntity && !Strings.isNullOrEmpty(bizAuditConfigEntity.getCityRole())
|
if (checkGps) {
|
||||||
&& AuditStatus.ShiDaiShenHe.equals(auditStatus)) {
|
Assert.notEmpty(gpsId, () -> Exceptions.clierr("车辆未绑定GPS"));
|
||||||
roleEntity = roleService.getOne(Wrappers.lambdaQuery(RoleEntity.class).eq(
|
DeviceLocalizerEntity deviceLocalizerEntity = baseMapper.gpsLastOnlineTime(gpsId);
|
||||||
RoleEntity::getRoleCode, bizAuditConfigEntity.getCityRole()));
|
LocalDateTime lastTime = deviceLocalizerEntity.getLastTime();
|
||||||
|
|
||||||
|
boolean after = lastTime == null || lastTime.isBefore(LocalDateTime.now().minusMinutes(5));
|
||||||
|
if (after) {
|
||||||
|
bizWarnService.save(new BizWarnEntity()
|
||||||
|
.setWarnCategory(WarnCategory.EQUIPMENT.getVal())
|
||||||
|
.setWarnContent(StrUtil.format("{} 绑定的 GPS 设备已离线,设备号:{}", licensePlate, gpsId))
|
||||||
|
.setOrderId(orderInfo.getId())
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (null != roleEntity && null != roleEntity.getId()) {
|
|
||||||
List<SysUserRoleEntity> userIds = userRoleService.list(Wrappers.lambdaQuery(SysUserRoleEntity.class)
|
|
||||||
.eq(SysUserRoleEntity::getRoleId, roleEntity.getId()));
|
|
||||||
UserEntity orderUserEntity = userService.getById(orderInfo.getUserId());
|
|
||||||
OrderGoodsEntity entity = orderGoodsService.getById(orderInfo.getGoodsId());
|
|
||||||
BizCompanyEntity companyEntity = bizCompanyService.getById(orderInfo.getTransCompanyId());
|
|
||||||
Set<String> openId = new HashSet<>();
|
|
||||||
TemplateMessageParam param;
|
|
||||||
for (SysUserRoleEntity userRoleEntity : userIds) {
|
|
||||||
UserEntity userEntity = userService.getById(userRoleEntity.getUserId());
|
|
||||||
if (null != userEntity && !Strings.isNullOrEmpty(userEntity.getOpenid())) {
|
|
||||||
if (openId.add(userEntity.getOpenid())) {
|
|
||||||
param = new TemplateMessageParam();
|
|
||||||
param.setUserId(userRoleEntity.getUserId());
|
|
||||||
param.setTempType(TempType.AUDIT_PENDING.getVal());
|
|
||||||
param.setSn(orderInfo.getSn());
|
|
||||||
param.setCfCompanyName(orderUserEntity.getNickname());
|
|
||||||
param.setGoodsName(entity.getGoodsName());
|
|
||||||
param.setCompanyName(companyEntity.getCompanyName());
|
|
||||||
param.setCreateTime(orderInfo.getCreateTime());
|
|
||||||
log.info("发送审核通知模板消息,参数:{}", param);
|
|
||||||
wechatTemplateMessageService.sendTemplateMessage(param);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.info("未查到用户信息:{}", userRoleEntity.getUserId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("通知失败", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTrack(gpsId, licensePlate, orderInfo.getId(), truckId);
|
||||||
|
|
||||||
|
CompletableFuture.runAsync(() -> Websocket.publish(new WsMsg().setEvent("down/order/status_change")
|
||||||
|
.setData(MapUtil.builder()
|
||||||
|
.put("sn", orderInfo.getSn())
|
||||||
|
.put("licensePlate", licensePlate)
|
||||||
|
.put("orderStatus", OrderStatus.QingYunZhong)
|
||||||
|
.build()))).exceptionally(e -> {
|
||||||
|
log.error("订单状态改变事件发布失败,订单{},状态{}", orderInfo.getSn(), OrderStatus.QingYunZhong, e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
|
@ -2386,7 +2376,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
placeEntity.setArea(area);
|
placeEntity.setArea(area);
|
||||||
placeEntity.setAreaName(areaName);
|
placeEntity.setAreaName(areaName);
|
||||||
orderCargoPlaceService.updateById(placeEntity);
|
orderCargoPlaceService.updateById(placeEntity);
|
||||||
BizAuditConfigEntity bizAuditConfigEntity = bizAuditConfigService.getOne(Wrappers.lambdaQuery(BizAuditConfigEntity.class)
|
/* BizAuditConfigEntity bizAuditConfigEntity = bizAuditConfigService.getOne(Wrappers.lambdaQuery(BizAuditConfigEntity.class)
|
||||||
.eq(BizAuditConfigEntity::getArea, area));
|
.eq(BizAuditConfigEntity::getArea, area));
|
||||||
try {
|
try {
|
||||||
// 通知审核人
|
// 通知审核人
|
||||||
|
|
@ -2424,7 +2414,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("通知失败", e);
|
log.error("通知失败", e);
|
||||||
}
|
} */
|
||||||
} else {
|
} else {
|
||||||
throw Exceptions.clierr("未查询到装货地址");
|
throw Exceptions.clierr("未查询到装货地址");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue