commit
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled

This commit is contained in:
2026-06-05 16:18:40 +08:00
parent 1ca34c6bb2
commit 3cba3bb74e
4393 changed files with 450030 additions and 103 deletions

View File

@@ -0,0 +1,18 @@
package jnpf.certificate.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Set;
@Data
@Configuration
@ConfigurationProperties(prefix = "config.food-safety-ocr")
public class FoodSafetyOcrConfig {
private Set<String> titleNames = Set.of("标题");
private Set<String> titleValues = Set.of("食品经营许可证");
private Set<String> issueDateNames = Set.of("日期");
private Set<String> expireDateNames = Set.of("有效期至");
private Set<String> businessItemsNames = Set.of("经营项目");
}

View File

@@ -0,0 +1,338 @@
package jnpf.certificate.consumer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.seata.spring.annotation.GlobalTransactional;
import jnpf.base.ActionResult;
import jnpf.certificate.service.CertificateManageApiService;
import jnpf.config.ConfigValueUtil;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.message.enums.permission.v2.OperationTypeMessageEnums;
import jnpf.message.enums.permission.v2.OrganizeCategoryMessageEnums;
import jnpf.message.model.permission.v2.OrganizeUpdateMessageDTO;
import jnpf.model.certificate.vo.CertificateOrganizeBusinessLicenseVO;
import jnpf.permission.V2OrganizeApi;
import jnpf.permission.eum.v2.OrganizeCategoryEnums;
import jnpf.permission.vo.v2.organzie.OrganizeGeneralDetailVO;
import jnpf.util.DateUtil;
import jnpf.util.ServiceException;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.acks.AcknowledgmentCallback;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 证照关联处理组织架构消费者。
*/
@Slf4j
@Component
@EnableBinding(CertificateConsumerSource.class)
public class CertificateConsumer {
private static final String IDEMPOTENT_KEY_PREFIX = "certificate:consume";
private static final String IDEMPOTENT_VALUE = "1";
private static final long IDEMPOTENT_EXPIRE_DAYS = 1L;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
@Lazy
private ConfigValueUtil configValueUtil;
@Resource
private CertificateManageApiService certificateManageApiService;
/**
* 监听组织架构变更消息并同步证照数据。
*/
@StreamListener(target = CertificateConsumerSource.INPUT, condition = "headers['ROCKET_TAGS'] == 'TAG_ORGANIZE'")
public void receiveStoreFranchiseeChange(Message<String> message) {
AcknowledgmentCallback acknowledgmentCallback = null;
String idempotentKey = null;
try {
MessageHeaders headers = message.getHeaders();
acknowledgmentCallback = headers.get(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, AcknowledgmentCallback.class);
if (acknowledgmentCallback != null) {
acknowledgmentCallback.noAutoAck();
}
String payload = message.getPayload();
if (StrUtil.isBlank(payload)) {
acknowledgeAccept(acknowledgmentCallback);
return;
}
String rocketKeys = headers.get("ROCKET_KEYS", String.class);
if (StrUtil.isBlank(rocketKeys)) {
log.warn("证照组织变更消息缺少ROCKET_KEYS跳过处理。payload={}", payload);
acknowledgeAccept(acknowledgmentCallback);
return;
}
idempotentKey = IDEMPOTENT_KEY_PREFIX + ":" + rocketKeys;
Boolean firstConsume = stringRedisTemplate.opsForValue()
.setIfAbsent(idempotentKey, IDEMPOTENT_VALUE, IDEMPOTENT_EXPIRE_DAYS, TimeUnit.DAYS);
if (!Boolean.TRUE.equals(firstConsume)) {
acknowledgeAccept(acknowledgmentCallback);
return;
}
List<OrganizeUpdateMessageDTO> messageList;
try {
messageList = JSONUtil.toList(JSONUtil.parseArray(payload), OrganizeUpdateMessageDTO.class);
} catch (Exception e) {
log.error("证照组织变更消息解析失败payload={}", payload, e);
throw e;
}
if (CollUtil.isEmpty(messageList)) {
acknowledgeAccept(acknowledgmentCallback);
return;
}
for (OrganizeUpdateMessageDTO item : messageList) {
processOrganizeChange(item);
}
acknowledgeAccept(acknowledgmentCallback);
} catch (Exception e) {
//todo 异常处理
String msg = e.getMessage();
if(msg.equals("组织不存在,无法同步营业执照") || msg.equals("切换租户失败")){
acknowledgeRequeue(acknowledgmentCallback);
}
if (StrUtil.isNotBlank(idempotentKey)) {
stringRedisTemplate.delete(idempotentKey);
}
log.error("处理证照组织变更消息失败message={}", JSONUtil.toJsonStr(message), e);
throw e;
} finally {
// TenantDataSourceUtil.clearSwitchDataSource();
}
}
/**
* 分发处理组织变更。
*/
private void processOrganizeChange(OrganizeUpdateMessageDTO item) {
if (item == null) {
return;
}
OperationTypeMessageEnums operationType = item.getOperationTypeEnum();
OrganizeCategoryMessageEnums category = item.getOrganizeCategoryEnum();
if (operationType == null || category == null) {
return;
}
if (!OrganizeCategoryMessageEnums.STORE.equals(category)
&& !OrganizeCategoryMessageEnums.COMPANY.equals(category)) {
return;
}
String tenantId = StrUtil.trim(item.getTenantId());
if (StrUtil.isBlank(tenantId)) {
return;
}
switchTenant(tenantId);
if (OperationTypeMessageEnums.ADD.equals(operationType)) {
handleAdd(item);
return;
}
if (OperationTypeMessageEnums.UPDATE.equals(operationType)) {
handleUpdate(item);
return;
}
if (OperationTypeMessageEnums.DELETE.equals(operationType)) {
handleDelete(item);
}
}
/**
* 处理新增。
* 门店:新增营业执照+食品经营许可证两条缺失证照。
* 公司:同步新增营业执照。
*/
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
protected void handleAdd(OrganizeUpdateMessageDTO item) {
if (OrganizeCategoryMessageEnums.STORE.equals(item.getOrganizeCategoryEnum())) {
handleStoreAdd(item.getId());
return;
}
if (OrganizeCategoryMessageEnums.COMPANY.equals(item.getOrganizeCategoryEnum())) {
CertificateOrganizeBusinessLicenseVO req = buildCompanyLicense(item.getId(), item.getJsonEntity());
certificateManageApiService.saveBusinessLicense(req);
}
}
/**
* 处理编辑。
* 门店:无需处理。
* 公司:同步更新营业执照。
*/
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
protected void handleUpdate(OrganizeUpdateMessageDTO item) {
if (OrganizeCategoryMessageEnums.STORE.equals(item.getOrganizeCategoryEnum())) {
return;
}
if (OrganizeCategoryMessageEnums.COMPANY.equals(item.getOrganizeCategoryEnum())) {
CertificateOrganizeBusinessLicenseVO req = buildCompanyLicense(item.getId(), item.getJsonEntity());
certificateManageApiService.saveBusinessLicense(req);
}
}
/**
* 处理删除。
* 按组织ID删除该组织主体下全部证照。
*/
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
protected void handleDelete(OrganizeUpdateMessageDTO item) {
String organizeId = StrUtil.trim(item.getId());
if (StrUtil.isBlank(organizeId)) {
return;
}
certificateManageApiService.deleteBusinessLicense(organizeId, null);
}
/**
* 门店新增时补齐默认缺失证照。
*/
private void handleStoreAdd(String storeId) {
String targetStoreId = StrUtil.trim(storeId);
if (StrUtil.isBlank(targetStoreId)) {
return;
}
certificateManageApiService.initStoreDefaultCertificates(targetStoreId);
}
/**
* 构建公司营业执照同步参数,逻辑与 V2OrganizeServiceImpl 的公司新增/编辑保持一致。
*/
private CertificateOrganizeBusinessLicenseVO buildCompanyLicense(String organizeId, String jsonEntity) {
String targetOrganizeId = StrUtil.trim(organizeId);
ServiceException.isTrue(StrUtil.isNotBlank(targetOrganizeId), "组织ID不能为空");
CertificateOrganizeBusinessLicenseVO licenseVO = new CertificateOrganizeBusinessLicenseVO();
licenseVO.setOrganizeId(targetOrganizeId);
JSONObject jsonObject = parseJSONEntity(jsonEntity);
licenseVO.setCertificateImage(jsonObject.getStr("licenseImg"));
licenseVO.setCompanyName(jsonObject.getStr("fullName"));
JSONObject propertyJson = parsePropertyJson(jsonObject);
//前端如果没值传的是""空串直接去获取JsonArry会报错这里判断一下
String businessTermText = propertyJson.getStr("businessTerm");
if(StringUtil.isNotBlank(businessTermText)){
licenseVO.setBusinessTerm(businessTermText);
JSONArray businessTerm = propertyJson.getJSONArray("businessTerm");
if(businessTerm.size() == 2){
licenseVO.setIssueDate(businessTerm.getStr(0));
licenseVO.setExpireDate(businessTerm.getStr(1));
}
}
String managerName = propertyJson.getStr("managerName");
if (managerName != null) {
licenseVO.setLegalRepresentative(managerName);
}
String address = propertyJson.getStr("address");
if (address!= null) {
licenseVO.setCompanyAddress(address);
}
Integer businessTermLong = propertyJson.getInt("businessTermLong");
if (businessTermLong!= null) {
licenseVO.setIsLongTerm(businessTermLong);
}
Date establishDate = propertyJson.getDate("foundedTime");
if(Objects.nonNull(establishDate)){
licenseVO.setEstablishDate(DateUtil.daFormat(establishDate));
}
return licenseVO;
}
private JSONObject parseJSONEntity(String jsonEntity) {
if (StrUtil.isBlank(jsonEntity) || !JSONUtil.isTypeJSONObject(jsonEntity)) {
return new JSONObject();
}
return JSONUtil.parseObj(jsonEntity);
}
/**
* 从组织消息 jsonEntity 中解析 propertyJson。
*/
private JSONObject parsePropertyJson(JSONObject jsonEntity) {
if (Objects.isNull(jsonEntity)) {
return new JSONObject();
}
JSONObject propertyObj = jsonEntity.getJSONObject("propertyJson");
if(Objects.isNull(propertyObj)){
return new JSONObject();
}
return propertyObj;
}
private Integer toInteger(Object value) {
if (value == null) {
return null;
}
try {
return Integer.parseInt(String.valueOf(value));
} catch (Exception e) {
return null;
}
}
/**
* 切换租户数据源。
*/
private void switchTenant(String tenantId) {
if (!configValueUtil.isMultiTenancy()) {
log.info("证照组织变更消息配置为非多租户跳过切换租户。tenantId={}", tenantId);
return;
}
try {
TenantDataSourceUtil.switchTenant(tenantId);
} catch (Exception e) {
throw new RuntimeException("切换租户失败tenantId:"+tenantId, e);
}
}
/**
* 手动确认消费成功。
*/
private void acknowledgeAccept(AcknowledgmentCallback acknowledgmentCallback) {
if (acknowledgmentCallback == null || acknowledgmentCallback.isAcknowledged()) {
return;
}
acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.ACCEPT);
}
/**
* 手动回执重试。
*/
private void acknowledgeRequeue(AcknowledgmentCallback acknowledgmentCallback) {
if (acknowledgmentCallback == null || acknowledgmentCallback.isAcknowledged()) {
return;
}
acknowledgmentCallback.acknowledge(AcknowledgmentCallback.Status.REQUEUE);
}
}

View File

@@ -0,0 +1,17 @@
package jnpf.certificate.consumer;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
/**
* 证照消息通道定义
*/
public interface CertificateConsumerSource {
/**
* 消费通道
*/
String INPUT = "permission-certificate-input";
@Input(INPUT)
SubscribableChannel input();
}

View File

@@ -0,0 +1,182 @@
package jnpf.certificate.controller;
import com.github.pagehelper.PageInfo;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.certificate.service.CertificateManageService;
import jnpf.certificate.service.CertificateInstanceService;
import jnpf.model.certificate.req.CertificateHealthManageQueryReq;
import jnpf.model.certificate.req.CertificateInstanceQueryReq;
import jnpf.model.certificate.req.CertificateStoreManageQueryReq;
import jnpf.model.certificate.req.CertificateStoreDashboardReq;
import jnpf.model.certificate.req.CertificateSyncHealthReq;
import jnpf.model.certificate.req.app.CertificateAppBusinessLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHealthCertificateUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHygieneLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppStoreCustomUpdateReq;
import jnpf.model.certificate.vo.CertificateHealthManageVO;
import jnpf.model.certificate.vo.CertificateInstanceVO;
import jnpf.model.certificate.vo.CertificateStoreManageVO;
import jnpf.model.certificate.vo.CertificateStoreDashboardVO;
import jnpf.model.certificate.vo.CertificateStoreCustomStatusTableVO;
import jnpf.model.certificate.vo.CertificateTypeOptionVO;
import jnpf.model.certificate.vo.app.HealthCertificateDetailVO;
import jnpf.util.FtbUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.List;
import java.util.Optional;
/**
* web证照实例控制器。
*/
@RestController
@Validated
@RequestMapping("/web/certificate-instance")
public class CertificateInstanceController {
@Autowired
private CertificateInstanceService certificateInstanceService;
@Autowired
private CertificateManageService certificateManageService;
/**
* 更新健康证。
*
* @param req 更新参数
* @return 操作结果
*/
@PutMapping("/update-health")
public ActionResult<Void> updateHealth(@Validated @RequestBody CertificateAppHealthCertificateUpdateReq req) {
certificateManageService.updateHealthCertificate(req);
return ActionResult.success();
}
/**
* 更新营业执照。
*
* @param req 更新参数
* @return 操作结果
*/
@PutMapping("/update-business-license")
public ActionResult<Void> updateBusinessLicense(@Validated @RequestBody CertificateAppBusinessLicenseUpdateReq req) {
certificateManageService.updateBusinessLicense(req);
return ActionResult.success();
}
/**
* 更新食品经营许可证。
*
* @param req 更新参数
* @return 操作结果
*/
@PutMapping("/update-hygiene-license")
public ActionResult<Void> updateHygieneLicense(@Validated @RequestBody CertificateAppHygieneLicenseUpdateReq req) {
certificateManageService.updateHygieneLicense(req);
return ActionResult.success();
}
/**
* 更新门店自定义证照。
*
* @param req 更新参数
* @return 操作结果
*/
@PutMapping("/update-store-custom")
public ActionResult<Void> updateStoreCustom(@Valid @RequestBody CertificateAppStoreCustomUpdateReq req) {
certificateManageService.updateStoreCustomCertificate(req);
return ActionResult.success();
}
/**
* 按ID查询详情。
*
* @param id 实例ID
* @return 详情
*/
@GetMapping("/query-info/{id}")
public ActionResult<CertificateInstanceVO> queryInfo(@PathVariable("id") @NotBlank(message = "实例ID不能为空") String id) {
return ActionResult.success(certificateInstanceService.queryInfo(id));
}
/**
* 分页查询列表。
*
* @param req 查询参数
* @return 分页结果
*/
@GetMapping("/query-page")
@Deprecated
public ActionResult<PageListVO<CertificateInstanceVO>> queryPage(@Valid CertificateInstanceQueryReq req) {
PageInfo<CertificateInstanceVO> pageInfo = certificateInstanceService.queryPage(req);
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
}
/**
* 健康证管理分页查询。
*
* @param req 查询参数
* @return 分页结果
*/
@PostMapping("/query-health-page")
public ActionResult<PageListVO<CertificateHealthManageVO>> queryHealthPage(@RequestBody @Valid CertificateHealthManageQueryReq req) {
PageInfo<CertificateHealthManageVO> pageInfo = certificateInstanceService.queryHealthPage(req);
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
}
/**
* 门店证照分页查询。
*
* @param req 查询参数
* @return 分页结果
*/
@PostMapping("/query-store-page")
public ActionResult<PageListVO<CertificateStoreManageVO>> queryStorePage(@RequestBody @Valid CertificateStoreManageQueryReq req) {
PageInfo<CertificateStoreManageVO> pageInfo = certificateInstanceService.queryStorePage(req);
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
}
/**
* 门店证照看板统计。
*
* @param req 查询参数
* @return 看板统计结果
*/
@PostMapping("/store-dashboard")
public ActionResult<CertificateStoreDashboardVO> storeDashboard(@Validated @RequestBody CertificateStoreDashboardReq req) {
return ActionResult.success(certificateInstanceService.storeCertificateDashboard(req));
}
/**
* 门店证照看板表格分页查询。
*
* @param req 查询参数
* @return 分页结果
*/
@PostMapping("/store-dashboard-table-page")
public ActionResult<PageListVO<CertificateStoreCustomStatusTableVO>> storeDashboardTablePage(@Validated @RequestBody CertificateStoreDashboardReq req) {
PageInfo<CertificateStoreCustomStatusTableVO> pageInfo = certificateInstanceService.storeCertificateDashboardTablePage(req);
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
}
/**
* 查询证照类型选项。
*
* @return 证照类型选项
*/
@GetMapping("/query-certificate-type-list")
public ActionResult<List<CertificateTypeOptionVO>> queryCertificateTypeList() {
return ActionResult.success(certificateInstanceService.queryCertificateTypeList());
}
}

View File

@@ -0,0 +1,59 @@
package jnpf.certificate.controller;
import jnpf.base.ActionResult;
import jnpf.certificate.CertificateManageApi;
import jnpf.certificate.service.CertificateManageApiService;
import jnpf.model.certificate.vo.CertificateOrganizeBusinessLicenseVO;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* 组织营业执照管理接口。
*/
@Validated
@RestController
@RequestMapping("/web/certificate-manage-api")
public class CertificateManageApiController implements CertificateManageApi {
@Autowired
private CertificateManageApiService certificateManageApiService;
@Override
@GetMapping("/query-business-license")
public ActionResult<CertificateOrganizeBusinessLicenseVO> queryBusinessLicense(@RequestParam("organizeId") String organizeId) {
return ActionResult.success(certificateManageApiService.queryBusinessLicense(organizeId));
}
@Override
@PostMapping("/query-business-license-batch")
public ActionResult<List<CertificateOrganizeBusinessLicenseVO>> queryBusinessLicenseBatch(@RequestBody Collection<String> organizeIds) {
return ActionResult.success(certificateManageApiService.queryBusinessLicenseBatch(organizeIds));
}
@Override
@PostMapping("/save-business-license")
public ActionResult<Void> saveBusinessLicense(@Valid @RequestBody CertificateOrganizeBusinessLicenseVO req) {
certificateManageApiService.saveBusinessLicense(req);
return ActionResult.success();
}
@Override
@DeleteMapping("/delete-business-license")
public ActionResult<Void> deleteBusinessLicense(@RequestParam("organizeId") String organizeId,
@RequestParam(value = "loginUserId", required = false) String loginUserId) {
certificateManageApiService.deleteBusinessLicense(organizeId, loginUserId);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,190 @@
package jnpf.certificate.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.ocr.v20181119.OcrClient;
import com.tencentcloudapi.ocr.v20181119.models.*;
import jnpf.base.ActionResult;
import jnpf.certificate.config.FoodSafetyOcrConfig;
import jnpf.model.certificate.req.CertificateFoodSafetyOcrReq;
import jnpf.model.certificate.vo.CertificateFoodSafetyOcrVO;
import jnpf.permission.vo.LicenseVo;
import jnpf.personnels.config.TengxunLicenseConfig;
import jnpf.util.JsonUtil;
import jnpf.util.RedisUtil;
import jnpf.util.StringUtil;
import jnpf.util.UserProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 证照 OCR 控制器。
*/
@RestController
@Validated
@RequestMapping("/web/certificate-ocr")
@Slf4j
public class CertificateOcrController {
private static final String FOOD_SAFETY_OCR_CACHE_KEY_PREFIX = "ftb:certificate:ocr:food:safety:";
private static final long FOOD_SAFETY_OCR_CACHE_SECONDS = 24 * 60 * 60L;
@Autowired
private TengxunLicenseConfig licenseConfig;
@Autowired
private FoodSafetyOcrConfig foodSafetyOcrConfig;
@Autowired
private RedisUtil redisUtil;
/**
* 食品安全许可证识别(空实现)。
*
* @param req 图片地址
* @return 识别结果
*/
@PostMapping("/recognize-food-safety-license")
public ActionResult<CertificateFoodSafetyOcrVO> recognizeFoodSafetyLicense(@Validated @RequestBody CertificateFoodSafetyOcrReq req) {
return ActionResult.success(recognizeFoodSafetyLicense(req.getImageUrl())
.orElse(new CertificateFoodSafetyOcrVO(false)));
}
private Optional<CertificateFoodSafetyOcrVO> recognizeFoodSafetyLicense(String imageUrl) {
String tenantId = UserProvider.getUser().getTenantId();
String cacheKey = buildFoodSafetyOcrCacheKey(tenantId,imageUrl);
try {
Object cached = redisUtil.getString(cacheKey);
if (cached != null && StringUtil.isNotBlank(cached.toString())) {
CertificateFoodSafetyOcrVO cacheVo = JsonUtil.getJsonToBean(cached.toString(), CertificateFoodSafetyOcrVO.class);
if (cacheVo != null) {
return Optional.of(cacheVo);
}
}
} catch (Exception e) {
log.warn("read food safety ocr cache error, imageUrl={}", imageUrl, e);
}
try{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential(licenseConfig.getSecretId(), licenseConfig.getSecretKey());
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint(licenseConfig.getDomain());
// 实例化一个client选项可选的没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
OcrClient client = new OcrClient(cred, "ap-guangzhou", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
SmartStructuralOCRRequest smartStructuralOCRRequest = new SmartStructuralOCRRequest();
smartStructuralOCRRequest.setImageUrl(imageUrl);
SmartStructuralOCRResponse resp = client.SmartStructuralOCR(smartStructuralOCRRequest);
if(Objects.isNull(resp)){
return Optional.empty();
}
StructuralItem [] structuralItems = resp.getStructuralItems();
if(structuralItems == null || structuralItems.length == 0){
return Optional.empty();
}
Set<String> titleNames = foodSafetyOcrConfig.getTitleNames();
Set<String> issueDateNames = foodSafetyOcrConfig.getIssueDateNames();
Set<String> expireDateNames = foodSafetyOcrConfig.getExpireDateNames();
Set<String> businessItemsNames = foodSafetyOcrConfig.getBusinessItemsNames();
String title = null;
String issueDate = null;
String expireDate = null;
String businessItems = null;
for (StructuralItem structuralItem : structuralItems){
String name = structuralItem.getName();
String value = structuralItem.getValue();
if(titleNames.contains(name)){
title = value;
}else if(issueDateNames.contains(name)){
issueDate = value;
} else if (expireDateNames.contains(name)) {
expireDate = value;
} else if (businessItemsNames.contains(name)) {
businessItems = value;
}
}
if(Objects.isNull(title) || Objects.isNull(issueDate) || Objects.isNull(expireDate) || Objects.isNull(businessItems)){
log.error("ocr fail title,issueDate,expireDate,businessItems is null.structuralItems:{}",JsonUtil.getObjectToString(structuralItems));
return Optional.empty();
}
CertificateFoodSafetyOcrVO result = new CertificateFoodSafetyOcrVO(issueDate, expireDate, businessItems,true);
try {
redisUtil.insert(cacheKey, JsonUtil.getObjectToString(result), FOOD_SAFETY_OCR_CACHE_SECONDS);
} catch (Exception e) {
log.warn("write food safety ocr cache error, imageUrl={}", imageUrl, e);
}
return Optional.of(result);
} catch (TencentCloudSDKException e) {
log.error("Tencent ocr error ",e);
}
return Optional.empty();
}
private String buildFoodSafetyOcrCacheKey(String tenantId,String imageUrl) {
return FOOD_SAFETY_OCR_CACHE_KEY_PREFIX +tenantId+":"+ DigestUtil.md5Hex(StrUtil.nullToEmpty(imageUrl));
}
public static void main(String[] args) {
String imgUrl = "https://img.cdn1.vip/i/69dda78d48f89_1776134029.webp";
try{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential("AKIDJTbdT7ayRuIAC848D7mKm2Ji5XHua7es", "HOI1iFakDZidu461qaEweHtNfjBY5Rfp");
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ocr.tencentcloudapi.com");
// 实例化一个client选项可选的没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
OcrClient client = new OcrClient(cred, "ap-guangzhou", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
EnterpriseLicenseOCRRequest req = new EnterpriseLicenseOCRRequest();
req.setImageUrl(imgUrl);
// 返回的resp是一个BizLicenseOCRResponse的实例与请求对象对应
EnterpriseLicenseOCRResponse resp = client.EnterpriseLicenseOCR(req);
SmartStructuralOCRV2Request smartStructuralOCRV2Request = new SmartStructuralOCRV2Request();
smartStructuralOCRV2Request.setImageUrl(imgUrl);
SmartStructuralOCRV2Response smartStructuralOCRV2Response = client.SmartStructuralOCRV2(smartStructuralOCRV2Request);
SmartStructuralOCRRequest smartStructuralOCRRequest = new SmartStructuralOCRRequest();
smartStructuralOCRRequest.setImageUrl(imgUrl);
SmartStructuralOCRResponse smartStructuralOCRResponse = client.SmartStructuralOCR(smartStructuralOCRRequest);
System.out.println(resp);
System.out.println(JsonUtil.getObjectToString(smartStructuralOCRResponse.getStructuralItems()));
// 输出json格式的字符串回包
} catch (TencentCloudSDKException e) {
log.error("Tencent ocr error ",e);
}
}
}

View File

@@ -0,0 +1,75 @@
package jnpf.certificate.controller;
import io.seata.spring.annotation.GlobalTransactional;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.base.ActionResult;
import jnpf.certificate.service.CertificateStoreService;
import jnpf.exception.HandleException;
import jnpf.model.certificate.req.CertificateStoreSaveReq;
import jnpf.model.certificate.vo.CertificateStoreAndCertificatesVO;
import jnpf.model.certificate.vo.CertificateStoreTabVO;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
* web门店证照控制器。
*/
@RestController
@Validated
@RequestMapping("/web/certificate-store")
public class CertificateStoreController {
/**
* 门店证照服务。
*/
@Autowired
private CertificateStoreService certificateStoreService;
/**
* 保存门店及证照数据。
* 核心参数包含门店信息、健康证、营业执照、食品经营许可证和门店自定义证照集合。
*
* @param req 保存参数
* @return 操作结果
*/
@PostMapping("/save-store-and-certificates")
@GlobalTransactional
public ActionResult<String> saveStoreAndCertificates(@Validated @RequestBody CertificateStoreSaveReq req) {
return ActionResult.success("success",certificateStoreService.saveStoreAndCertificates(req));
}
@Operation(summary = "[删除]删除门店和证照")
@DeleteMapping(value = "/{id}")
@GlobalTransactional
public ActionResult<Boolean> deleteStore(@PathVariable("id") String id) throws HandleException {
return ActionResult.success(certificateStoreService.deleteStore(id));
}
/**
* 根据门店ID查询门店证照详情。营业执照、食品许可证、门店自定义证照集合。
*
* @param storeId 门店ID
* @return 门店证照详情
*/
@GetMapping("/get-store-certificates")
public ActionResult<CertificateStoreAndCertificatesVO> getStoreCertificates(@RequestParam("storeId")
@NotBlank(message = "门店ID不能为空")
String storeId) {
return ActionResult.success(certificateStoreService.getStoreAndCertificates(storeId));
}
/**
* 查询门店证照Tab列表。
*
* @return 门店证照Tab列表
*/
@GetMapping("/query-store-certificate-tab-list")
public ActionResult<List<CertificateStoreTabVO>> queryStoreCertificateTabList() {
return ActionResult.success(certificateStoreService.queryStoreCertificateTabList());
}
}

View File

@@ -0,0 +1,932 @@
package jnpf.certificate.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jnpf.authority.utils.PermissionsUtils;
import jnpf.base.ActionResult;
import jnpf.certificate.CertificateWarningApi;
import jnpf.certificate.helper.NoticeHelper;
import jnpf.certificate.helper.OrganizationHelper;
import jnpf.certificate.mapper.CertificateInstanceMapper;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoEntity;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.model.warningnotice.vo.WarningNoticeTargetVO;
import jnpf.model.warningnotice.vo.WarningNoticeUserConfigVO;
import jnpf.model.warningnotice.vo.WarningNoticeVO;
import jnpf.permission.UserApi;
import jnpf.permission.V2UserApi;
import jnpf.permission.dto.v2.user.QueryUserBatchDTO;
import jnpf.permission.entity.UserEntity;
import jnpf.permission.vo.v2.organzie.OrganizeBaseInfoVO;
import jnpf.permission.vo.v2.user.UserBaseInfoVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.storecertificatephoto.mapper.StoreCertificatePhotoMapper;
import jnpf.storecertificatephoto.service.WarningNoticeService;
import jnpf.util.CustomTenantUtil;
import jnpf.util.NoDataSourceBind;
import jnpf.util.StringUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static jnpf.certificate.helper.NoticeHelper.*;
/**
* 证照预警接口实现。
*/
@Slf4j
@RestController
@RequestMapping("/web/certificate-warning-api")
public class CertificateWarningController implements CertificateWarningApi {
private static final int STATUS_MISSING = 1;
private static final int STATUS_EXPIRED = 2;
private static final int STATUS_NEAR_EXPIRE = 3;
private static final int STATUS_NORMAL = 4;
private static final int SUBJECT_TYPE_EMPLOYEE = 1;
private static final int DEFAULT_NEAR_EXPIRE_DAYS = 30;
private static final int DEFAULT_NOTICE_FREQUENCY_DAYS = 1;
private static final int STORE_CUSTOM_TEMPLATE_DISABLED_STATUS = 0;
private static final int USER_ADMINISTRATOR_FLAG = 1;
private static final String NOTICE_USER_TYPE_POSITION = "position";
private static final String NOTICE_USER_TYPE_PERSONNEL = "personnel";
private static final String NOTICE_USER_TYPE_PERSON = "person";
@Value("${config.certificate.module.id:813679492035805253}")
private String HEALTH_CERTIFICATE_PERMISSION_MODULE_ID;
@Autowired
private CertificateInstanceMapper certificateInstanceMapper;
@Autowired
private WarningNoticeService warningNoticeService;
@Autowired
private StoreCertificatePhotoMapper storeCertificatePhotoMapper;
@Autowired
private NoticeHelper noticeHelper;
@Autowired
private V2UserApi v2UserApi;
@Autowired
private UserApi userApi;
@Autowired
private PermissionsUtils permissionsUtils;
@Autowired
private CustomTenantUtil customTenantUtil;
@Autowired
private OrganizationHelper organizationHelper;
/**
* 检查证照状态并发送临期通知:
* 1. 将正常/临期状态按规则纠正为临期或过期。
* 2. 向临期对象发送预警消息(健康证通知本人,组织/门店证照通知组织负责人)。
*
* @param tenantId 租户ID
* @return 处理结果
*/
@Override
@NoDataSourceBind
@PostMapping("/checkAndSendCertificateWarning")
public ActionResult<Boolean> checkAndSendCertificateWarning(@RequestParam("tenantId") String tenantId) {
try {
log.error("checkAndSendCertificateWarning tenantId:{}",tenantId);
customTenantUtil.checkOutTenant(tenantId);
doCheckAndSend(StrUtil.trim(tenantId));
return ActionResult.success(Boolean.TRUE);
} catch (Exception e) {
log.error("检查并发送证照预警失败tenantId={}", tenantId, e);
return ActionResult.success(Boolean.FALSE);
}
}
/**
* 执行检查与发送。
*/
private void doCheckAndSend(String tenantId) {
List<CertificateInstanceEntity> candidateList = queryCandidateCertificateList();
log.error("doCheckAndSend candidateList size:{}", CollUtil.isEmpty(candidateList) ? 0 : candidateList.size());
if (CollUtil.isEmpty(candidateList)) {
return;
}
Map<String, WarningNoticeVO> warningConfigMap = queryWarningConfigMap();
Map<String, StoreCertificatePhotoEntity> templateMap = queryTemplateMap(candidateList);
Map<String, Integer> templateNearExpireDaysMap = buildTemplateNearExpireDaysMap(templateMap);
List<CertificateInstanceEntity> needUpdateList = new ArrayList<>();
List<NearExpireNoticeTask> noticeTaskList = new ArrayList<>();
for (CertificateInstanceEntity entity : candidateList) {
if (entity == null || StrUtil.isBlank(entity.getId()) || StrUtil.isBlank(entity.getCertificateType())) {
continue;
}
WarningNoticeVO warningNoticeVO = buildWarningNoticeVO(entity, warningConfigMap);
boolean skipByExpiryReminderDays = shouldSkipWarningNoticeByExpiryReminderDays(warningNoticeVO);
boolean skipWarningNotice = isStoreCustomTemplateWarningDisabled(entity, templateMap);
int nearExpireDays = resolveNearExpireDays(entity, warningConfigMap, templateNearExpireDaysMap);
int targetStatus = calculateStatus(entity, nearExpireDays,templateMap);
if ((targetStatus == STATUS_NEAR_EXPIRE || targetStatus == STATUS_EXPIRED || targetStatus == STATUS_NORMAL)
&& !Objects.equals(entity.getStatus(), targetStatus)) {
entity.setStatus(targetStatus);
needUpdateList.add(entity);
}
int effectiveStatus = (targetStatus == STATUS_NEAR_EXPIRE || targetStatus == STATUS_EXPIRED)
? targetStatus : (entity.getStatus() == null ? STATUS_MISSING : entity.getStatus());
if (effectiveStatus != STATUS_NEAR_EXPIRE || skipWarningNotice || skipByExpiryReminderDays) {
continue;
}
Integer daysToExpire = calculateDaysToExpire(entity.getExpireDate());
if (daysToExpire == null || daysToExpire < 0) {
continue;
}
int noticeFrequencyDays = resolveNoticeFrequencyDays(warningNoticeVO);
if (!shouldSendByFrequency(daysToExpire, noticeFrequencyDays)) {
continue;
}
String templateName = null;
if(isStoreCustomCertificate(entity)){
StoreCertificatePhotoEntity storeCertificatePhotoEntity = templateMap.get(entity.getTemplateId());
if(Objects.nonNull(storeCertificatePhotoEntity)){
templateName = storeCertificatePhotoEntity.getCertificateName();
}
}
noticeTaskList.add(new NearExpireNoticeTask(entity, daysToExpire, warningNoticeVO,templateName));
}
updateCertificateStatusBatch(needUpdateList);
sendNearExpireNoticeBatch(noticeTaskList, tenantId);
}
private WarningNoticeVO buildWarningNoticeVO(CertificateInstanceEntity entity, Map<String, WarningNoticeVO> warningConfigMap) {
String certificateType = entity.getCertificateType();
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType())) {
return warningConfigMap.get(entity.getTemplateId());
}
return warningConfigMap.get(certificateType);
}
/**
* 查询待检查的证照数据(仅正常、临期)。
*/
private List<CertificateInstanceEntity> queryCandidateCertificateList() {
LambdaQueryWrapper<CertificateInstanceEntity> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(CertificateInstanceEntity::getEnabledMark, 0);
queryWrapper.eq(CertificateInstanceEntity::getTemplateStatus,1);
queryWrapper.in(CertificateInstanceEntity::getStatus, Arrays.asList(STATUS_NORMAL, STATUS_NEAR_EXPIRE));
queryWrapper.in(CertificateInstanceEntity::getCertificateType, Arrays.asList(
CertificateTypeEnum.HEALTH_CERTIFICATE.getType(),
CertificateTypeEnum.BUSINESS_LICENSE.getType(),
CertificateTypeEnum.HYGIENE_LICENSE.getType(),
CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType()
));
queryWrapper.orderByAsc(CertificateInstanceEntity::getCertificateType);
queryWrapper.orderByAsc(CertificateInstanceEntity::getSubjectType);
queryWrapper.orderByAsc(CertificateInstanceEntity::getSubjectId);
return certificateInstanceMapper.selectList(queryWrapper);
}
/**
* 查询预警设置(健康证、营业执照、食品经营许可证、门店自定义证照)。
*/
private Map<String, WarningNoticeVO> queryWarningConfigMap() {
Map<String, WarningNoticeVO> result = new HashMap<>(4);
List<WarningNoticeVO> warningNoticeVOList = warningNoticeService.queryAll();
for (WarningNoticeVO warningNoticeVO:warningNoticeVOList){
result.put(warningNoticeVO.getTypeOrTemplateId(), warningNoticeVO);
}
return result;
}
/**
* 查询门店自定义证照模板临期天数配置。
*/
private Map<String, StoreCertificatePhotoEntity> queryTemplateMap(List<CertificateInstanceEntity> instanceList) {
List<String> templateIds = instanceList.stream()
.filter(Objects::nonNull)
.filter(entity -> StrUtil.equalsIgnoreCase(entity.getCertificateType(), CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType()))
.map(CertificateInstanceEntity::getTemplateId)
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.distinct()
.collect(Collectors.toList());
if (CollUtil.isEmpty(templateIds)) {
return Collections.emptyMap();
}
LambdaQueryWrapper<StoreCertificatePhotoEntity> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.in(StoreCertificatePhotoEntity::getId, templateIds);
queryWrapper.eq(StoreCertificatePhotoEntity::getEnabledMark, 0);
List<StoreCertificatePhotoEntity> templateList = storeCertificatePhotoMapper.selectList(queryWrapper);
if (CollUtil.isEmpty(templateList)) {
return Collections.emptyMap();
}
Map<String, StoreCertificatePhotoEntity> result = new HashMap<>(templateList.size());
for (StoreCertificatePhotoEntity template : templateList) {
if (template == null || StrUtil.isBlank(template.getId())) {
continue;
}
result.put(StrUtil.trim(template.getId()), template);
}
return result;
}
/**
* 构建门店自定义证照模板临期天数映射。
*/
private Map<String, Integer> buildTemplateNearExpireDaysMap(Map<String, StoreCertificatePhotoEntity> templateMap) {
if (CollUtil.isEmpty(templateMap)) {
return Collections.emptyMap();
}
Map<String, Integer> result = new HashMap<>(templateMap.size());
for (Map.Entry<String, StoreCertificatePhotoEntity> entry : templateMap.entrySet()) {
String templateId = StrUtil.trim(entry.getKey());
if (StrUtil.isBlank(templateId)) {
continue;
}
StoreCertificatePhotoEntity template = entry.getValue();
Integer reminderDays = template == null ? null : template.getExpiryReminderDays();
result.put(templateId, reminderDays == null || reminderDays < 0 ? DEFAULT_NEAR_EXPIRE_DAYS : reminderDays);
}
return result;
}
/**
* 按证照类型解析临期阈值天数。
*/
private int resolveNearExpireDays(CertificateInstanceEntity entity,
Map<String, WarningNoticeVO> warningConfigMap,
Map<String, Integer> templateNearExpireDaysMap) {
if (entity == null || StrUtil.isBlank(entity.getCertificateType())) {
return DEFAULT_NEAR_EXPIRE_DAYS;
}
String certificateType = StrUtil.trim(entity.getCertificateType());
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType())) {
String templateId = StrUtil.trim(entity.getTemplateId());
Integer templateDays = templateNearExpireDaysMap.get(templateId);
return templateDays == null || templateDays < 0 ? DEFAULT_NEAR_EXPIRE_DAYS : templateDays;
}
WarningNoticeVO warningNoticeVO = warningConfigMap.get(certificateType);
Integer reminderDays = warningNoticeVO == null ? null : warningNoticeVO.getExpiryReminderDays();
return reminderDays == null || reminderDays < 0 ? DEFAULT_NEAR_EXPIRE_DAYS : reminderDays;
}
/**
* 若是门店自定义证照且模板状态为0则不发送预警通知。
*/
private boolean isStoreCustomTemplateWarningDisabled(CertificateInstanceEntity entity,
Map<String, StoreCertificatePhotoEntity> templateMap) {
if (entity == null || !StrUtil.equalsIgnoreCase(entity.getCertificateType(), CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType())) {
return false;
}
String templateId = StrUtil.trim(entity.getTemplateId());
if (StrUtil.isBlank(templateId)) {
return false;
}
StoreCertificatePhotoEntity template = templateMap.get(templateId);
return template != null && Integer.valueOf(STORE_CUSTOM_TEMPLATE_DISABLED_STATUS).equals(template.getStatus());
}
/**
* 按证照类型解析通知频率天数。
*/
private int resolveNoticeFrequencyDays(WarningNoticeVO warningNoticeVO) {
Integer frequencyDays = warningNoticeVO == null ? null : warningNoticeVO.getNoticeFrequencyDays();
return frequencyDays == null || frequencyDays <= 0 ? DEFAULT_NOTICE_FREQUENCY_DAYS : frequencyDays;
}
/**
* 当预警配置的临期提醒天数小于等于0时跳过预警通知发送。
*/
private boolean shouldSkipWarningNoticeByExpiryReminderDays(WarningNoticeVO warningNoticeVO) {
Integer expiryReminderDays = warningNoticeVO == null ? null : warningNoticeVO.getExpiryReminderDays();
return expiryReminderDays == null || expiryReminderDays <= 0;
}
/**
* 批量更新证照状态。
*/
private void updateCertificateStatusBatch(List<CertificateInstanceEntity> needUpdateList) {
if (CollUtil.isEmpty(needUpdateList)) {
return;
}
for (CertificateInstanceEntity entity : needUpdateList) {
if (entity == null || StrUtil.isBlank(entity.getId()) || entity.getStatus() == null) {
continue;
}
CertificateInstanceEntity updateEntity = new CertificateInstanceEntity();
updateEntity.setId(entity.getId());
updateEntity.setStatus(entity.getStatus());
certificateInstanceMapper.updateById(updateEntity);
}
}
/**
* 批量发送临期通知。
*/
private void sendNearExpireNoticeBatch(List<NearExpireNoticeTask> noticeTaskList, String tenantId) {
if (CollUtil.isEmpty(noticeTaskList) || StrUtil.isBlank(tenantId)) {
return;
}
Pair<Map<String, OrganizeBaseInfoVO>,Map<String,UserBaseInfoVO>> organizeMapAndUserOrgMap = queryOrganizeInfoMap(noticeTaskList,tenantId);
Map<String, OrganizeBaseInfoVO> organizeMap = organizeMapAndUserOrgMap.getLeft();
Map<String,UserBaseInfoVO> userBaseInfoByUserId = organizeMapAndUserOrgMap.getRight();
for (NearExpireNoticeTask task : noticeTaskList) {
if (task == null || task.getEntity() == null) {
continue;
}
CertificateInstanceEntity entity = task.getEntity();
OrganizeBaseInfoVO organizeBaseInfoVO = organizeMap.get(entity.getSubjectId());
//组织被禁用,则不推送
if(!isHealthEmployeeCertificate(entity) && Objects.nonNull(organizeBaseInfoVO) && organizeBaseInfoVO.isDisabled()){
continue;
}
//如果不是健康证
if(!isHealthEmployeeCertificate(entity)){
List<String> receiverUserIds = resolveReceiverUserIds(entity, organizeMap, task.getWarningNoticeVO(), tenantId);
if(CollUtil.isEmpty(receiverUserIds)){
continue;
}
NoticeMessage noticeMessage = buildNearExpireMessage(entity, task, organizeMap,false,null);
noticeHelper.sendMessage(receiverUserIds, tenantId, noticeMessage.getTitle(), noticeMessage.getContent(), null, null, null);
continue;
}
//如果是健康证的通知
//获取健康证本人
List<String> receiverUserIds = resolveReceiverUserIds(entity, organizeMap, task.getWarningNoticeVO(), tenantId);
if (CollUtil.isNotEmpty(receiverUserIds)){
NoticeMessage noticeMessage = buildNearExpireMessage(entity, task, organizeMap,true,null);
noticeHelper.sendMessage(receiverUserIds, tenantId, noticeMessage.getTitle(), noticeMessage.getContent(), BUTTON_NAME_HANDLER, buildJumpHealthCertificateUrl(entity), MP_ID_CERTIFICATE);
}
//获取健康证所属组织的负责人
OrganizeBaseInfoVO healthEmployeeOrgLeaderReceiver = resolveHealthEmployeeOrgLeaderReceiver(entity, organizeMap, userBaseInfoByUserId);
List<String> otherReceiverUserIds = new ArrayList<>();
if(Objects.nonNull(healthEmployeeOrgLeaderReceiver)){
otherReceiverUserIds.add(healthEmployeeOrgLeaderReceiver.getLeaderId());
}
//获取配置的通知者
List<String> configUserIds = resolveWarningConfigUserIds(entity, task.getWarningNoticeVO(), tenantId);
if(CollUtil.isNotEmpty(configUserIds)){
otherReceiverUserIds.addAll(configUserIds);
}
if (CollUtil.isEmpty(otherReceiverUserIds)) {
continue;
}
//健康证风险,发给该健康证的组织负责人以及配置的人员
NoticeMessage healthEmployLeaderNoticeMessage = buildNearExpireMessage(entity, task, organizeMap,false,userBaseInfoByUserId);
if(Objects.isNull(healthEmployLeaderNoticeMessage)){
continue;
}
noticeHelper.sendMessage(otherReceiverUserIds, tenantId, healthEmployLeaderNoticeMessage.getTitle(), healthEmployLeaderNoticeMessage.getContent(), BUTTON_NAME_HANDLER, buildJumpHealthCertificateUrl(entity), MP_ID_CERTIFICATE);
}
}
private String buildJumpHealthCertificateUrl(CertificateInstanceEntity entity) {
if(Objects.isNull(entity)){
return null;
}
String subjectId = entity.getSubjectId();
if(StringUtil.isBlank(subjectId)){
return null;
}
return String.format(URL_CERTIFICATE,entity.getId());
}
/**
* 左侧 查询组织信息映射(用于拿负责人和组织名称)。 key为组织idvalue为基础组织信息。
* 右侧 如果是健康证还需要查询员工所属组织信息。key为userIdvalue为该userId的基础信息
* 包含非员工主体的组织信息,以及健康证员工所属组织的信息。
*/
private Pair<Map<String, OrganizeBaseInfoVO>,Map<String,UserBaseInfoVO>> queryOrganizeInfoMap(List<NearExpireNoticeTask> noticeTaskList,String tenantId) {
Set<String> organizeIdSet = new LinkedHashSet<>();
// 收集健康证员工的 subjectId用于查询其所属组织
Set<String> healthEmployeeUserIds = new LinkedHashSet<>();
for (NearExpireNoticeTask task : noticeTaskList) {
if (task == null || task.getEntity() == null) {
continue;
}
CertificateInstanceEntity entity = task.getEntity();
if (Integer.valueOf(SUBJECT_TYPE_EMPLOYEE).equals(entity.getSubjectType())) {
if (isHealthEmployeeCertificate(entity) && StrUtil.isNotBlank(entity.getSubjectId())) {
healthEmployeeUserIds.add(StrUtil.trim(entity.getSubjectId()));
}
continue;
}
String organizeId = StrUtil.trim(entity.getSubjectId());
if (StrUtil.isNotBlank(organizeId)) {
organizeIdSet.add(organizeId);
}
}
Map<String, UserBaseInfoVO> userBaseInfoByUserId = Collections.emptyMap();
// 查询健康证员工所属的组织ID批量SQL查询
if (CollUtil.isNotEmpty(healthEmployeeUserIds)) {
userBaseInfoByUserId = organizationHelper.buildUserBaseInfoByUserIds(healthEmployeeUserIds,tenantId);
organizeIdSet.addAll(userBaseInfoByUserId.values()
.stream()
.map(UserBaseInfoVO::getOrganizeId)
.collect(Collectors.toSet()));
}
if (CollUtil.isEmpty(organizeIdSet)) {
return Pair.of(Collections.emptyMap(),userBaseInfoByUserId);
}
return Pair.of(organizationHelper.buildBaseOrganizeVO(organizeIdSet,tenantId),userBaseInfoByUserId);
}
/**
* 解析接收人:
* 1. 健康证通知当前人员
* 2. 组织/门店证照通知组织负责人。
*/
private List<String> resolveReceiverUserIds(CertificateInstanceEntity entity,
Map<String, OrganizeBaseInfoVO> organizeMap,
WarningNoticeVO warningNoticeVO,
String tenantId) {
if (entity == null) {
return Collections.emptyList();
}
String subjectId = StrUtil.trim(entity.getSubjectId());
if (StrUtil.isBlank(subjectId)) {
return Collections.emptyList();
}
List<String> receiverUserIds = new ArrayList<>();
if (isHealthEmployeeCertificate(entity)) {
receiverUserIds.add(subjectId);
} else {
addOrganizationLeaderReceiver(entity, organizeMap, receiverUserIds);
}
if (warningNoticeVO != null && !isHealthEmployeeCertificate(entity)) {
List<String> configUserIds = resolveWarningConfigUserIds(entity, warningNoticeVO, tenantId);
if (CollUtil.isNotEmpty(configUserIds)) {
receiverUserIds.addAll(configUserIds);
}
}
return normalizeIdList(receiverUserIds);
}
/**
* 非健康证默认通知组织负责人。
*/
private void addOrganizationLeaderReceiver(CertificateInstanceEntity entity,
Map<String, OrganizeBaseInfoVO> organizeMap,
List<String> receiverUserIds) {
if (entity == null || CollUtil.isEmpty(organizeMap) || receiverUserIds == null) {
return;
}
String subjectId = StrUtil.trim(entity.getSubjectId());
if (StrUtil.isBlank(subjectId)) {
return;
}
OrganizeBaseInfoVO organize = organizeMap.get(subjectId);
if (organize == null || StrUtil.isBlank(organize.getLeaderId())) {
return;
}
receiverUserIds.add(StrUtil.trim(organize.getLeaderId()));
}
/**
* 健康证通知该员工所属组织的负责人。
*/
private OrganizeBaseInfoVO resolveHealthEmployeeOrgLeaderReceiver(CertificateInstanceEntity entity,
Map<String, OrganizeBaseInfoVO> organizeMap,
Map<String,UserBaseInfoVO> userBaseInfoByUserId) {
if (Objects.isNull(entity) || CollUtil.isEmpty(organizeMap)) {
return null;
}
if(!isHealthEmployeeCertificate(entity)){
return null;
}
String subjectId = StrUtil.trim(entity.getSubjectId());
UserBaseInfoVO userBaseInfoVO = userBaseInfoByUserId.get(subjectId);
if(Objects.isNull(userBaseInfoVO)){
log.error("resolveHealthEmployeeOrgLeaderReceiver 用户的基础为空!.subjectId:{}",subjectId);
return null;
}
String orgId = userBaseInfoVO.getOrganizeId();
if(StrUtil.isBlank(orgId)){
log.error("resolveHealthEmployeeOrgLeaderReceiver 用户的组织id为空!.subjectId:{}",subjectId);
return null;
}
OrganizeBaseInfoVO organize = organizeMap.get(orgId);
if (organize != null && StrUtil.isNotBlank(organize.getLeaderId())) {
return organize;
}
return null;
}
/**
* 解析预警配置接收人。
*/
private List<String> resolveWarningConfigUserIds(CertificateInstanceEntity entity,
WarningNoticeVO warningNoticeVO,
String tenantId) {
if (entity == null || warningNoticeVO == null || StrUtil.isBlank(tenantId)) {
return Collections.emptyList();
}
List<WarningNoticeUserConfigVO> noticeConfigList = warningNoticeVO.getNoticeConfigList();
if (CollUtil.isEmpty(noticeConfigList)) {
return Collections.emptyList();
}
boolean healthCertificate = isHealthEmployeeCertificate(entity);
List<String> receiverUserIds = new ArrayList<>();
for (WarningNoticeUserConfigVO config : noticeConfigList) {
if (config == null || StrUtil.isBlank(config.getNoticeUserType()) || CollUtil.isEmpty(config.getNoticeUserList())) {
continue;
}
String noticeUserType = StrUtil.trim(config.getNoticeUserType());
if (StrUtil.equalsAnyIgnoreCase(noticeUserType, NOTICE_USER_TYPE_POSITION)) {
receiverUserIds.addAll(resolvePositionUserIds(config.getNoticeUserList(), entity, tenantId));
continue;
}
if (StrUtil.equalsAnyIgnoreCase(noticeUserType, NOTICE_USER_TYPE_PERSONNEL, NOTICE_USER_TYPE_PERSON)) {
if (healthCertificate) {
receiverUserIds.addAll(resolveHealthPersonnelConfigUserIds(config.getNoticeUserList(), entity, tenantId));
} else {
receiverUserIds.addAll(extractTargetIds(config.getNoticeUserList()));
}
}
}
return normalizeIdList(receiverUserIds);
}
/**
* 按岗位解析通知人。健康证场景下需按数据权限过滤。
*/
private List<String> resolvePositionUserIds(List<WarningNoticeTargetVO> noticeUserList,
CertificateInstanceEntity entity,
String tenantId) {
List<String> positionIds = extractTargetIds(noticeUserList);
if (CollUtil.isEmpty(positionIds) || StrUtil.isBlank(tenantId)) {
return Collections.emptyList();
}
QueryUserBatchDTO dto = new QueryUserBatchDTO();
dto.setPositionIds(positionIds);
dto.setTenantId(tenantId);
ActionResult<List<UserBoundVO>> userInfoBatch = v2UserApi.getUserInfoBatch(dto);
if (userInfoBatch == null || !Integer.valueOf(200).equals(userInfoBatch.getCode()) || CollUtil.isEmpty(userInfoBatch.getData())) {
return Collections.emptyList();
}
List<String> positionUserIds = normalizeIdList(userInfoBatch.getData().stream().map(UserBoundVO::getId).collect(Collectors.toList()));
// 健康证场景:按数据权限过滤岗位人员
if (isHealthEmployeeCertificate(entity) && CollUtil.isNotEmpty(positionUserIds)) {
return filterByDataPermission(positionUserIds, entity, tenantId);
}
return positionUserIds;
}
/**
* 按数据权限过滤通知人员(健康证场景)。
*/
private List<String> filterByDataPermission(List<String> candidateUserIds,
CertificateInstanceEntity entity,
String tenantId) {
String subjectId = entity == null ? null : StrUtil.trim(entity.getSubjectId());
if (StrUtil.isBlank(subjectId) || StrUtil.isBlank(tenantId) || CollUtil.isEmpty(candidateUserIds)) {
return Collections.emptyList();
}
List<UserEntity> userEntityList = userApi.getUserListNoData(candidateUserIds, tenantId);
if (CollUtil.isEmpty(userEntityList)) {
return Collections.emptyList();
}
List<String> result = new ArrayList<>();
for (UserEntity userEntity : userEntityList) {
if (userEntity == null || StrUtil.isBlank(userEntity.getId())) {
continue;
}
String userId = StrUtil.trim(userEntity.getId());
if (Integer.valueOf(USER_ADMINISTRATOR_FLAG).equals(userEntity.getIsAdministrator())) {
result.add(userId);
continue;
}
List<String> dataPermissionUserIds;
try {
dataPermissionUserIds = permissionsUtils.obtainPersonnelUserIdDataPermissions(userId, HEALTH_CERTIFICATE_PERMISSION_MODULE_ID);
} catch (Exception ex) {
log.warn("query health certificate position user permission failed,userId={},module={}", userId, HEALTH_CERTIFICATE_PERMISSION_MODULE_ID, ex);
continue;
}
if (dataPermissionUserIds == null) {
result.add(userId);
continue;
}
if (CollUtil.isNotEmpty(dataPermissionUserIds) && dataPermissionUserIds.contains(subjectId)) {
result.add(userId);
}
}
return normalizeIdList(result);
}
/**
* 健康证-按人员配置时,按数据权限过滤可通知人员。
*/
private List<String> resolveHealthPersonnelConfigUserIds(List<WarningNoticeTargetVO> noticeUserList,
CertificateInstanceEntity entity,
String tenantId) {
String subjectId = entity == null ? null : StrUtil.trim(entity.getSubjectId());
if (StrUtil.isBlank(subjectId) || StrUtil.isBlank(tenantId)) {
return Collections.emptyList();
}
List<String> targetUserIds = extractTargetIds(noticeUserList);
if (CollUtil.isEmpty(targetUserIds)) {
return Collections.emptyList();
}
List<UserEntity> userEntityList = userApi.getUserListNoData(targetUserIds, tenantId);
if (CollUtil.isEmpty(userEntityList)) {
return Collections.emptyList();
}
List<String> result = new ArrayList<>();
for (UserEntity userEntity : userEntityList) {
if (userEntity == null || StrUtil.isBlank(userEntity.getId())) {
continue;
}
String userId = StrUtil.trim(userEntity.getId());
if (Integer.valueOf(USER_ADMINISTRATOR_FLAG).equals(userEntity.getIsAdministrator())) {
result.add(userId);
continue;
}
List<String> dataPermissionUserIds;
try {
dataPermissionUserIds = permissionsUtils.obtainPersonnelUserIdDataPermissions(userId, HEALTH_CERTIFICATE_PERMISSION_MODULE_ID);
} catch (Exception ex) {
log.warn("query health certificate user permission failed,userId={},module={}", userId, HEALTH_CERTIFICATE_PERMISSION_MODULE_ID, ex);
continue;
}
if (dataPermissionUserIds == null || CollUtil.isEmpty(dataPermissionUserIds)) {
if (dataPermissionUserIds == null) {
result.add(userId);
}
continue;
}
if (dataPermissionUserIds.contains(subjectId)) {
result.add(userId);
}
}
return normalizeIdList(result);
}
/**
* 提取配置对象ID。
*/
private List<String> extractTargetIds(List<WarningNoticeTargetVO> noticeUserList) {
if (CollUtil.isEmpty(noticeUserList)) {
return Collections.emptyList();
}
return normalizeIdList(noticeUserList.stream().map(WarningNoticeTargetVO::getId).collect(Collectors.toList()));
}
/**
* 标准化ID列表。
*/
private List<String> normalizeIdList(Collection<String> idList) {
if (CollUtil.isEmpty(idList)) {
return Collections.emptyList();
}
return idList.stream()
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.distinct()
.collect(Collectors.toList());
}
/**
* 判断是否健康证+人员主体。
*/
private boolean isHealthEmployeeCertificate(CertificateInstanceEntity entity) {
return entity != null
&& StrUtil.equalsIgnoreCase(entity.getCertificateType(), CertificateTypeEnum.HEALTH_CERTIFICATE.getType())
&& Integer.valueOf(SUBJECT_TYPE_EMPLOYEE).equals(entity.getSubjectType());
}
/**
* 判断是否自定义证照
*/
private boolean isStoreCustomCertificate(CertificateInstanceEntity entity) {
return entity != null
&& StrUtil.equalsIgnoreCase(entity.getCertificateType(), CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType());
}
/**
* 计算状态。
*/
private int calculateStatus(CertificateInstanceEntity entity, Integer nearExpireDays,Map<String, StoreCertificatePhotoEntity> templateMap) {
Integer isLongTerm = entity.getIsLongTerm();
Date expireDate = entity.getExpireDate();
String certificateType = entity.getCertificateType();
String templateId = entity.getTemplateId();
StoreCertificatePhotoEntity storeCertificatePhotoEntity = templateMap.get(templateId);
if(CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType().equals(certificateType) &&//自定义证照如果临期提醒设置为0则计算为正常
Objects.nonNull(storeCertificatePhotoEntity) &&
Integer.valueOf(0).equals(nearExpireDays)){
return STATUS_NORMAL;
}
if (Integer.valueOf(1).equals(isLongTerm)) {
return STATUS_NORMAL;
}
if (expireDate == null) {
return STATUS_MISSING;
}
Date today = DateUtil.beginOfDay(DateUtil.date());
Date target = DateUtil.beginOfDay(expireDate);
if (target.before(today)) {
return STATUS_EXPIRED;
}
int threshold = nearExpireDays == null || nearExpireDays < 0 ? 0 : nearExpireDays;
long daysDiff = DateUtil.betweenDay(today, target, false);
return daysDiff <= threshold ? STATUS_NEAR_EXPIRE : STATUS_NORMAL;
}
/**
* 计算距离到期天数。
*/
private Integer calculateDaysToExpire(Date expireDate) {
if (expireDate == null) {
return null;
}
Date today = DateUtil.beginOfDay(DateUtil.date());
Date target = DateUtil.beginOfDay(expireDate);
return (int) DateUtil.betweenDay(today, target, false);
}
/**
* 按通知频率判断是否发送。
*/
private boolean shouldSendByFrequency(Integer daysToExpire, Integer noticeFrequencyDays) {
if (daysToExpire == null || daysToExpire < 0) {
return false;
}
int frequency = noticeFrequencyDays == null || noticeFrequencyDays <= 0 ? DEFAULT_NOTICE_FREQUENCY_DAYS : noticeFrequencyDays;
return frequency <= 1 || daysToExpire % frequency == 0;
}
/**
* 构建临期通知文案。
*/
private NoticeMessage buildNearExpireMessage(CertificateInstanceEntity entity,
NearExpireNoticeTask task,
Map<String, OrganizeBaseInfoVO> organizeMap,
boolean healthCertificateReceiverBySelf,
Map<String,UserBaseInfoVO> userBaseInfoByUserId) {
Integer daysToExpire = task.getDaysToExpire();
String certificateName = resolveCertificateName(entity == null ? null : entity.getCertificateType(),task.getCertificateTemplateName());
int remainDays = daysToExpire == null ? 0 : Math.max(daysToExpire, 0);
if(isHealthEmployeeCertificate(entity)){
return buildHealthCertificateNearExpireMessage(entity,healthCertificateReceiverBySelf,userBaseInfoByUserId,remainDays);
}
String subjectName = "-";
if (entity != null && StrUtil.isNotBlank(entity.getSubjectId())) {
OrganizeBaseInfoVO organize = organizeMap.get(StrUtil.trim(entity.getSubjectId()));
if (organize != null && StrUtil.isNotBlank(organize.getName())) {
subjectName = StrUtil.trim(organize.getName());
}
}
String title = subjectName + certificateName + "即将到期,请及时处理";
String content;
if(remainDays <= 0){
content = String.format("%s%s今天到期请及时更新。", subjectName, certificateName);
}else{
content = String.format("%s%s离到期时间还有%s天请及时更新。", subjectName, certificateName, remainDays);
}
return new NoticeMessage(title, content);
}
private NoticeMessage buildHealthCertificateNearExpireMessage(CertificateInstanceEntity entity,
boolean receiverBySelf,
Map<String,UserBaseInfoVO> userBaseInfoByUserId,
int remainDays) {
if(receiverBySelf){
return new NoticeMessage("您的健康证存在风险,请及时处理", buildHealthCertificateNearExpireContent(remainDays));
}
UserBaseInfoVO baseInfoVO = userBaseInfoByUserId.get(entity.getSubjectId());
if(Objects.isNull(baseInfoVO)){
log.error("buildNearExpireMessage 通知健康证所属负责人该健康证的用户名称获取为空。entity:{}",entity);
return null;
}
return new NoticeMessage("您管理组织的健康证存在风险,请及时处理", buildNearExpireContentByNoSelf(baseInfoVO.getUserName(),remainDays));
}
private String buildHealthCertificateNearExpireContent(int remainDays) {
if(remainDays <= 0){
return "您的健康证今天过期,请及时更新健康证。";
}
return String.format("您的健康证离到期时间还有%s天请及时更新健康证。", remainDays);
}
private String buildNearExpireContentByNoSelf(String username, int remainDays) {
if(remainDays <= 0){
return String.format("%s的健康证今天过期请及时更新健康证。",username);
}
return String.format("%s的健康证离到期时间还有%s天请及时更新健康证。",username,remainDays);
}
/**
* 证照类型转中文名称。
*/
private String resolveCertificateName(String certificateType,String certificateTemplateName) {
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.HEALTH_CERTIFICATE.getType())) {
return "健康证";
}
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.BUSINESS_LICENSE.getType())) {
return "营业执照";
}
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.HYGIENE_LICENSE.getType())) {
return "食品经营许可证";
}
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType())) {
return certificateTemplateName;
}
return "证照";
}
/**
* 临期通知任务。
*/
@Data
private static class NearExpireNoticeTask {
private final CertificateInstanceEntity entity;
private final Integer daysToExpire;
private final WarningNoticeVO warningNoticeVO;
private final String certificateTemplateName;
private NearExpireNoticeTask(CertificateInstanceEntity entity, Integer daysToExpire, WarningNoticeVO warningNoticeVO,String certificateTemplateName) {
this.entity = entity;
this.daysToExpire = daysToExpire;
this.warningNoticeVO = warningNoticeVO;
this.certificateTemplateName = certificateTemplateName;
}
private CertificateInstanceEntity getEntity() {
return entity;
}
private Integer getDaysToExpire() {
return daysToExpire;
}
private WarningNoticeVO getWarningNoticeVO() {
return warningNoticeVO;
}
}
/**
* 通知文案。
*/
private static class NoticeMessage {
private final String title;
private final String content;
private NoticeMessage(String title, String content) {
this.title = title;
this.content = content;
}
private String getTitle() {
return title;
}
private String getContent() {
return content;
}
}
}

View File

@@ -0,0 +1,112 @@
package jnpf.certificate.controller.app;
import jnpf.base.ActionResult;
import jnpf.certificate.service.CertificateInstanceService;
import jnpf.certificate.service.CertificateManageService;
import jnpf.model.certificate.req.app.CertificateAppBusinessLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHealthCertificateUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHygieneLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppStoreCustomUpdateReq;
import jnpf.model.certificate.vo.app.CertificateAppCertificateDetailVO;
import jnpf.model.certificate.vo.app.HealthCertificateDetailVO;
import jnpf.model.certificate.vo.app.HealthCertificateVO;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.Optional;
/**
* App端证照管理控制器。
*/
@RestController
@Validated
@RequestMapping("/app/certificate-manage")
public class AppCertificateManageController {
/**
* App端证照管理服务。
*/
@Autowired
private CertificateManageService certificateManageService;
@Autowired
private CertificateInstanceService certificateInstanceService;
/**
* 根据证照实例ID查询证照详情。
* 返回结果中包含certificateType并按类型返回不同明细对象。
*
* @param certificateInstanceId 证照实例ID
* @return 证照详情
*/
@GetMapping("/query-info")
public ActionResult<CertificateAppCertificateDetailVO> queryInfo(@RequestParam("certificateInstanceId")
@NotBlank(message = "证照实例ID不能为空")
String certificateInstanceId) {
return ActionResult.success(certificateManageService.queryInfo(certificateInstanceId));
}
/**
* 更新健康证。
*
* @param req 更新参数
* @return 操作结果
*/
@PutMapping("/update-health")
public ActionResult<Void> updateHealth(@Validated @RequestBody CertificateAppHealthCertificateUpdateReq req) {
certificateManageService.updateHealthCertificate(req);
return ActionResult.success();
}
/**
* 查询某个人的健康证信息
*
* @return 健康证信息
*/
@GetMapping("/query-health-certificate/{userId}")
public ActionResult<HealthCertificateVO> getHealthCertificateDetail(@PathVariable("userId") String userId) {
Optional<HealthCertificateDetailVO> healthCertificateDetailVOOptional = certificateInstanceService.getHealthCertificateDetail(userId);
return healthCertificateDetailVOOptional.map(h->ActionResult.success(HealthCertificateVO.of(h)))
.orElseGet(() -> ActionResult.fail(404, "未查询到健康证信息"));
}
/**
* 更新营业执照。
*
* @param req 更新参数
* @return 操作结果
*/
@PutMapping("/update-business-license")
public ActionResult<Void> updateBusinessLicense(@Validated @RequestBody CertificateAppBusinessLicenseUpdateReq req) {
certificateManageService.updateBusinessLicense(req);
return ActionResult.success();
}
/**
* 更新食品经营许可证。
*
* @param req 更新参数
* @return 操作结果
*/
@PutMapping("/update-hygiene-license")
public ActionResult<Void> updateHygieneLicense(@Validated @RequestBody CertificateAppHygieneLicenseUpdateReq req) {
certificateManageService.updateHygieneLicense(req);
return ActionResult.success();
}
/**
* 更新门店自定义证照。
*
* @param req 更新参数
* @return 操作结果
*/
@PutMapping("/update-store-custom")
public ActionResult<Void> updateStoreCustom(@Valid @RequestBody CertificateAppStoreCustomUpdateReq req) {
certificateManageService.updateStoreCustomCertificate(req);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,83 @@
package jnpf.certificate.controller.app;
import jnpf.base.ActionResult;
import jnpf.certificate.service.CertificateAppReminderService;
import jnpf.model.certificate.req.app.CertificateAppBatchRemindReq;
import jnpf.model.certificate.req.app.CertificateAppSingleRemindReq;
import jnpf.util.UserProvider;
import jnpf.util.context.ThreadContext;
import lombok.RequiredArgsConstructor;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* App端证照提醒控制器。
*/
@RestController
@Validated
@RequestMapping("/app/certificate-reminder")
public class AppCertificateReminderController {
/**
* 证照提醒服务。
*/
@Autowired
private CertificateAppReminderService certificateAppReminderService;
@Autowired
private ThreadPoolTaskExecutor commonExecutor;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final String REMIND_LOCK_KEY_PREFIX = "certificate-reminder:";
/**
* 一键提醒。
*
* @param req 提醒参数(主体类型)
* @return 操作结果
*/
@PostMapping("/batch-remind")
public ActionResult<Void> batchRemind(@Validated @RequestBody CertificateAppBatchRemindReq req) {
Boolean succ = stringRedisTemplate.opsForValue().setIfAbsent(buildRemindLockKey(),"1", 10, TimeUnit.SECONDS);
if(Objects.isNull(succ) || !succ){
return ActionResult.fail("操作太快了,请稍后在操作!");
}
//可能数据过多,但由于内部过滤数据,无法通过单独的线程执行,所以先暂时这样。
certificateAppReminderService.batchRemind(req);
return ActionResult.success();
}
/**
* 单个提醒。
*
* @param req 提醒参数证照实例ID
* @return 操作结果
*/
@PostMapping("/single-remind")
public ActionResult<Void> singleRemind(@Validated @RequestBody CertificateAppSingleRemindReq req) {
Boolean succ = stringRedisTemplate.opsForValue().setIfAbsent(buildRemindLockKey(),"1", 10, TimeUnit.SECONDS);
if(Objects.isNull(succ) || !succ){
return ActionResult.fail("操作太快了,请稍后在操作!");
}
certificateAppReminderService.singleRemind(req);
return ActionResult.success();
}
private String buildRemindLockKey() {
String userId = UserProvider.getLoginUserId();
return REMIND_LOCK_KEY_PREFIX + userId;
}
}

View File

@@ -0,0 +1,88 @@
package jnpf.certificate.controller.app;
import com.github.pagehelper.PageInfo;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.certificate.service.CertificateAppRiskService;
import jnpf.model.certificate.req.app.CertificateAppEmployeeRiskQueryReq;
import jnpf.model.certificate.req.app.CertificateAppRiskChartReq;
import jnpf.model.certificate.req.app.CertificateAppStoreRiskQueryReq;
import jnpf.model.certificate.vo.app.CertificateAppEmployeeRiskVO;
import jnpf.model.certificate.vo.app.CertificateAppRiskChartVO;
import jnpf.model.certificate.vo.app.CertificateAppRiskReminderCountVO;
import jnpf.model.certificate.vo.app.CertificateAppStoreRiskVO;
import jnpf.util.FtbUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* App端证照风险管理控制器。
*/
@RestController
@Validated
@RequestMapping("/app/certificate-risk")
public class AppCertificateRiskController {
/**
* 证照风险服务。
*/
@Autowired
private CertificateAppRiskService certificateAppRiskService;
/**
* 查询风险图表统计。
*
* @param req 查询参数(组织门店、证照类型)
* @return 风险图表统计数据
*/
@GetMapping("/query-chart")
public ActionResult<CertificateAppRiskChartVO> queryChart(@Valid CertificateAppRiskChartReq req) {
return ActionResult.success(certificateAppRiskService.queryChart(req));
}
/**
* 分页查询员工证照风险列表。
*
* @param req 查询参数(组织门店、分页)
* @return 员工证照风险分页结果(含分页信息与列表)
*/
@GetMapping("/query-employee-page")
public ActionResult<PageListVO<CertificateAppEmployeeRiskVO>> queryEmployeePage(@Valid CertificateAppEmployeeRiskQueryReq req) {
PageInfo<CertificateAppEmployeeRiskVO> pageInfo = certificateAppRiskService.queryEmployeePage(req);
// 返回员工证照风险分页数据。
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
}
/**
* 分页查询门店证照风险列表。
* 默认仅查询缺失、过期、临期状态1、2、3
*
* @param req 查询参数(组织门店、证照类型、分页)
* @return 门店证照风险分页结果
*/
@GetMapping("/query-store-page")
public ActionResult<PageListVO<CertificateAppStoreRiskVO>> queryStorePage(@Valid CertificateAppStoreRiskQueryReq req) {
PageInfo<CertificateAppStoreRiskVO> pageInfo = certificateAppRiskService.queryStorePage(req);
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
}
/**
* 查询风险提醒总数量。
* 无入参,统计缺失、临期、过期的员工风险数量和组织风险数量。
*
* @return 风险提醒数量统计
*/
@GetMapping("/query-reminder-count")
public ActionResult<CertificateAppRiskReminderCountVO> queryReminderCount(@RequestParam(value = "orgId",required = false)String orgId) {
return ActionResult.success(certificateAppRiskService.queryRiskReminderCount(orgId));
}
}

View File

@@ -0,0 +1,151 @@
package jnpf.certificate.helper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.handler.codec.http.HttpUtil;
import jnpf.ImRobotApi;
import jnpf.base.ActionResult;
import jnpf.from.*;
import jnpf.util.JsonUtil;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpGet;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* 证照提醒消息发送工具。
* 参考健康证定时任务的消息结构,实现按用户集合发送提醒。
*/
@Component
@Slf4j
public class NoticeHelper {
private static final String APP_NAME = "证照管理";
public static final String MP_ID_CERTIFICATE = "__UNI__C5F3D72";
public static final String URL_CERTIFICATE = "/pages/license/detail?id=%s&label=健康证";
public static final String BUTTON_NAME_HANDLER = "去处理";
private static final String LOGO_URL = "https://jnpf-resource-1304460613.cos.ap-chengdu.myqcloud.com/jnpf-resources/UserAvatar/665ecb69e4b0ae5df114c87a.png";
private static final int SEND_BATCH_SIZE = 450;
@Resource
private ImRobotApi imRobotApi;
/**
* 发送机器人提醒消息。
*
* @param userIds 接收用户ID集合
* @param tenantId 租户ID
* @param title 消息标题
* @param content 消息内容
* @param buttonName 跳转按钮名称(可为空)
* @param url 跳转地址(可为空)
* @param mpId 小程序ID可为空
*/
public void sendMessage(Collection<String> userIds, String tenantId, String title, String content,
String buttonName, String url, String mpId) {
List<String> targetUserIds = normalizeUserIds(userIds);
if (CollUtil.isEmpty(targetUserIds) || StrUtil.isBlank(tenantId) || StrUtil.isBlank(title) || StrUtil.isBlank(content)) {
return;
}
SingleSendRobotNoticeForm form = new SingleSendRobotNoticeForm();
form.setRobotTypeEnum(ImRobotTypeEnum.FTB_XZS);
form.setTenantId(tenantId);
if (StrUtil.isNotBlank(mpId)) {
form.setMessageAttributionRobotMpId(mpId);
}
form.setRobotNoticeDataForm(buildNoticeData(title, content, buttonName, url, mpId));
for (int start = 0; start < targetUserIds.size(); start += SEND_BATCH_SIZE) {
int end = Math.min(start + SEND_BATCH_SIZE, targetUserIds.size());
form.setToUserIds(targetUserIds.subList(start, end));
ActionResult<?> actionResult = imRobotApi.sendSingleRobotNotice(form);
if (actionResult == null || !Integer.valueOf(200).equals(actionResult.getCode())) {
log.error("证照提醒消息发送失败req={},result={}", JSONUtil.toJsonStr(form), JSONUtil.toJsonStr(actionResult));
}
}
}
/**
* 构建消息体。
*/
private SendRobotNoticeDataForm buildNoticeData(String title, String content, String buttonName, String url, String mpId) {
SendRobotNoticeDataForm noticeData = new SendRobotNoticeDataForm();
noticeData.setLogo(LOGO_URL);
noticeData.setAppName(APP_NAME);
noticeData.setTitle(title);
noticeData.setContent(content);
if (StrUtil.isNotBlank(buttonName) && StrUtil.isNotBlank(url) && StrUtil.isNotBlank(mpId)) {
JumpUrlListModel jumpUrl = new JumpUrlListModel();
jumpUrl.setDisplayMethodEnum(JumpUrlListModel.DisplayMethodEnum.TRUE);
jumpUrl.setButtonName(buttonName);
jumpUrl.setReqMethod(JumpUrlListModel.ReqMethodEnum.GET);
jumpUrl.setType(1);
jumpUrl.setUrl(url);
jumpUrl.setMpId(mpId);
//fixme 有空改为传参,不这样处理
if(url.contains("?")){
jumpUrl.setMiniAppUrl(buildMiniAppUrl(mpId,url));
}
LinkedList<JumpUrlListModel> jumpUrlList = new LinkedList<>();
jumpUrlList.add(jumpUrl);
noticeData.setJumpUrlList(jumpUrlList);
}
return noticeData;
}
private String buildUrl(String url) {
if(!url.contains("?")){
return url;
}
return url.substring(0,url.indexOf("?"));
}
private MiniAppUrl buildMiniAppUrl(String mpId,String url) {
String params = url.substring(url.indexOf("?")+1);
if(StringUtil.isBlank(params)){
return null;
}
JSONObject mpParam = new JSONObject();
for (String kv:params.split("&")){
String [] kvs = kv.split("=");
if(kvs.length != 2){
return null;
}
mpParam.set(kvs[0],kvs[1]);
}
return MiniAppUrl.builder()
.mpId(mpId)
.mpPage(buildUrl(url))
.mpParam(mpParam.toString())
.build();
}
/**
* 规整接收用户集合:去空、去重、去首尾空格。
*/
private List<String> normalizeUserIds(Collection<String> userIds) {
if (CollUtil.isEmpty(userIds)) {
return new ArrayList<>();
}
Set<String> userIdSet = new LinkedHashSet<>();
for (String userId : userIds) {
String trimUserId = StrUtil.trim(userId);
if (StrUtil.isNotBlank(trimUserId)) {
userIdSet.add(trimUserId);
}
}
return new ArrayList<>(userIdSet);
}
}

View File

@@ -0,0 +1,243 @@
package jnpf.certificate.helper;
import cn.hutool.core.collection.CollectionUtil;
import jnpf.base.ActionResult;
import jnpf.permission.StoreApi;
import jnpf.permission.V2OrganizeApi;
import jnpf.permission.V2PositionApi;
import jnpf.permission.V2UserApi;
import jnpf.permission.dto.v2.organzie.BaseOrganizeQueryDTO;
import jnpf.permission.dto.v2.user.QueryUserOrgRelationDTO;
import jnpf.permission.dto.v2.user.QueryUserBaseRelationDTO;
import jnpf.permission.eum.v2.UserWorkStatusEnums;
import jnpf.permission.vo.store.StoreInfoDetailVO;
import jnpf.permission.vo.v2.organzie.OrganizeBaseInfoVO;
import jnpf.permission.vo.v2.organzie.OrganizeGeneralDetailVO;
import jnpf.permission.vo.v2.position.PositionBaseInfoVO;
import jnpf.permission.vo.v2.user.UserBaseInfoVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.permission.vo.v2.user.UserBaseRelationSimpleVO;
import jnpf.util.StringUtil;
import jnpf.util.UserProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component
@Slf4j
public class OrganizationHelper {
@Autowired
private V2OrganizeApi v2OrganizeApi;
@Autowired
private V2PositionApi v2PositionApi;
@Autowired
private V2UserApi v2UserApi;
@Autowired
private StoreApi storeApi;
public List<String> getUserIdsByOrgId(String orgId){
if(StringUtil.isBlank(orgId)){
return Collections.emptyList();
}
ActionResult<List<String>> actionResult = v2UserApi.listUserIdsByOrganizeId(orgId, UserProvider.getUser().getTenantId());
return getDataByResult(actionResult,"v2UserApi.listUserIdsByOrganizeId",orgId)
.orElse(Collections.emptyList());
}
/**
* 根据多个userId查询多个用户信息
* @return
*/
public Map<String, UserBaseRelationSimpleVO> getUserBaseRelationMapByOrgId(String orgId){
if(StringUtil.isBlank(orgId)){
return Collections.emptyMap();
}
QueryUserBaseRelationDTO dto = new QueryUserBaseRelationDTO();
dto.setTenantId(UserProvider.getUser().getTenantId());
dto.setOrgIds(Collections.singletonList(orgId));
return getUserBaseRelationMapByUserIds(dto);
}
public Map<String, UserBaseRelationSimpleVO> getUserBaseRelationMapByUserIds(Collection<String> userIds){
if(CollectionUtil.isEmpty(userIds)){
return Collections.emptyMap();
}
QueryUserBaseRelationDTO dto = new QueryUserBaseRelationDTO();
dto.setTenantId(UserProvider.getUser().getTenantId());
dto.setUserIds(userIds);
return getUserBaseRelationMapByUserIds(dto);
}
public Map<String, UserBaseRelationSimpleVO> getUserBaseRelationMapByUserIds(QueryUserBaseRelationDTO dto){
if(Objects.isNull(dto)){
return Collections.emptyMap();
}
dto.setTenantId(UserProvider.getUser().getTenantId());
ActionResult<List<UserBaseRelationSimpleVO>> actionResult = v2UserApi.queryUserBaseRelationList(dto);
Optional<List<UserBaseRelationSimpleVO>> listOptional = getDataByResult(actionResult,"v2UserApi.queryUserBaseRelationList",dto);
return listOptional.map(l->l.stream()
.filter(item -> StringUtil.isNotBlank(item.getUserId()))
.collect(Collectors.toMap(UserBaseRelationSimpleVO::getUserId, item -> item, (v1, v2) -> v1)))
.orElse(Collections.emptyMap());
}
public Map<String, UserBoundVO> getUserPrimaryBoundBatch(Collection<String> userIds) {
if(CollectionUtil.isEmpty(userIds)){
return Collections.emptyMap();
}
ActionResult<List<UserBoundVO>> actionResult = v2UserApi.getUserPrimaryBoundBatch(new ArrayList<>(userIds),UserProvider.getUser().getTenantId());
Optional<List<UserBoundVO>> listOptional = getDataByResult(actionResult,"v2UserApi.getUserPrimaryBoundBatch",userIds);
return listOptional
.map(l->l.stream()
.collect(Collectors.toMap(UserBoundVO::getId, item -> item, (v1, v2) -> v1)))
.orElse(Collections.emptyMap());
}
public Map<String,UserBoundVO> getAllUserInfoBatch(Collection<String> userIds){
if(CollectionUtil.isEmpty(userIds)){
return Collections.emptyMap();
}
ActionResult<List<UserBoundVO>> actionResult = v2UserApi.getAllUserInfoBatch(new ArrayList<>(userIds),UserProvider.getUser().getTenantId());
Optional<List<UserBoundVO>> listOptional = getDataByResult(actionResult,"v2UserApi.getAllUserInfoBatch",userIds);
return listOptional.map(l->l.stream()
.collect(Collectors.toMap(UserBoundVO::getId,u->u)))
.orElse(Collections.emptyMap());
}
public Map<String,List<UserBoundVO>> buildUserBoundVOs(Collection<String> orgIds,
boolean hasChild,
List<UserWorkStatusEnums> notUserWorkStatusEnumsList,
String tenantId) {
if(CollectionUtil.isEmpty(orgIds)){
return Collections.emptyMap();
}
ActionResult<List<UserBoundVO>> actionResult = v2UserApi.listTargetOrganizesOrHaveChild(new ArrayList<>(orgIds),hasChild,notUserWorkStatusEnumsList,tenantId);
Optional<List<UserBoundVO>> listOptional = getDataByResult(actionResult,"v2UserApi.listTargetOrganizesOrHaveChild",orgIds,hasChild,notUserWorkStatusEnumsList,tenantId);
return listOptional.map(l->l.stream()
.collect(Collectors.groupingBy(UserBoundVO::getOrganizeId)))
.orElse(Collections.emptyMap());
}
public Map<String, PositionBaseInfoVO> buildPositionBaseInfos(Collection<String> positionIds) {
if(CollectionUtil.isEmpty(positionIds)){
return Collections.emptyMap();
}
String tenantId = UserProvider.getUser().getTenantId();
ActionResult<List<PositionBaseInfoVO>> actionResult = v2PositionApi.listPositionBaseInfoByIds(new ArrayList<>(positionIds), tenantId);
Optional<List<PositionBaseInfoVO>> positionBaseInfoVOS = getDataByResult(actionResult,"v2PositionApi.listPositionBaseInfoByIds",positionIds);
return positionBaseInfoVOS.map(l->l.stream()
.collect(Collectors.toMap(PositionBaseInfoVO::getId, item -> item, (v1, v2) -> v1)))
.orElse(Collections.emptyMap());
}
public Map<String, OrganizeGeneralDetailVO> buildOrganizeGenerals(Collection<String> orgIds) {
if(CollectionUtil.isEmpty(orgIds)){
return Collections.emptyMap();
}
ActionResult<List<OrganizeGeneralDetailVO>> listActionResult = v2OrganizeApi.organizesByOrganizeIds(new ArrayList<>(orgIds));
Optional<List<OrganizeGeneralDetailVO>> listOptional = getDataByResult(listActionResult,"v2OrganizeApi.organizesByOrganizeIds",orgIds);
return listOptional.map(l->l.stream()
.collect(Collectors.toMap(OrganizeGeneralDetailVO::getId, item -> item, (v1, v2) -> v1)))
.orElse(Collections.emptyMap());
}
public Map<String, List<StoreInfoDetailVO>> buildStoreInfoDetailsByFranchiseeId(String ...franchiseeId) {
ActionResult<List<StoreInfoDetailVO>> listActionResult = storeApi.listInfoDetailsByFranchiseeIds(List.of(franchiseeId));
Optional<List<StoreInfoDetailVO>> listOptional = getDataByResult(listActionResult,"storeApi.listInfoDetailsByFranchiseeIds", (Object) franchiseeId);
return listOptional.map(storeInfoDetailVOS -> storeInfoDetailVOS.stream()
.collect(Collectors.groupingBy(StoreInfoDetailVO::getFranchiseeId)))
.orElse(Collections.emptyMap());
}
public Map<String,List<String>> buildUserIdsByOrgIds(Collection<String> orgIds,boolean orgIdsEmptyQueryAll,String storeTag){
if(CollectionUtil.isEmpty(orgIds) && !orgIdsEmptyQueryAll){
return Collections.emptyMap();
}
QueryUserOrgRelationDTO queryUserOrgRelationDTO = new QueryUserOrgRelationDTO(orgIdsEmptyQueryAll,orgIds,UserProvider.getUser().getTenantId(),storeTag);
ActionResult<Map<String, List<String>>> actionResult = v2UserApi.listUserIdsByQueryDTO(queryUserOrgRelationDTO);
return getDataByResult(actionResult,"v2UserApi.listUserIdsByQueryDTO",queryUserOrgRelationDTO)
.orElse(Collections.emptyMap());
}
/**
* 根据用户id获取组织id。key为userId,value为组织id
* @param userIds
* @return
*/
public Map<String,String> buildOrgIdsByUserIds(Collection<String> userIds,String tenantId){
if(CollectionUtil.isEmpty(userIds)){
return Collections.emptyMap();
}
ActionResult<Map<String, String>> actionResult = v2UserApi.listOrganizeIdsByUserIds(userIds,tenantId);
return getDataByResult(actionResult,"v2UserApi.listOrganizeIdsByUserIds",userIds)
.orElse(Collections.emptyMap());
}
/**
* 根据用户id获取用户基础信息用户id名称组织id
* @param userIds
* @return
*/
public Map<String, UserBaseInfoVO> buildUserBaseInfoByUserIds(Collection<String> userIds, String tenantId){
if(CollectionUtil.isEmpty(userIds)){
return Collections.emptyMap();
}
ActionResult<List<UserBaseInfoVO>> actionResult = v2UserApi.listUserBaseInfoByUserIds(userIds,tenantId);
return getDataByResult(actionResult,"v2UserApi.listUserBaseInfoByUserIds",userIds,tenantId)
.orElse(Collections.emptyList())
.stream()
.collect(Collectors.toMap(UserBaseInfoVO::getUserId, item -> item));
}
public Map<String, OrganizeBaseInfoVO> buildBaseOrganizeVO(Collection<String> orgIds,String tenantId) {
if(CollectionUtil.isEmpty(orgIds)){
return Collections.emptyMap();
}
ActionResult<List<OrganizeBaseInfoVO>> listActionResult = v2OrganizeApi.baseOrganizesByOrganizeIds(orgIds,tenantId);
Optional<List<OrganizeBaseInfoVO>> listOptional = getDataByResult(listActionResult,"v2OrganizeApi.baseOrganizesByOrganizeIds",orgIds);
return listOptional.map(l->l.stream()
.collect(Collectors.toMap(OrganizeBaseInfoVO::getId, item -> item)))
.orElse(Collections.emptyMap());
}
public Map<String, OrganizeBaseInfoVO> buildBaseOrganizeVO(Collection<String> orgIds,String storeTag,String tenantId) {
if(CollectionUtil.isEmpty(orgIds) && StringUtil.isBlank(storeTag)){
return Collections.emptyMap();
}
BaseOrganizeQueryDTO dto = new BaseOrganizeQueryDTO();
dto.setOrganizeIds(orgIds);
dto.setStoreTag(storeTag);
dto.setTenantId(tenantId);
dto.setQueryParentName(true);
ActionResult<List<OrganizeBaseInfoVO>> listActionResult = v2OrganizeApi.baseOrganizesByQueryDTO(dto);
Optional<List<OrganizeBaseInfoVO>> listOptional = getDataByResult(listActionResult,"v2OrganizeApi.baseOrganizesByOrganizeIds",orgIds);
return listOptional.map(l->l.stream()
.collect(Collectors.toMap(OrganizeBaseInfoVO::getId, item -> item)))
.orElse(Collections.emptyMap());
}
private <T>Optional<T> getDataByResult(ActionResult<T> actionResult,String url,Object...params) {
if(Objects.isNull(actionResult)){
log.error("{} return null.params:{}",url,params);
return Optional.empty();
}
if(actionResult.getCode() != 200){
log.error("{} return code is not 200.actionResult:{},params:{}",url,actionResult,params);
return Optional.empty();
}
return Optional.of(actionResult.getData());
}
}

View File

@@ -0,0 +1,14 @@
package jnpf.certificate.mapper;
import jnpf.certificate.model.CertificateAppReminderRecord;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* App端证照提醒Mapper。
*/
public interface CertificateAppReminderMapper {
}

View File

@@ -0,0 +1,40 @@
package jnpf.certificate.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fantaibao.permission.annotation.DataScope;
import com.fantaibao.permission.enums.FilterWhereTypeEnum;
import jnpf.model.certificate.req.app.CertificateAppEmployeeRiskQueryReq;
import jnpf.model.certificate.req.app.CertificateAppRiskChartReq;
import jnpf.model.certificate.req.app.CertificateAppStoreRiskQueryReq;
import jnpf.model.certificate.vo.app.CertificateAppEmployeeRiskVO;
import jnpf.model.certificate.vo.app.CertificateAppStatusCountVO;
import jnpf.model.certificate.vo.app.CertificateAppStoreRiskItemVO;
import jnpf.model.certificate.vo.app.CertificateAppStoreRiskVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
public interface CertificateAppRiskMapper {
@DataScope(tableField = "F_SubjectId",tableAlias = "ci")
List<CertificateAppStatusCountVO> queryHealthRiskStatusCount(@Param("req") CertificateAppRiskChartReq req);
@DataScope(tableFieldOrg = "F_SubjectId",tableAlias = "ci",type = FilterWhereTypeEnum.ORGANIZATION_FILTER)
List<CertificateAppStatusCountVO> queryStoreRiskStatusCount(@Param("req") CertificateAppRiskChartReq req);
@DataScope(tableField = "F_SubjectId",tableAlias = "ci")
Page<CertificateAppEmployeeRiskVO> queryEmployeeRiskPageByUserIds(@Param("page") Page<CertificateAppEmployeeRiskVO> page,
@Param("userIds") Collection<String> userIds);
@DataScope(tableFieldOrg = "F_SubjectId",tableAlias = "ci",type = FilterWhereTypeEnum.ORGANIZATION_FILTER)
Page<CertificateAppStoreRiskVO> queryStoreRiskPage(@Param("page") Page<CertificateAppStoreRiskVO> page,
@Param("req") CertificateAppStoreRiskQueryReq req);
List<CertificateAppStoreRiskItemVO> queryStoreRiskDetailList(@Param("subjectIds") Collection<String> subjectIds);
@DataScope(tableField = "F_SubjectId",tableAlias = "ci")
Long queryEmployeeRiskTotalCount(@Param("userIds") Collection<String> userIds);
@DataScope(tableFieldOrg = "F_SubjectId",tableAlias = "ci",type = FilterWhereTypeEnum.ORGANIZATION_FILTER)
Long queryOrganizationRiskTotalCount(@Param("orgIds") Collection<String> orgIds);
}

View File

@@ -0,0 +1,11 @@
package jnpf.certificate.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.certificate.po.CertificateBusinessLicenseExtEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 营业执照扩展Mapper。
*/
public interface CertificateBusinessLicenseExtMapper extends SuperMapper<CertificateBusinessLicenseExtEntity> {
}

View File

@@ -0,0 +1,11 @@
package jnpf.certificate.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.certificate.po.CertificateHygieneLicenseExtEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 食品许可证扩展Mapper。
*/
public interface CertificateHygieneLicenseExtMapper extends SuperMapper<CertificateHygieneLicenseExtEntity> {
}

View File

@@ -0,0 +1,11 @@
package jnpf.certificate.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.certificate.po.CertificateInstanceItemEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 证照实例明细Mapper。
*/
public interface CertificateInstanceItemMapper extends SuperMapper<CertificateInstanceItemEntity> {
}

View File

@@ -0,0 +1,115 @@
package jnpf.certificate.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fantaibao.permission.annotation.DataScope;
import com.fantaibao.permission.enums.FilterWhereTypeEnum;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.certificate.dto.EmployeeOrganizeDTO;
import jnpf.model.certificate.dto.HealthCertificateStatusDTO;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.certificate.req.CertificateHealthManageQueryReq;
import jnpf.model.certificate.req.CertificateStoreManageQueryReq;
import jnpf.model.certificate.vo.CertificateHealthDashboardStatusStatVO;
import jnpf.model.certificate.vo.CertificateHealthManageVO;
import jnpf.model.certificate.vo.CertificateStoreCustomStatusTableVO;
import jnpf.model.certificate.vo.CertificateStoreManageVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
/**
* 证照实例Mapper。
*/
public interface CertificateInstanceMapper extends SuperMapper<CertificateInstanceEntity> {
/**
* 健康证管理分页查询。
*
* @param page 分页参数
* @param req 查询参数
* @param nearExpireDays 临期天数阈值
* @return 分页结果
*/
@DataScope(tableAlias = "fci",tableField = "F_SubjectId")
Page<CertificateHealthManageVO> queryHealthManagePage(@Param("page") Page<CertificateHealthManageVO> page,
@Param("req") CertificateHealthManageQueryReq req,
@Param("nearExpireDays") Integer nearExpireDays);
@DataScope(tableAlias = "fci",tableField = "F_SubjectId")
List<HealthCertificateStatusDTO> queryHealthCertificateStatus(@Param("userIds") Collection<String> userIds);
/**
* 非健康证看板按状态直接聚合统计(不按组织分组)
*
* @param certificateType 证照类型
* @param templateId 模板ID仅门店自定义证照场景
* @param storeIds 门店IDs筛选
* @param storeType 门店类型筛选
* @return 状态聚合结果
*/
@DataScope(tableAlias = "fci", type = FilterWhereTypeEnum.STORE_FILTER, tableFieldOrg = "F_SubjectId")
List<CertificateHealthDashboardStatusStatVO> queryStoreDashboardStatusStats(@Param("certificateType") String certificateType,
@Param("templateId") String templateId,
@Param("storeIds") List<String> storeIds,
@Param("storeType") String storeType);
/**
* 非健康证看板表格分页查询(按门店聚合)。
*
* @param page 分页参数
* @param certificateType 证照类型
* @param templateId 模板ID仅门店自定义证照场景
* @param storeIds 门店IDs筛选
* @param storeType 门店类型筛选
* @return 门店表格分页数据
*/
@DataScope(tableAlias = "fci", type = FilterWhereTypeEnum.ORGANIZATION_FILTER, tableFieldOrg = "F_SubjectId")
Page<CertificateStoreCustomStatusTableVO> queryStoreDashboardTablePage(@Param("page") Page<CertificateStoreCustomStatusTableVO> page,
@Param("certificateType") String certificateType,
@Param("templateId") String templateId,
@Param("storeIds") List<String> storeIds,
@Param("storeType") String storeType);
/**
* 批量初始化员工健康证实例(仅插入不存在的数据)。
*
* @param entities 员工用户ID集合
* @return 插入条数
*/
int batchInitHealthCertificate(@Param("entities") Collection<CertificateInstanceEntity> entities);
/**
* 根据id查询证照实例
* @param id
* @return
*/
CertificateInstanceEntity selectInstanceById(@Param("id") String id);
/**
* 根据多个subjectType和多个status查询证照实例
* @param subjectTypes
* @param statuses
* @return
*/
@DataScope(tableAlias = "fci",tableField = "F_SubjectId",type = FilterWhereTypeEnum.USER_FILTER)
List<CertificateInstanceEntity> selectBySubjectTypesAndStatusesFilterUser(@Param("subjectTypes") Collection<Integer> subjectTypes,
@Param("statuses") Collection<Integer> statuses,
@Param("userIds") Collection<String> userIds);
/**
* 根据多个subjectType和多个status查询证照实例
* @param subjectTypes
* @param statuses
* @return
*/
@DataScope(tableAlias = "fci",tableFieldOrg = "F_SubjectId",type = FilterWhereTypeEnum.ORGANIZATION_FILTER)
List<CertificateInstanceEntity> selectBySubjectTypesAndStatusesFilterOrganization(@Param("subjectTypes") Collection<Integer> subjectTypes,
@Param("statuses") Collection<Integer> statuses,
@Param("orgId") String orgId);
@DataScope(tableAlias = "fci",tableFieldOrg = "F_SubjectId",type = FilterWhereTypeEnum.ORGANIZATION_FILTER)
Page<CertificateStoreManageVO> selectCertificateStoreManageVOPage(@Param("page") Page<CertificateStoreManageVO> page,
@Param("queryReq")CertificateStoreManageQueryReq queryReq);
}

View File

@@ -0,0 +1,57 @@
package jnpf.certificate.model;
import lombok.Data;
import java.util.Date;
/**
* App证照提醒查询结果模型。
*/
@Data
public class CertificateAppReminderRecord {
/**
* 证照实例ID。
*/
private String certificateInstanceId;
/**
* 证照类型。
*/
private String certificateType;
/**
* 主体类型1-员工2-组织3-门店。
*/
private Integer subjectType;
/**
* 主体ID。
*/
private String subjectId;
/**
* 证照状态1-缺失2-过期3-临期4-正常。
*/
private Integer status;
/**
* 到期日期。
*/
private Date expireDate;
/**
* 接收提醒的用户ID。
*/
private String receiverUserId;
/**
* 主体名称(组织/门店名称)。
*/
private String subjectName;
/**
* 组织权限匹配ID。
*/
private String orgPermissionId;
}

View File

@@ -0,0 +1,24 @@
package jnpf.certificate.service;
import jnpf.model.certificate.req.app.CertificateAppBatchRemindReq;
import jnpf.model.certificate.req.app.CertificateAppSingleRemindReq;
/**
* App端证照提醒服务。
*/
public interface CertificateAppReminderService {
/**
* 一键提醒。
*
* @param req 提醒参数
*/
void batchRemind(CertificateAppBatchRemindReq req);
/**
* 单个提醒。
*
* @param req 提醒参数
*/
void singleRemind(CertificateAppSingleRemindReq req);
}

View File

@@ -0,0 +1,47 @@
package jnpf.certificate.service;
import com.github.pagehelper.PageInfo;
import jnpf.model.certificate.req.app.CertificateAppEmployeeRiskQueryReq;
import jnpf.model.certificate.req.app.CertificateAppRiskChartReq;
import jnpf.model.certificate.req.app.CertificateAppStoreRiskQueryReq;
import jnpf.model.certificate.vo.app.CertificateAppEmployeeRiskVO;
import jnpf.model.certificate.vo.app.CertificateAppRiskChartVO;
import jnpf.model.certificate.vo.app.CertificateAppRiskReminderCountVO;
import jnpf.model.certificate.vo.app.CertificateAppStoreRiskVO;
/**
* App 端证照风险服务。
*/
public interface CertificateAppRiskService {
/**
* 查询证照风险图表统计。
*
* @param req 查询参数(组织门店、证照类型)
* @return 图表统计结果
*/
CertificateAppRiskChartVO queryChart(CertificateAppRiskChartReq req);
/**
* 分页查询员工证照风险列表(缺失、临期、过期)。
*
* @param req 查询参数(组织门店、分页)
* @return 分页结果
*/
PageInfo<CertificateAppEmployeeRiskVO> queryEmployeePage(CertificateAppEmployeeRiskQueryReq req);
/**
* 分页查询门店证照风险列表(缺失、临期、过期)。
*
* @param req 查询参数(组织门店、证照类型、状态、分页)
* @return 分页结果
*/
PageInfo<CertificateAppStoreRiskVO> queryStorePage(CertificateAppStoreRiskQueryReq req);
/**
* 查询风险提醒总数量(员工+组织)。
*
* @return 风险提醒数量统计
*/
CertificateAppRiskReminderCountVO queryRiskReminderCount(String orgId);
}

View File

@@ -0,0 +1,181 @@
package jnpf.certificate.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.certificate.req.CertificateInstanceAddReq;
import jnpf.model.certificate.req.CertificateHealthManageQueryReq;
import jnpf.model.certificate.req.CertificateInstanceQueryReq;
import jnpf.model.certificate.req.CertificateStoreManageQueryReq;
import jnpf.model.certificate.req.CertificateStoreDashboardReq;
import jnpf.model.certificate.req.CertificateSyncHealthReq;
import jnpf.model.certificate.req.CertificateInstanceUpdateReq;
import jnpf.model.certificate.vo.CertificateHealthManageVO;
import jnpf.model.certificate.vo.CertificateInstanceVO;
import jnpf.model.certificate.vo.CertificateTypeOptionVO;
import jnpf.model.certificate.vo.CertificateStoreManageVO;
import jnpf.model.certificate.vo.CertificateStoreDashboardVO;
import jnpf.model.certificate.vo.CertificateStoreCustomStatusTableVO;
import jnpf.model.certificate.vo.app.HealthCertificateDetailVO;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 证照实例服务。
*/
public interface CertificateInstanceService extends IService<CertificateInstanceEntity> {
/**
* 新增证照实例。
*
* @param req 请求参数
*/
void add(CertificateInstanceAddReq req);
/**
* 更新证照实例。
*
* @param req 请求参数
*/
void update(CertificateInstanceUpdateReq req);
/**
* 按ID查询详情。
*
* @param id 实例ID
* @return 详情
*/
CertificateInstanceVO queryInfo(String id);
/**
* 分页查询列表。
*
* @param req 查询参数
* @return 分页结果
*/
PageInfo<CertificateInstanceVO> queryPage(CertificateInstanceQueryReq req);
/**
* 健康证管理分页查询。
*
* @param req 查询参数
* @return 分页结果
*/
PageInfo<CertificateHealthManageVO> queryHealthPage(CertificateHealthManageQueryReq req);
/**
* 门店证照分页查询。
*
* @param req 查询参数
* @return 分页结果
*/
PageInfo<CertificateStoreManageVO> queryStorePage(CertificateStoreManageQueryReq req);
/**
* 逻辑删除。
*
* @param id 实例ID
*/
void delete(String id);
/**
* 保存用户的健康证信息
* @param userId
* @param healthCertificate
* @param startHealthDate
* @param endHealthDate
*/
void saveHealthCertificate(String userId, String healthCertificate, String startHealthDate, String endHealthDate);
/**
* 删除员工健康证
* @param userId
*/
void deleteHealthCertificate(String userId);
/**
* 根据用户id查询健康证
* @param userId
* @return
*/
Optional<HealthCertificateDetailVO> getHealthCertificateDetail(String userId);
/**
* 根据用户id查询健康证
* @param userIds
* @return
*/
List<HealthCertificateDetailVO> getHealthCertificateDetails(Collection<String> userIds);
/**
* 根据用户id查询健康证
* @param userIds
* @return
*/
default Map<String, HealthCertificateDetailVO> getHealthCertificateDetailMap(Collection<String> userIds){
return getHealthCertificateDetails(userIds)
.stream()
.collect(Collectors.toMap(HealthCertificateDetailVO::getUserId, v -> v));
}
/**
* 根据状态,证照类型 查询用户id列表
* @param status
* @return
*/
List<String> getUserIdsByStatus(Integer status,String certificateType);
/**
* 查询健康证用户id列表
* @param status
* @return
*/
default List<String> getHealthCertificateUserIdsByStatus(Integer status){
return getUserIdsByStatus(status, CertificateTypeEnum.HEALTH_CERTIFICATE.getType());
}
/**
* 门店自定义证照看板统计。
*
* @param req 查询参数
* @return 看板统计结果
*/
CertificateStoreDashboardVO storeCertificateDashboard(CertificateStoreDashboardReq req);
/**
* 门店证照看板表格分页查询。
*
* @param req 查询参数
* @return 分页结果
*/
PageInfo<CertificateStoreCustomStatusTableVO> storeCertificateDashboardTablePage(CertificateStoreDashboardReq req);
/**
* 查询证照类型下拉选项。
*
* @return 证照类型选项
*/
List<CertificateTypeOptionVO> queryCertificateTypeList();
/**
* 批量初始化员工健康证实例(缺失状态)。
*
* @param userIds 员工用户ID集合
* @return 初始化条数
*/
int batchInitHealthCertificate(Collection<String> userIds);
/**
* 初始化员工健康证实例(缺失状态)。
*
* @param userId 员工用户ID
* @return 是否成功
*/
default boolean initHealthCertificate(String userId){
return batchInitHealthCertificate(List.of(userId)) > 0;
}
}

View File

@@ -0,0 +1,50 @@
package jnpf.certificate.service;
import jnpf.model.certificate.vo.CertificateOrganizeBusinessLicenseVO;
import java.util.Collection;
import java.util.List;
/**
* 组织营业执照开放接口服务。
*/
public interface CertificateManageApiService {
/**
* 根据组织ID查询营业执照信息。
*
* @param organizeId 组织ID
* @return 营业执照信息
*/
CertificateOrganizeBusinessLicenseVO queryBusinessLicense(String organizeId);
/**
* 根据组织ID列表批量查询营业执照信息。
*
* @param organizeIds 组织ID列表
* @return 营业执照信息列表
*/
List<CertificateOrganizeBusinessLicenseVO> queryBusinessLicenseBatch(Collection<String> organizeIds);
/**
* 保存营业执照信息。
*
* @param req 营业执照参数
*/
void saveBusinessLicense(CertificateOrganizeBusinessLicenseVO req);
/**
* 初始化门店默认缺失证照(营业执照、食品经营许可证)。
*
* @param storeId 门店ID
*/
void initStoreDefaultCertificates(String storeId);
/**
* 根据组织ID删除该主体下全部证照信息。
*
* @param organizeId 组织ID
* @param loginUserId 当前登录用户ID
*/
void deleteBusinessLicense(String organizeId, String loginUserId);
}

View File

@@ -0,0 +1,50 @@
package jnpf.certificate.service;
import jnpf.model.certificate.req.app.CertificateAppBusinessLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHealthCertificateUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHygieneLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppStoreCustomUpdateReq;
import jnpf.model.certificate.vo.app.CertificateAppCertificateDetailVO;
/**
* 端证照管理服务。
*/
public interface CertificateManageService {
/**
* 根据证照实例ID查询详情。
*
* @param certificateInstanceId 证照实例ID
* @return 证照详情
*/
CertificateAppCertificateDetailVO queryInfo(String certificateInstanceId);
/**
* 更新健康证。
*
* @param req 更新参数
*/
void updateHealthCertificate(CertificateAppHealthCertificateUpdateReq req);
/**
* 更新营业执照。
*
* @param req 更新参数
*/
void updateBusinessLicense(CertificateAppBusinessLicenseUpdateReq req);
/**
* 更新食品经营许可证。
*
* @param req 更新参数
*/
void updateHygieneLicense(CertificateAppHygieneLicenseUpdateReq req);
/**
* 更新门店自定义证照。
*
* @param req 更新参数
*/
void updateStoreCustomCertificate(CertificateAppStoreCustomUpdateReq req);
}

View File

@@ -0,0 +1,46 @@
package jnpf.certificate.service;
import jnpf.base.ActionResult;
import jnpf.exception.HandleException;
import jnpf.model.certificate.req.CertificateStoreSaveReq;
import jnpf.model.certificate.vo.CertificateStoreAndCertificatesVO;
import jnpf.model.certificate.vo.CertificateStoreTabVO;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
/**
* 门店证照服务。
*/
public interface CertificateStoreService {
/**
* 保存门店及证照数据。
*
* @param req 保存参数
*/
String saveStoreAndCertificates(CertificateStoreSaveReq req);
/**
* 删除门店。
*
* @param id 门店ID
* @return 是否成功
*/
boolean deleteStore(@PathVariable("id") String id) throws HandleException;
/**
* 根据门店ID查询门店及门店证照详情。
*
* @param storeId 门店ID
* @return 门店及门店证照详情
*/
CertificateStoreAndCertificatesVO getStoreAndCertificates(String storeId);
/**
* 查询门店证照Tab列表。
*
* @return 门店证照Tab列表
*/
List<CertificateStoreTabVO> queryStoreCertificateTabList();
}

View File

@@ -0,0 +1,628 @@
package jnpf.certificate.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import jnpf.authority.utils.PermissionsApplicableEnums;
import jnpf.authority.utils.PermissionsApplicableObject;
import jnpf.authority.utils.PermissionsUtils;
import jnpf.base.ActionResult;
import jnpf.certificate.helper.NoticeHelper;
import jnpf.certificate.helper.OrganizationHelper;
import jnpf.certificate.mapper.CertificateInstanceMapper;
import jnpf.certificate.model.CertificateAppReminderRecord;
import jnpf.certificate.service.CertificateAppReminderService;
import jnpf.certificate.util.SubjectNameUtils;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.certificate.req.app.CertificateAppBatchRemindReq;
import jnpf.model.certificate.req.app.CertificateAppSingleRemindReq;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.permission.V2UserApi;
import jnpf.permission.vo.v2.organzie.OrganizeBaseInfoVO;
import jnpf.permission.vo.v2.organzie.OrganizeGeneralDetailVO;
import jnpf.permission.vo.v2.user.UserBoundInfoVO;
import jnpf.storecertificatephoto.helper.StoreCertificatePhotoHelper;
import jnpf.util.ServiceException;
import jnpf.util.StringUtil;
import jnpf.util.UserProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.MutableTriple;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import static jnpf.certificate.helper.NoticeHelper.URL_CERTIFICATE;
/**
* App端证照提醒服务实现。
*/
@Service
@Slf4j
public class CertificateAppReminderServiceImpl implements CertificateAppReminderService {
private static final int STATUS_MISSING = 1;
private static final int STATUS_EXPIRED = 2;
private static final int STATUS_NEAR_EXPIRE = 3;
private static final int STATUS_NORMAL = 4;
private static final int SUBJECT_TYPE_EMPLOYEE = 1;
private static final int SUBJECT_TYPE_ORGANIZATION = 2;
private static final int SUBJECT_TYPE_STORE = 3;
@Autowired
private CertificateInstanceMapper certificateInstanceMapper;
@Autowired
private V2UserApi v2UserApi;
@Autowired
private OrganizationHelper organizationHelper;
@Autowired
private PermissionsUtils permissionsUtils;
@Autowired
private NoticeHelper noticeHelper;
@Autowired
private StoreCertificatePhotoHelper storeCertificatePhotoHelper;
@Autowired
private ThreadPoolTaskExecutor commonExecutor;
/**
* 一键提醒。
* 健康证提醒员工本人,非健康证提醒组织负责人。
*/
@Override
public void batchRemind(CertificateAppBatchRemindReq req) {
log.error("batchRemind req:{}",req);
ServiceException.notNull(req, "请求参数不能为空");
Integer subjectType = req.getSubjectType();
ServiceException.isTrue(subjectType != null && subjectType >= SUBJECT_TYPE_EMPLOYEE && subjectType <= SUBJECT_TYPE_STORE,
"主体类型不合法");
// 构建查询的subjectType列表subjectType=2时同时查3
List<Integer> subjectTypes = new ArrayList<>();
subjectTypes.add(subjectType);
if (Integer.valueOf(SUBJECT_TYPE_ORGANIZATION).equals(subjectType)) {
subjectTypes.add(SUBJECT_TYPE_STORE);
}
// 查询非正常状态的证照实例
List<Integer> statuses = Arrays.asList(STATUS_MISSING, STATUS_EXPIRED, STATUS_NEAR_EXPIRE);
List<CertificateInstanceEntity> records= null;
List<String> userIds = buildQueryUserIds(req.getOrgId());
if(Objects.nonNull(userIds) && userIds.isEmpty()){//没查到数据,返回
return;
}
if(req.getSubjectType() == SUBJECT_TYPE_EMPLOYEE){
records = certificateInstanceMapper.selectBySubjectTypesAndStatusesFilterUser(subjectTypes, statuses,userIds);
}else{
records = certificateInstanceMapper.selectBySubjectTypesAndStatusesFilterOrganization(subjectTypes, statuses,req.getOrgId());
}
if (CollUtil.isEmpty(records)) {
return;
}
String tenantId = UserProvider.getUser().getTenantId();
String remindUserName = StrUtil.blankToDefault(StrUtil.trim(UserProvider.getUser().getUserName()), "系统");
// 按是否健康证分组,批量解析接收人
Map<Boolean, List<CertificateInstanceEntity>> grouped = records.stream()
.collect(Collectors.partitioningBy(r -> isHealthCertificate(r.getCertificateType())));
// 健康证:接收人就是员工本人(subjectId)
Map<String, String> healthReceiverMap = resolveHealthReceivers(grouped.getOrDefault(true, Collections.emptyList()));
// 非健康证:接收人是组织负责人
MutableTriple<Map<String,String>,Map<String,String>,Map<String,String>>
orgReceiversAndOrgNamesAndTemplateNames = buildOrgReceiversAndOrgNamesAndTemplateNames(grouped.getOrDefault(false, Collections.emptyList()));
Map<String,String> orgReceivers = orgReceiversAndOrgNamesAndTemplateNames.getLeft();
Map<String,String> orgNames = orgReceiversAndOrgNamesAndTemplateNames.getMiddle();
Map<String,String> templateNames = orgReceiversAndOrgNamesAndTemplateNames.getRight();
List<CertificateInstanceEntity> finalRecords = records;
commonExecutor.execute(() -> sendNotices(finalRecords, healthReceiverMap, orgReceivers, orgNames, templateNames, tenantId, remindUserName));
}
private List<String> buildQueryUserIds(String orgId) {
if(StringUtil.isBlank(orgId)){
return null;
}
Map<String,List<String>> userIdsByOrgIds = organizationHelper.buildUserIdsByOrgIds(List.of(orgId),false,null);
if(CollUtil.isEmpty(userIdsByOrgIds)){
return Collections.emptyList();
}
List<String> userIds = userIdsByOrgIds.get(orgId);
if(CollectionUtil.isEmpty(userIds)){
return Collections.emptyList();
}
return userIds;
}
private void sendNotices(List<CertificateInstanceEntity> records,
Map<String, String> healthReceiverMap,
Map<String,String> orgReceivers,
Map<String,String> orgNames,
Map<String,String> templateNames,
String tenantId,
String remindUserName){
for (CertificateInstanceEntity record : records) {
String receiverUserId = isHealthCertificate(record.getCertificateType())
? healthReceiverMap.get(StrUtil.trim(record.getSubjectId()))
: orgReceivers.get(StrUtil.trim(record.getSubjectId()));
if (StrUtil.isBlank(receiverUserId)) {
continue;
}
String subjectId = record.getSubjectId();
String orgName = orgNames.get(subjectId);
String templateName = null;
String templateId = record.getTemplateId();
if(StringUtil.isNotBlank(templateId)){
templateName = templateNames.get(templateId);
}
sendNotice(record, receiverUserId, tenantId, remindUserName,templateName,orgName);
}
}
// 批量解析健康证接收人subjectId -> userId
private Map<String, String> resolveHealthReceivers(List<CertificateInstanceEntity> records) {
if (CollUtil.isEmpty(records)) {
return Collections.emptyMap();
}
Set<String> subjectIds = records.stream()
.map(r -> StrUtil.trim(r.getSubjectId()))
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
if (CollUtil.isEmpty(subjectIds)) {
return Collections.emptyMap();
}
// 健康证subjectId就是userId直接映射
Map<String, String> result = new HashMap<>(subjectIds.size());
subjectIds.forEach(id -> result.put(id, id));
return result;
}
// 批量解析非健康证接收人subjectId(组织/门店) -> leaderId
private Map<String, String> resolveOrgReceivers(List<CertificateInstanceEntity> records) {
if (CollUtil.isEmpty(records)) {
return Collections.emptyMap();
}
Set<String> subjectIds = records.stream()
.map(r -> StrUtil.trim(r.getSubjectId()))
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
if (CollUtil.isEmpty(subjectIds)) {
return Collections.emptyMap();
}
Map<String, OrganizeGeneralDetailVO> organizeMap = organizationHelper.buildOrganizeGenerals(subjectIds);
Map<String, String> result = new HashMap<>(subjectIds.size());
for (String subjectId : subjectIds) {
OrganizeGeneralDetailVO org = organizeMap.get(subjectId);
if (org != null && StrUtil.isNotBlank(org.getLeaderId())) {
result.put(subjectId, StrUtil.trim(org.getLeaderId()));
}
}
return result;
}
/**
* 左边是 组织接收人。key为subjectIdvalue为组织负责人id
* 中间是 组织名称key为组织idvalue为组织名称
* 右边是 模板名称key为模板idvalue为模板名称
* @param records
* @return
*/
private MutableTriple<Map<String,String>,Map<String,String>,Map<String,String>>
buildOrgReceiversAndOrgNamesAndTemplateNames(List<CertificateInstanceEntity> records) {
if (CollUtil.isEmpty(records)) {
return MutableTriple.of(Collections.emptyMap(),Collections.emptyMap(),Collections.emptyMap());
}
Set<String> templateIds = new HashSet<>();
Set<String> orgIds = new HashSet<>();
for (CertificateInstanceEntity record:records){
String templateId = record.getTemplateId();
if(StringUtil.isNotBlank(templateId)){
templateIds.add(templateId);
}
orgIds.add(record.getSubjectId());
}
Map<String,String> orgReceivers = new HashMap<>();
Map<String,String> orgNames = new HashMap<>();
Map<String, OrganizeGeneralDetailVO> organizeMap = organizationHelper.buildOrganizeGenerals(orgIds);
Map<String,String> templateNames = storeCertificatePhotoHelper.buildStoreCertificateIdAndNames(templateIds,null);
for (String orgId : orgIds) {
OrganizeGeneralDetailVO org = organizeMap.get(orgId);
if(Objects.isNull(org)){
continue;
}
if (StrUtil.isNotBlank(org.getLeaderId())) {
orgReceivers.put(orgId, StrUtil.trim(org.getLeaderId()));
}
orgNames.put(orgId,org.getName());
}
return MutableTriple.of(orgReceivers,orgNames,templateNames);
}
/**
* 单个提醒。
* 根据证照实例ID查询后按证照类型解析接收人并发送提醒。
*/
@Override
public void singleRemind(CertificateAppSingleRemindReq req) {
ServiceException.notNull(req, "请求参数不能为空");
List<String> certificateInstanceIds = req.getCertificateInstanceIds();
ServiceException.isTrue(CollUtil.isNotEmpty(certificateInstanceIds), "证照实例ID不能为空");
// 查询证照实例
List<CertificateInstanceEntity> certificateInstanceEntities = certificateInstanceMapper.selectBatchIds(certificateInstanceIds);
if(CollUtil.isEmpty(certificateInstanceEntities)){
log.warn("证照实例不存在certificateInstanceId={}", certificateInstanceIds);
return;
}
Set<String> templateIds = new HashSet<>();
Set<String> orgIds = new HashSet<>();
for (CertificateInstanceEntity record:certificateInstanceEntities) {
if (record.getStatus() == null || Integer.valueOf(STATUS_NORMAL).equals(record.getStatus())) {
log.warn("证照状态无需提醒certificateInstanceId={}, status={}", record.getId(), record.getStatus());
continue;
}
String templateId = record.getTemplateId();
Integer subjectType = record.getSubjectType();
String subjectId = record.getSubjectId();
if(StringUtil.isNotBlank(templateId)){
templateIds.add(templateId);
}
if(Objects.nonNull(subjectType) && subjectType != SUBJECT_TYPE_EMPLOYEE){
orgIds.add(subjectId);
}
}
Map<String,String> templateIdAndNames = storeCertificatePhotoHelper.buildStoreCertificateIdAndNames(templateIds,null);
Map<String, OrganizeBaseInfoVO> organizeBaseInfoById = organizationHelper.buildBaseOrganizeVO(orgIds,UserProvider.getUser().getTenantId());
for (CertificateInstanceEntity record:certificateInstanceEntities){
if (record.getStatus() == null || Integer.valueOf(STATUS_NORMAL).equals(record.getStatus())) {
log.warn("证照状态无需提醒certificateInstanceId={}, status={}", record.getId(), record.getStatus());
continue;
}
// 解析接收人
String receiverUserId = resolveSingleReceiverUserId(record,organizeBaseInfoById);
if (StrUtil.isBlank(receiverUserId)) {
log.warn("未找到提醒接收人certificateInstanceId={}, subjectType={}, subjectId={}",
record.getId(), record.getSubjectType(), record.getSubjectId());
continue;
}
String tenantId = UserProvider.getUser().getTenantId();
String remindUserName = StrUtil.blankToDefault(StrUtil.trim(UserProvider.getUser().getUserName()), "系统");
String templateName = templateIdAndNames.get(record.getTemplateId());
String orgName = null;
OrganizeBaseInfoVO organizeBaseInfoVO = organizeBaseInfoById.get(record.getSubjectId());
if(Objects.nonNull(organizeBaseInfoVO)){
orgName = organizeBaseInfoVO.getName();
}
sendNotice(record, receiverUserId, tenantId, remindUserName,templateName,orgName);
}
}
/**
* 解析单条提醒的接收人用户ID。
* 健康证通过V2UserApi#getUsersBound查询员工本人信息。
* 非健康证通过OrganizationHelper#buildOrganizeGenerals查询组织负责人。
*/
private String resolveSingleReceiverUserId(CertificateInstanceEntity record,Map<String, OrganizeBaseInfoVO> organizeBaseInfoById) {
String subjectId = StrUtil.trim(record.getSubjectId());
if (StrUtil.isBlank(subjectId)) {
return null;
}
if (isHealthCertificate(record.getCertificateType())) {
return subjectId;
}
OrganizeBaseInfoVO organize = organizeBaseInfoById.get(subjectId);
if (organize == null) {
log.error("组织信息不存在subjectId={}", subjectId);
return null;
}
return StrUtil.trim(organize.getLeaderId());
}
/**
* 发送单条提醒消息。
*/
private void sendNotice(CertificateInstanceEntity record,
String receiverUserId,
String tenantId,
String remindUserName,
String templateName,
String orgName) {
if (record == null) {
return;
}
if (StrUtil.isBlank(receiverUserId)) {
return;
}
NoticeMessage noticeMessage = buildNoticeMessage(record, remindUserName,templateName,orgName);
if(isHealthCertificate(record.getCertificateType())){
noticeHelper.sendMessage(
Collections.singletonList(receiverUserId),
tenantId,
noticeMessage.getTitle(),
noticeMessage.getContent(),
NoticeHelper.BUTTON_NAME_HANDLER,
buildJumpHealthCertificateUrl(record),
NoticeHelper.MP_ID_CERTIFICATE
);
return;
}
noticeHelper.sendMessage(
Collections.singletonList(receiverUserId),
tenantId,
noticeMessage.getTitle(),
noticeMessage.getContent(),
null,
null,
null
);
}
private String buildJumpHealthCertificateUrl(CertificateInstanceEntity record) {
if(Objects.isNull(record)){
return null;
}
String subjectId = record.getSubjectId();
if(StringUtil.isBlank(subjectId)){
return null;
}
return String.format(URL_CERTIFICATE,record.getId());
}
/**
* 构建提醒文案。
* 文案依据原型:标题“存在风险,请及时处理”,正文按缺失/临期/过期区分。
*/
private NoticeMessage buildNoticeMessage(CertificateInstanceEntity record,
String remindUserName,
String templateName,
String orgName) {
String certificateType = StrUtil.trim(record.getCertificateType());
Integer status = record.getStatus();
String certificateName = resolveCertificateTypeName(certificateType,templateName);
int remainDays = calculateRemainDays(record.getExpireDate());
String subjectName = StrUtil.blankToDefault(orgName,StrUtil.blankToDefault(SubjectNameUtils.getSubjectName(record.getSubjectType()), "该主体"));
if (isHealthCertificate(certificateType)) {
String title = "您的健康证存在风险,请及时处理!";
if (Integer.valueOf(STATUS_NEAR_EXPIRE).equals(status)) {
return new NoticeMessage(title, buildHealthCertificateNearExpireContent(remainDays,remindUserName));
}
if (Integer.valueOf(STATUS_EXPIRED).equals(status)) {
return new NoticeMessage(title, String.format("您的健康证已过期,%s提醒您及时更新健康证。", remindUserName));
}
return new NoticeMessage(title, String.format("您的健康证缺失,%s提醒您及时更新健康证。", remindUserName));
}
String title = subjectName + certificateName + "存在风险,请及时处理!";
if (Integer.valueOf(STATUS_NEAR_EXPIRE).equals(status)) {
return new NoticeMessage(title, buildCertificateNearExpireContent(subjectName,certificateName,remainDays));
}
if (Integer.valueOf(STATUS_EXPIRED).equals(status)) {
return new NoticeMessage(title, String.format("%s%s已过期请及时更新%s。", subjectName, certificateName, certificateName));
}
return new NoticeMessage(title, String.format("%s%s缺失请及时上传%s。", subjectName, certificateName, certificateName));
}
private String buildCertificateNearExpireContent(String subjectName, String certificateName, int remainDays) {
if(remainDays <= 0){
return String.format("%s%s今天过期请及时更新%s。", subjectName, certificateName, certificateName);
}
return String.format("%s%s离到期时间还有%s天请及时更新%s。", subjectName, certificateName, remainDays, certificateName);
}
private String buildHealthCertificateNearExpireContent(int remainDays,String remindUserName) {
if(remainDays <= 0){
return String.format("您的健康证今天过期,%s提醒您请及时更新健康证。", remindUserName);
}
return String.format("您的健康证离到期时间还有%s天%s提醒您请及时更新健康证。", remainDays,remindUserName);
}
/**
* 单条提醒权限校验。
*/
private void validateSinglePermission(CertificateAppReminderRecord record, String userId, String moduleId) {
Integer subjectType = record.getSubjectType();
if (Integer.valueOf(SUBJECT_TYPE_EMPLOYEE).equals(subjectType)) {
PermissionUserScope userScope = resolvePermissionUserScope(userId, moduleId);
if (Boolean.TRUE.equals(userScope.getAllDataPermission())) {
return;
}
ServiceException.isTrue(CollUtil.isNotEmpty(userScope.getUserIds()), "暂无数据权限");
ServiceException.isTrue(userScope.getUserIds().contains(StrUtil.trim(record.getReceiverUserId())), "暂无当前证照数据权限");
return;
}
if (Integer.valueOf(SUBJECT_TYPE_ORGANIZATION).equals(subjectType) || Integer.valueOf(SUBJECT_TYPE_STORE).equals(subjectType)) {
PermissionOrgScope orgScope = resolvePermissionOrgScope(userId, moduleId,UserProvider.getUser().getTenantId());
if (Boolean.TRUE.equals(orgScope.getAllDataPermission())) {
return;
}
String orgPermissionId = StrUtil.trim(record.getOrgPermissionId());
ServiceException.isTrue(StrUtil.isNotBlank(orgPermissionId), "组织数据不存在,无法提醒");
ServiceException.isTrue(CollUtil.isNotEmpty(orgScope.getOrgIds()), "暂无数据权限");
ServiceException.isTrue(orgScope.getOrgIds().contains(orgPermissionId), "暂无当前证照数据权限");
return;
}
throw new ServiceException("主体类型不支持提醒");
}
/**
* 解析员工维度权限范围。
* allDataPermission=true 代表全量权限false 时使用 userIds 过滤。
*/
private PermissionUserScope resolvePermissionUserScope(String userId, String moduleId) {
List<String> permissionUserIds = permissionsUtils.obtainPersonnelUserIdDataPermissions(userId, moduleId);
if (permissionUserIds == null) {
return new PermissionUserScope(true, Collections.emptyList());
}
return new PermissionUserScope(false, normalizeIdList(permissionUserIds));
}
/**
* 解析组织维度权限范围。
* allDataPermission=true 代表全量权限false 时使用 orgIds 过滤。
*/
private PermissionOrgScope resolvePermissionOrgScope(String userId, String moduleId,String tenantId) {
if (Boolean.TRUE.equals(UserProvider.getUser().getIsAdministrator())) {
return new PermissionOrgScope(true, Collections.emptyList());
}
PermissionsApplicableObject applicableObject = permissionsUtils.obtainTheScopeOfUserPermissionsEnums(userId, moduleId,tenantId);
PermissionsApplicableEnums applicableEnums = applicableObject == null ? null : applicableObject.getPermissionsApplicableEnums();
if (PermissionsApplicableEnums.ALL.equals(applicableEnums)) {
return new PermissionOrgScope(true, Collections.emptyList());
}
List<String> orgIds = normalizeIdList(applicableObject == null ? Collections.emptyList() : applicableObject.getOrgIds());
if (CollUtil.isEmpty(orgIds)) {
try {
List<String> fallbackOrgIds = permissionsUtils.obtainPersonnelOrganizationIdDataPermissions(userId);
if (fallbackOrgIds == null) {
return new PermissionOrgScope(true, Collections.emptyList());
}
orgIds = normalizeIdList(fallbackOrgIds);
} catch (Exception e) {
return new PermissionOrgScope(false, Collections.emptyList());
}
}
return new PermissionOrgScope(false, orgIds);
}
/**
* 规整ID集合去空、去重、去首尾空格。
*/
private List<String> normalizeIdList(Collection<String> idList) {
if (CollUtil.isEmpty(idList)) {
return Collections.emptyList();
}
return idList.stream()
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.distinct()
.collect(Collectors.toList());
}
/**
* 是否健康证类型。
*/
private boolean isHealthCertificate(String certificateType) {
return StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.HEALTH_CERTIFICATE.getType());
}
/**
* 证照类型转中文名称。
*/
private String resolveCertificateTypeName(String certificateType,String templateName) {
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.HEALTH_CERTIFICATE.getType())) {
return "健康证";
}
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.BUSINESS_LICENSE.getType())) {
return "营业执照";
}
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.HYGIENE_LICENSE.getType())) {
return "食品经营许可证";
}
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType())) {
return templateName;
}
return "证照";
}
/**
* 计算距离到期的剩余天数。
*/
private int calculateRemainDays(Date expireDate) {
if (expireDate == null) {
return 0;
}
Date today = DateUtil.beginOfDay(DateUtil.date());
Date target = DateUtil.beginOfDay(expireDate);
long days = DateUtil.betweenDay(today, target, false);
return (int) Math.max(days, 0L);
}
/**
* 员工权限范围。
*/
private static class PermissionUserScope {
private final Boolean allDataPermission;
private final List<String> userIds;
private PermissionUserScope(Boolean allDataPermission, List<String> userIds) {
this.allDataPermission = allDataPermission;
this.userIds = userIds;
}
public Boolean getAllDataPermission() {
return allDataPermission;
}
public List<String> getUserIds() {
return userIds;
}
}
/**
* 组织权限范围。
*/
private static class PermissionOrgScope {
private final Boolean allDataPermission;
private final List<String> orgIds;
private PermissionOrgScope(Boolean allDataPermission, List<String> orgIds) {
this.allDataPermission = allDataPermission;
this.orgIds = orgIds;
}
public Boolean getAllDataPermission() {
return allDataPermission;
}
public List<String> getOrgIds() {
return orgIds;
}
}
/**
* 提醒文案对象。
*/
private static class NoticeMessage {
private final String title;
private final String content;
private NoticeMessage(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
}
}

View File

@@ -0,0 +1,514 @@
package jnpf.certificate.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.pagehelper.PageInfo;
import jnpf.authority.utils.PermissionsApplicableEnums;
import jnpf.authority.utils.PermissionsApplicableObject;
import jnpf.authority.utils.PermissionsUtils;
import jnpf.certificate.helper.OrganizationHelper;
import jnpf.certificate.mapper.CertificateAppRiskMapper;
import jnpf.certificate.service.CertificateAppRiskService;
import jnpf.model.certificate.req.app.CertificateAppEmployeeRiskQueryReq;
import jnpf.model.certificate.req.app.CertificateAppRiskChartReq;
import jnpf.model.certificate.req.app.CertificateAppStoreRiskQueryReq;
import jnpf.model.certificate.vo.app.CertificateAppEmployeeRiskVO;
import jnpf.model.certificate.vo.app.CertificateAppRiskChartVO;
import jnpf.model.certificate.vo.app.CertificateAppRiskReminderCountVO;
import jnpf.model.certificate.vo.app.CertificateAppStatusCountVO;
import jnpf.model.certificate.vo.app.CertificateAppStoreRiskItemVO;
import jnpf.model.certificate.vo.app.CertificateAppStoreRiskVO;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.permission.eum.v2.FtbStoreTagEnum;
import jnpf.permission.vo.v2.organzie.OrganizeBaseInfoVO;
import jnpf.permission.vo.v2.organzie.OrganizeGeneralDetailVO;
import jnpf.permission.vo.v2.position.PositionBaseInfoVO;
import jnpf.permission.vo.v2.user.UserBaseRelationSimpleVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.storecertificatephoto.helper.StoreCertificatePhotoHelper;
import jnpf.util.StringUtil;
import jnpf.util.UserProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
* App 端证照风险服务实现。
*/
@Service
public class CertificateAppRiskServiceImpl implements CertificateAppRiskService {
private static final int STATUS_MISSING = 1;
private static final int STATUS_EXPIRED = 2;
private static final int STATUS_NEAR_EXPIRE = 3;
private static final int STATUS_NORMAL = 4;
/**
* 证照风险查询 Mapper。
*/
@Autowired
private CertificateAppRiskMapper certificateAppRiskMapper;
/**
* 数据权限工具。
*/
@Autowired
private PermissionsUtils permissionsUtils;
@Autowired
private OrganizationHelper organizationHelper;
@Autowired
private StoreCertificatePhotoHelper storeCertificatePhotoHelper;
/**
* 查询证照风险图表统计。
* 健康证按用户数据权限统计,非健康证按组织数据权限统计。
*/
@Override
public CertificateAppRiskChartVO queryChart(CertificateAppRiskChartReq req) {
CertificateAppRiskChartReq queryReq = req == null ? new CertificateAppRiskChartReq() : req;
queryReq.setOrgId(StrUtil.trim(queryReq.getOrgId()));
queryReq.setCertificateType(StrUtil.blankToDefault(
StrUtil.trim(queryReq.getCertificateType()),
CertificateTypeEnum.HEALTH_CERTIFICATE.getType()
));
String orgId = queryReq.getOrgId();
boolean healthCertificateQuery = isHealthCertificate(queryReq.getCertificateType());
buildQueryUserIds(queryReq,healthCertificateQuery);
//如果查询组织id但是没有查询到该组织的用户id列表则返回空
if(healthCertificateQuery && StringUtil.isNotBlank(orgId) && CollectionUtil.isEmpty(queryReq.getUserIds())){
return buildChart(null);
}
List<CertificateAppStatusCountVO> statusCountList;
if (healthCertificateQuery) {
statusCountList = certificateAppRiskMapper.queryHealthRiskStatusCount(queryReq);
} else {
statusCountList = certificateAppRiskMapper.queryStoreRiskStatusCount(queryReq);
}
return buildChart(statusCountList);
}
private void buildQueryUserIds(CertificateAppRiskChartReq queryReq,boolean healthCertificateQuery) {
if(!healthCertificateQuery){
return;
}
String queryReqOrgId = queryReq.getOrgId();
if(StringUtil.isBlank(queryReqOrgId)){
return;
}
List<String> userIdsByOrgId = organizationHelper.getUserIdsByOrgId(queryReqOrgId);
queryReq.setUserIds(userIdsByOrgId);
}
/**
* 分页查询员工证照风险数据。
*/
@Override
public PageInfo<CertificateAppEmployeeRiskVO> queryEmployeePage(CertificateAppEmployeeRiskQueryReq req) {
CertificateAppEmployeeRiskQueryReq queryReq = req == null ? new CertificateAppEmployeeRiskQueryReq() : req;
queryReq.setOrgId(StrUtil.trim(queryReq.getOrgId()));
long current = queryReq.getCurrentPage() == null || queryReq.getCurrentPage() <= 0 ? 1L : queryReq.getCurrentPage();
long pageSize = queryReq.getPageSize() == null || queryReq.getPageSize() <= 0 ? 10L : queryReq.getPageSize();
String orgId = queryReq.getOrgId();
Collection<String> queryUserIds;
Map<String, UserBaseRelationSimpleVO> relationMap;
if (StringUtil.isNotBlank(orgId)) {
relationMap = organizationHelper.getUserBaseRelationMapByOrgId(orgId);
queryUserIds = relationMap.keySet();
} else {
relationMap = Collections.emptyMap();
queryUserIds = null;
}
if (StringUtil.isNotBlank(orgId) && CollectionUtil.isEmpty(queryUserIds)) {
return buildEmptyEmployeePage(current, pageSize);
}
Page<CertificateAppEmployeeRiskVO> page = new Page<>(current, pageSize);
Page<CertificateAppEmployeeRiskVO> queryPage = certificateAppRiskMapper.queryEmployeeRiskPageByUserIds(page, queryUserIds);
List<CertificateAppEmployeeRiskVO> records = queryPage.getRecords() == null ? new ArrayList<>() : queryPage.getRecords();
if (CollectionUtil.isEmpty(records)) {
return buildEmptyEmployeePage(current, pageSize);
}
Set<String> pageUserIds = records.stream()
.map(CertificateAppEmployeeRiskVO::getUserId)
.filter(StringUtil::isNotBlank)
.collect(Collectors.toSet());
if (CollectionUtil.isEmpty(pageUserIds)) {
return buildEmptyEmployeePage(current, pageSize);
}
if (StringUtil.isBlank(orgId)) {
relationMap = organizationHelper.getUserBaseRelationMapByUserIds(pageUserIds);
}
for (CertificateAppEmployeeRiskVO item : records){
UserBaseRelationSimpleVO relation = relationMap.get(item.getUserId());
if(Objects.isNull(relation)){
continue;
}
item.setWorkerName(StrUtil.blankToDefault(StrUtil.trim(relation.getUserName()), "-"));
item.setPhone(StrUtil.blankToDefault(StrUtil.trim(relation.getMobilePhone()), "-"));
item.setOrgId(relation.getOrganizeId());
item.setPositionId(relation.getPositionId());
item.setOrgName(relation.getOrganizeName());
item.setPositionName(relation.getPositionName());
item.setPhone(relation.getMobilePhone());
}
PageInfo<CertificateAppEmployeeRiskVO> pageInfo = new PageInfo<>();
pageInfo.setList(records);
pageInfo.setPageNum((int) queryPage.getCurrent());
pageInfo.setPageSize((int) queryPage.getSize());
pageInfo.setTotal(queryPage.getTotal());
return pageInfo;
}
/**
* 分页查询门店证照风险数据。
*/
@Override
public PageInfo<CertificateAppStoreRiskVO> queryStorePage(CertificateAppStoreRiskQueryReq req) {
CertificateAppStoreRiskQueryReq queryReq = req == null ? new CertificateAppStoreRiskQueryReq() : req;
queryReq.setOrgId(StrUtil.trim(queryReq.getOrgId()));
queryReq.setCertificateType(StrUtil.trim(queryReq.getCertificateType()));
long current = queryReq.getCurrentPage() == null || queryReq.getCurrentPage() <= 0 ? 1L : queryReq.getCurrentPage();
long pageSize = queryReq.getPageSize() == null || queryReq.getPageSize() <= 0 ? 10L : queryReq.getPageSize();
PageInfo<CertificateAppStoreRiskVO> emptyPage = buildEmptyStorePage(current, pageSize);
Page<CertificateAppStoreRiskVO> page = new Page<>(current, pageSize);
Page<CertificateAppStoreRiskVO> queryPage = certificateAppRiskMapper.queryStoreRiskPage(page, queryReq);
List<CertificateAppStoreRiskVO> records = queryPage.getRecords() == null ? Collections.emptyList() : queryPage.getRecords();
if (CollectionUtil.isEmpty(records)) {
return emptyPage;
}
Set<String> orgIds = new HashSet<>();
records.forEach(vo -> {
orgIds.add(vo.getSubjectId());
});
List<CertificateAppStoreRiskItemVO> detailList = certificateAppRiskMapper.queryStoreRiskDetailList(orgIds);
Set<String> templateIds = detailList.stream()
.map(CertificateAppStoreRiskItemVO::getTemplateId)
.filter(StringUtil::isNotBlank)
.collect(Collectors.toSet());
Map<String,String> templateIdAndNames = null;
if(CollUtil.isNotEmpty(templateIds)){
templateIdAndNames = storeCertificatePhotoHelper.buildStoreCertificateIdAndNames(templateIds,1);
}else {
templateIdAndNames = new HashMap<>();
}
Map<String, String> finalTemplateIdAndNames = templateIdAndNames;
Map<String, List<CertificateAppStoreRiskItemVO>> detailMap = CollectionUtil.emptyIfNull(detailList).stream()
.peek(item -> item.setCertificateTypeName(resolveCertificateTypeName(item.getCertificateType(),item.getTemplateId(), finalTemplateIdAndNames)))
.filter(item -> StringUtil.isNotBlank(item.getCertificateTypeName()))//过滤掉被禁用的证照
.collect(Collectors.groupingBy(CertificateAppStoreRiskItemVO::getSubjectId, LinkedHashMap::new, Collectors.toList()));
for (CertificateAppStoreRiskVO record : records) {
List<CertificateAppStoreRiskItemVO> certificateList = detailMap.getOrDefault(record.getSubjectId(), Collections.emptyList());
record.setCertificateList(certificateList);
}
Map<String, OrganizeBaseInfoVO> organizeBaseInfoVOMap = organizationHelper.buildBaseOrganizeVO(orgIds,UserProvider.getUser().getTenantId());
fillStoreRiskItem(records,organizeBaseInfoVOMap);
PageInfo<CertificateAppStoreRiskVO> pageInfo = new PageInfo<>();
pageInfo.setList(records);
pageInfo.setPageNum((int) queryPage.getCurrent());
pageInfo.setPageSize((int) queryPage.getSize());
pageInfo.setTotal(queryPage.getTotal());
return pageInfo;
}
/**
* 查询有风险的员工、组织、总数量
*/
@Override
public CertificateAppRiskReminderCountVO queryRiskReminderCount(String orgId) {
List<String> orgIds = new ArrayList<>(1);
Map<String,List<String>> userIdsByOrgId = null;
if(StringUtil.isNotBlank(orgId)){
userIdsByOrgId = organizationHelper.buildUserIdsByOrgIds(List.of(orgId),true,"");
orgIds.add(orgId);
}else{
userIdsByOrgId = organizationHelper.buildUserIdsByOrgIds(null,true,"");
}
Set<String> userIds = userIdsByOrgId.values()
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toSet());
long employeeCount = 0L;
if(!CollUtil.isEmpty(userIds)){
employeeCount = certificateAppRiskMapper.queryEmployeeRiskTotalCount(userIds);
}
Long organizationCount = certificateAppRiskMapper.queryOrganizationRiskTotalCount(orgIds);
if(Objects.isNull(organizationCount)){
organizationCount = 0L;
}
CertificateAppRiskReminderCountVO vo = new CertificateAppRiskReminderCountVO();
vo.setEmployeeCount(employeeCount);
vo.setOrganizationCount(organizationCount);
vo.setTotalCount(employeeCount + organizationCount);
return vo;
}
/**
* 解析员工数据权限范围。
* 通过 allDataPermission 标识是否全量权限userIds 为受限时可访问用户。
*/
private PermissionUserScope resolvePermissionUserScope(String userId, String moduleId) {
List<String> permissionUserIds = permissionsUtils.obtainPersonnelUserIdDataPermissions(userId, moduleId);
if (permissionUserIds == null) {
return new PermissionUserScope(true, Collections.emptyList());
}
return new PermissionUserScope(false, normalizeIdList(permissionUserIds));
}
/**
* 解析组织数据权限范围。
* 通过 allDataPermission 标识是否全量权限orgIds 为受限时可访问组织。
*/
private PermissionOrgScope resolvePermissionOrgScope(String userId, String moduleId,String tenantId) {
if (Boolean.TRUE.equals(UserProvider.getUser().getIsAdministrator())) {
return new PermissionOrgScope(true, Collections.emptyList());
}
PermissionsApplicableObject applicableObject = permissionsUtils.obtainTheScopeOfUserPermissionsEnums(userId, moduleId,tenantId);
PermissionsApplicableEnums applicableEnums = applicableObject == null ? null : applicableObject.getPermissionsApplicableEnums();
if (PermissionsApplicableEnums.ALL.equals(applicableEnums)) {
return new PermissionOrgScope(true, Collections.emptyList());
}
List<String> orgIds = normalizeIdList(applicableObject == null ? Collections.emptyList() : applicableObject.getOrgIds());
if (CollUtil.isEmpty(orgIds)) {
try {
List<String> fallbackOrgIds = permissionsUtils.obtainPersonnelOrganizationIdDataPermissions(userId);
if (fallbackOrgIds == null) {
return new PermissionOrgScope(true, Collections.emptyList());
}
orgIds = normalizeIdList(fallbackOrgIds);
} catch (Exception e) {
return new PermissionOrgScope(false, Collections.emptyList());
}
}
return new PermissionOrgScope(false, orgIds);
}
/**
* 规整 ID 列表:去空、去重、去前后空格。
*/
private List<String> normalizeIdList(List<String> idList) {
if (CollUtil.isEmpty(idList)) {
return Collections.emptyList();
}
return idList.stream()
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.distinct()
.collect(Collectors.toList());
}
/**
* 是否为健康证类型。
*/
private boolean isHealthCertificate(String certificateType) {
return StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.HEALTH_CERTIFICATE.getType());
}
/**
* 构建图表统计结果对象。
*/
private CertificateAppRiskChartVO buildChart(List<CertificateAppStatusCountVO> statusCountList) {
Map<Integer, Integer> countMap = CollUtil.emptyIfNull(statusCountList)
.stream()
.filter(item -> item != null && item.getStatus() != null)
.collect(Collectors.toMap(
CertificateAppStatusCountVO::getStatus,
item -> item.getCount() == null ? 0 : item.getCount(),
Integer::sum
));
int missingCount = countMap.getOrDefault(STATUS_MISSING, 0);
int nearExpireCount = countMap.getOrDefault(STATUS_NEAR_EXPIRE, 0);
int expiredCount = countMap.getOrDefault(STATUS_EXPIRED, 0);
int normalCount = countMap.getOrDefault(STATUS_NORMAL, 0);
int totalCount = missingCount + nearExpireCount + expiredCount + normalCount;
CertificateAppRiskChartVO chartVO = new CertificateAppRiskChartVO();
chartVO.setMissingCount(missingCount);
chartVO.setNearExpireCount(nearExpireCount);
chartVO.setExpiredCount(expiredCount);
chartVO.setNormalCount(normalCount);
chartVO.setTotalCount(totalCount);
chartVO.setStatusList(Arrays.asList(
buildStatusCount(STATUS_MISSING, missingCount),
buildStatusCount(STATUS_NEAR_EXPIRE, nearExpireCount),
buildStatusCount(STATUS_EXPIRED, expiredCount),
buildStatusCount(STATUS_NORMAL, normalCount)
));
return chartVO;
}
/**
* 构建单个状态统计项。
*/
private CertificateAppStatusCountVO buildStatusCount(Integer status, Integer count) {
CertificateAppStatusCountVO item = new CertificateAppStatusCountVO();
item.setStatus(status);
item.setStatusName(resolveStatusName(status));
item.setCount(count == null ? 0 : count);
return item;
}
/**
* 构建空图表结果。
*/
private CertificateAppRiskChartVO buildEmptyChart() {
return buildChart(Collections.emptyList());
}
/**
* 构建员工风险空分页。
*/
private PageInfo<CertificateAppEmployeeRiskVO> buildEmptyEmployeePage(long current, long pageSize) {
PageInfo<CertificateAppEmployeeRiskVO> pageInfo = new PageInfo<>();
pageInfo.setList(Collections.emptyList());
pageInfo.setPageNum((int) current);
pageInfo.setPageSize((int) pageSize);
pageInfo.setTotal(0);
return pageInfo;
}
/**
* 构建门店风险空分页。
*/
private PageInfo<CertificateAppStoreRiskVO> buildEmptyStorePage(long current, long pageSize) {
PageInfo<CertificateAppStoreRiskVO> pageInfo = new PageInfo<>();
pageInfo.setList(Collections.emptyList());
pageInfo.setPageNum((int) current);
pageInfo.setPageSize((int) pageSize);
pageInfo.setTotal(0);
return pageInfo;
}
/**
* 填充门店风险返回项的展示字段。
*/
private void fillStoreRiskItem(List<CertificateAppStoreRiskVO> records,Map<String, OrganizeBaseInfoVO> organizeBaseInfoVOMap) {
if (CollectionUtil.isEmpty(records)) {
return;
}
for (CertificateAppStoreRiskVO item : records){
OrganizeBaseInfoVO organizeBaseInfoVO = organizeBaseInfoVOMap.get(item.getSubjectId());
if(Objects.isNull(organizeBaseInfoVO)){
continue;
}
item.setOrganizeId(organizeBaseInfoVO.getId());
item.setOrganizeName(organizeBaseInfoVO.getName());
String storeTag = organizeBaseInfoVO.getStoreTag();
item.setStoreTag(storeTag);
item.setOrganizeLeaderName(organizeBaseInfoVO.getLeaderName());
item.setOrganizeLeaderId(organizeBaseInfoVO.getLeaderId());
FtbStoreTagEnum storeTagEnum = FtbStoreTagEnum.fromName(storeTag);
if(Objects.nonNull(storeTagEnum)){
item.setStoreTagName(storeTagEnum.getLabel());
}
}
}
/**
* 状态编码转中文名称。
*/
private String resolveStatusName(Integer status) {
if (Integer.valueOf(STATUS_MISSING).equals(status)) {
return "缺失";
}
if (Integer.valueOf(STATUS_EXPIRED).equals(status)) {
return "过期";
}
if (Integer.valueOf(STATUS_NEAR_EXPIRE).equals(status)) {
return "临期";
}
if (Integer.valueOf(STATUS_NORMAL).equals(status)) {
return "正常";
}
return "-";
}
/**
* 证照类型编码转中文名称。
*/
private String resolveCertificateTypeName(String certificateType,String templateId,Map<String,String> templateIdAndNames) {
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.HEALTH_CERTIFICATE.getType())) {
return "健康证";
}
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.BUSINESS_LICENSE.getType())) {
return "营业执照";
}
if (StrUtil.equalsIgnoreCase(certificateType, CertificateTypeEnum.HYGIENE_LICENSE.getType())) {
return "食品经营许可证";
}
if (StrUtil.isBlank(certificateType)) {
return "证照";
}
String templateName = templateIdAndNames.get(templateId);
if(StringUtil.isNotBlank(templateName)){
return templateName;
}
return "";
}
/**
* 用户维度权限范围。
*/
private static class PermissionUserScope {
private final Boolean allDataPermission;
private final List<String> userIds;
private PermissionUserScope(Boolean allDataPermission, List<String> userIds) {
this.allDataPermission = allDataPermission;
this.userIds = userIds;
}
public Boolean getAllDataPermission() {
return allDataPermission;
}
public List<String> getUserIds() {
return userIds;
}
}
/**
* 组织维度权限范围。
*/
private static class PermissionOrgScope {
private final Boolean allDataPermission;
private final List<String> orgIds;
private PermissionOrgScope(Boolean allDataPermission, List<String> orgIds) {
this.allDataPermission = allDataPermission;
this.orgIds = orgIds;
}
public Boolean getAllDataPermission() {
return allDataPermission;
}
public List<String> getOrgIds() {
return orgIds;
}
}
}

View File

@@ -0,0 +1,574 @@
package jnpf.certificate.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.seata.spring.annotation.GlobalTransactional;
import jnpf.certificate.mapper.CertificateBusinessLicenseExtMapper;
import jnpf.certificate.mapper.CertificateHygieneLicenseExtMapper;
import jnpf.certificate.mapper.CertificateInstanceItemMapper;
import jnpf.certificate.mapper.CertificateInstanceMapper;
import jnpf.certificate.service.CertificateManageApiService;
import jnpf.model.certificate.po.CertificateBusinessLicenseExtEntity;
import jnpf.model.certificate.po.CertificateHygieneLicenseExtEntity;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.certificate.po.CertificateInstanceItemEntity;
import jnpf.model.certificate.vo.CertificateOrganizeBusinessLicenseVO;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.util.FtbUtil;
import jnpf.util.ServiceException;
import jnpf.util.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
/**
* 组织营业执照开放接口服务实现。
*/
@Slf4j
@Service
public class CertificateManageApiServiceImpl implements CertificateManageApiService {
private static final int STATUS_MISSING = 1;
private static final int STATUS_EXPIRED = 2;
private static final int STATUS_NEAR_EXPIRE = 3;
private static final int STATUS_NORMAL = 4;
private static final int SUBJECT_TYPE_ORGANIZE = 2;
private static final int SUBJECT_TYPE_STORE = 3;
private static final int IS_LONG_TERM_NO = 0;
private static final int NEAR_EXPIRE_DAYS = 30;
@Autowired
private CertificateInstanceMapper certificateInstanceMapper;
@Autowired
private CertificateBusinessLicenseExtMapper certificateBusinessLicenseExtMapper;
@Autowired
private CertificateHygieneLicenseExtMapper certificateHygieneLicenseExtMapper;
@Autowired
private CertificateInstanceItemMapper certificateInstanceItemMapper;
/**
* 根据组织ID查询营业执照信息。
*/
@Override
public CertificateOrganizeBusinessLicenseVO queryBusinessLicense(String organizeId) {
ServiceException.isTrue(StrUtil.isNotBlank(organizeId), "组织ID不能为空");
return buildOrganizeBusinessLicenseVO(StrUtil.trim(organizeId));
}
/**
* 根据组织ID列表批量查询营业执照信息。
*/
@Override
public List<CertificateOrganizeBusinessLicenseVO> queryBusinessLicenseBatch(Collection<String> organizeIds) {
if (CollUtil.isEmpty(organizeIds)) {
return Collections.emptyList();
}
List<String> targetIds = organizeIds.stream()
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.distinct()
.collect(Collectors.toList());
if (CollUtil.isEmpty(targetIds)) {
return Collections.emptyList();
}
Map<String, CertificateInstanceEntity> instanceMap = queryBusinessInstanceMap(targetIds);
Map<String, CertificateBusinessLicenseExtEntity> extMap = queryBusinessExtMap(instanceMap.values());
List<CertificateOrganizeBusinessLicenseVO> result = new ArrayList<>(targetIds.size());
for (String organizeId : targetIds) {
CertificateInstanceEntity instance = instanceMap.get(organizeId);
CertificateBusinessLicenseExtEntity ext = instance == null ? null : extMap.get(instance.getId());
result.add(convertToVO(organizeId, instance, ext));
}
return result;
}
/**
* 保存组织营业执照信息。
*/
@Override
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
public void saveBusinessLicense(CertificateOrganizeBusinessLicenseVO req) {
ServiceException.notNull(req, "请求参数不能为空");
String organizeId = StrUtil.trim(req.getOrganizeId());
ServiceException.isTrue(StrUtil.isNotBlank(organizeId), "组织ID不能为空");
Date establishDate = parseDate(req.getEstablishDate(), "成立时间");
Date issueDate = parseDate(req.getIssueDate(), "发证日期");
Date expireDate = parseDate(req.getExpireDate(), "到期日期");
Integer isLongTerm = req.getIsLongTerm() == null ? 0 : req.getIsLongTerm();
if (Integer.valueOf(1).equals(isLongTerm)) {
expireDate = null;
}
String certificateType = CertificateTypeEnum.BUSINESS_LICENSE.getType();
String certificateImage = trimToNull(req.getCertificateImage());
int status = calculateStatus(isLongTerm, expireDate);
String instanceId = upsertBusinessInstance(
organizeId,
certificateType,
certificateImage,
issueDate,
expireDate,
isLongTerm,
status
);
if(Objects.isNull(instanceId)){
return;
}
upsertBusinessExt(instanceId, req, establishDate);
}
/**
* 初始化门店默认缺失证照(营业执照、食品经营许可证)。
*/
@Override
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
public void initStoreDefaultCertificates(String storeId) {
String targetStoreId = StrUtil.trim(storeId);
ServiceException.isTrue(StrUtil.isNotBlank(targetStoreId), "门店ID不能为空");
List<String> defaultTypes = Arrays.asList(
CertificateTypeEnum.BUSINESS_LICENSE.getType(),
CertificateTypeEnum.HYGIENE_LICENSE.getType()
);
for (String certificateType : defaultTypes) {
initStoreDefaultCertificate(targetStoreId, certificateType);
}
}
/**
* 根据组织ID删除该主体下全部证照信息。
*/
@Override
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
public void deleteBusinessLicense(String organizeId, String loginUserId) {
String targetOrganizeId = StrUtil.trim(organizeId);
ServiceException.isTrue(StrUtil.isNotBlank(targetOrganizeId), "组织ID不能为空");
Date now = new Date();
LambdaUpdateWrapper<CertificateInstanceEntity> instanceUpdateWrapper = Wrappers.lambdaUpdate();
instanceUpdateWrapper.eq(CertificateInstanceEntity::getEnabledMark, 0);
instanceUpdateWrapper.eq(CertificateInstanceEntity::getSubjectId, targetOrganizeId);
instanceUpdateWrapper.set(CertificateInstanceEntity::getEnabledMark, 1);
instanceUpdateWrapper.set(CertificateInstanceEntity::getLastModifyTime, now);
if(StringUtil.isNotBlank(loginUserId)){
instanceUpdateWrapper.set(CertificateInstanceEntity::getLastModifyUserId, loginUserId);
}
certificateInstanceMapper.update(null, instanceUpdateWrapper);
LambdaQueryWrapper<CertificateInstanceEntity> instanceQueryWrapper = Wrappers.lambdaQuery();
instanceQueryWrapper.eq(CertificateInstanceEntity::getSubjectType, SUBJECT_TYPE_ORGANIZE);
instanceQueryWrapper.eq(CertificateInstanceEntity::getSubjectId, targetOrganizeId);
List<CertificateInstanceEntity> allBusinessInstances = certificateInstanceMapper.selectList(instanceQueryWrapper);
if (CollUtil.isEmpty(allBusinessInstances)) {
return;
}
List<String> instanceIds = allBusinessInstances.stream()
.map(CertificateInstanceEntity::getId)
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.distinct()
.collect(Collectors.toList());
if (CollUtil.isEmpty(instanceIds)) {
return;
}
LambdaUpdateWrapper<CertificateBusinessLicenseExtEntity> extUpdateWrapper = Wrappers.lambdaUpdate();
extUpdateWrapper.eq(CertificateBusinessLicenseExtEntity::getEnabledMark, 0);
extUpdateWrapper.in(CertificateBusinessLicenseExtEntity::getInstanceId, instanceIds);
extUpdateWrapper.set(CertificateBusinessLicenseExtEntity::getEnabledMark, 1);
extUpdateWrapper.set(CertificateBusinessLicenseExtEntity::getLastModifyTime, now);
extUpdateWrapper.set(CertificateBusinessLicenseExtEntity::getLastModifyUserId, loginUserId);
certificateBusinessLicenseExtMapper.update(null, extUpdateWrapper);
LambdaUpdateWrapper<CertificateHygieneLicenseExtEntity> hygieneExtUpdateWrapper = Wrappers.lambdaUpdate();
hygieneExtUpdateWrapper.eq(CertificateHygieneLicenseExtEntity::getEnabledMark, 0);
hygieneExtUpdateWrapper.in(CertificateHygieneLicenseExtEntity::getInstanceId, instanceIds);
hygieneExtUpdateWrapper.set(CertificateHygieneLicenseExtEntity::getEnabledMark, 1);
hygieneExtUpdateWrapper.set(CertificateHygieneLicenseExtEntity::getLastModifyTime, now);
hygieneExtUpdateWrapper.set(CertificateHygieneLicenseExtEntity::getLastModifyUserId, loginUserId);
certificateHygieneLicenseExtMapper.update(null, hygieneExtUpdateWrapper);
LambdaUpdateWrapper<CertificateInstanceItemEntity> itemUpdateWrapper = Wrappers.lambdaUpdate();
itemUpdateWrapper.eq(CertificateInstanceItemEntity::getEnabledMark, 0);
itemUpdateWrapper.in(CertificateInstanceItemEntity::getInstanceId, instanceIds);
itemUpdateWrapper.set(CertificateInstanceItemEntity::getEnabledMark, 1);
itemUpdateWrapper.set(CertificateInstanceItemEntity::getLastModifyTime, now);
itemUpdateWrapper.set(CertificateInstanceItemEntity::getLastModifyUserId, loginUserId);
certificateInstanceItemMapper.update(null, itemUpdateWrapper);
}
/**
* 查询并组装组织营业执照返回对象。
*/
private CertificateOrganizeBusinessLicenseVO buildOrganizeBusinessLicenseVO(String organizeId) {
CertificateInstanceEntity instance = queryLatestBusinessInstance(organizeId);
CertificateBusinessLicenseExtEntity ext = instance == null ? null : queryLatestBusinessExt(instance.getId());
return convertToVO(organizeId, instance, ext);
}
/**
* 批量查询营业执照主表数据映射。
*/
private Map<String, CertificateInstanceEntity> queryBusinessInstanceMap(List<String> organizeIds) {
LambdaQueryWrapper<CertificateInstanceEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateInstanceEntity::getEnabledMark, 0);
wrapper.eq(CertificateInstanceEntity::getSubjectType, SUBJECT_TYPE_ORGANIZE);
wrapper.eq(CertificateInstanceEntity::getCertificateType, CertificateTypeEnum.BUSINESS_LICENSE.getType());
wrapper.in(CertificateInstanceEntity::getSubjectId, organizeIds);
wrapper.orderByDesc(CertificateInstanceEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateInstanceEntity::getCreatorTime);
List<CertificateInstanceEntity> instanceList = certificateInstanceMapper.selectList(wrapper);
if (CollUtil.isEmpty(instanceList)) {
return Collections.emptyMap();
}
Map<String, CertificateInstanceEntity> result = new LinkedHashMap<>();
for (CertificateInstanceEntity entity : instanceList) {
if (entity == null || StrUtil.isBlank(entity.getSubjectId())) {
continue;
}
String subjectId = StrUtil.trim(entity.getSubjectId());
result.putIfAbsent(subjectId, entity);
}
return result;
}
/**
* 批量查询营业执照扩展表数据映射。
*/
private Map<String, CertificateBusinessLicenseExtEntity> queryBusinessExtMap(Iterable<CertificateInstanceEntity> instances) {
List<String> instanceIds = new ArrayList<>();
for (CertificateInstanceEntity entity : instances) {
if (entity != null && StrUtil.isNotBlank(entity.getId())) {
instanceIds.add(StrUtil.trim(entity.getId()));
}
}
if (CollUtil.isEmpty(instanceIds)) {
return Collections.emptyMap();
}
LambdaQueryWrapper<CertificateBusinessLicenseExtEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateBusinessLicenseExtEntity::getEnabledMark, 0);
wrapper.in(CertificateBusinessLicenseExtEntity::getInstanceId, instanceIds);
wrapper.orderByDesc(CertificateBusinessLicenseExtEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateBusinessLicenseExtEntity::getCreatorTime);
List<CertificateBusinessLicenseExtEntity> extList = certificateBusinessLicenseExtMapper.selectList(wrapper);
if (CollUtil.isEmpty(extList)) {
return Collections.emptyMap();
}
Map<String, CertificateBusinessLicenseExtEntity> result = new LinkedHashMap<>();
for (CertificateBusinessLicenseExtEntity ext : extList) {
if (ext == null || StrUtil.isBlank(ext.getInstanceId())) {
continue;
}
result.putIfAbsent(StrUtil.trim(ext.getInstanceId()), ext);
}
return result;
}
/**
* 查询组织最新营业执照主表记录。
*/
private CertificateInstanceEntity queryLatestBusinessInstance(String organizeId) {
LambdaQueryWrapper<CertificateInstanceEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateInstanceEntity::getEnabledMark, 0);
wrapper.eq(CertificateInstanceEntity::getSubjectType, SUBJECT_TYPE_ORGANIZE);
wrapper.eq(CertificateInstanceEntity::getSubjectId, organizeId);
wrapper.eq(CertificateInstanceEntity::getCertificateType, CertificateTypeEnum.BUSINESS_LICENSE.getType());
wrapper.orderByDesc(CertificateInstanceEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateInstanceEntity::getCreatorTime);
List<CertificateInstanceEntity> list = certificateInstanceMapper.selectList(wrapper);
return CollUtil.isEmpty(list) ? null : list.get(0);
}
/**
* 查询实例最新营业执照扩展记录。
*/
private CertificateBusinessLicenseExtEntity queryLatestBusinessExt(String instanceId) {
LambdaQueryWrapper<CertificateBusinessLicenseExtEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateBusinessLicenseExtEntity::getEnabledMark, 0);
wrapper.eq(CertificateBusinessLicenseExtEntity::getInstanceId, instanceId);
wrapper.orderByDesc(CertificateBusinessLicenseExtEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateBusinessLicenseExtEntity::getCreatorTime);
List<CertificateBusinessLicenseExtEntity> list = certificateBusinessLicenseExtMapper.selectList(wrapper);
return CollUtil.isEmpty(list) ? null : list.get(0);
}
/**
* 保存或更新营业执照主表信息。
*/
private String upsertBusinessInstance(String organizeId,
String certificateType,
String certificateImage,
Date issueDate,
Date expireDate,
Integer isLongTerm,
Integer status) {
LambdaUpdateWrapper<CertificateInstanceEntity> updateWrapper = buildBusinessInstanceUpdateWrapper(
organizeId, certificateType, certificateImage, issueDate, expireDate, isLongTerm, status
);
int updateRows = certificateInstanceMapper.update(null, updateWrapper);
if (updateRows > 0) {
CertificateInstanceEntity latest = queryLatestBusinessInstance(organizeId);
ServiceException.notNull(latest, "营业执照实例更新成功但未查询到数据");
return latest.getId();
}
CertificateInstanceEntity insertEntity = new CertificateInstanceEntity();
insertEntity.setId(FtbUtil.getId());
insertEntity.setCertificateType(certificateType);
insertEntity.setSubjectType(SUBJECT_TYPE_ORGANIZE);
insertEntity.setSubjectId(organizeId);
insertEntity.setCertificateImage(certificateImage);
insertEntity.setIssueDate(issueDate);
insertEntity.setExpireDate(expireDate);
if(Objects.nonNull(expireDate)){
insertEntity.setExpireTimestamp(expireDate.getTime());
}
insertEntity.setIsLongTerm(isLongTerm);
insertEntity.setStatus(status);
insertEntity.setEnabledMark(0);
try {
certificateInstanceMapper.insert(insertEntity);
return insertEntity.getId();
} catch (DuplicateKeyException e) {
log.warn("营业执照实例插入出现重复键执行重试更新。organizeId={}, certificateType={}", organizeId, certificateType, e);
LambdaUpdateWrapper<CertificateInstanceEntity> retryWrapper = buildBusinessInstanceUpdateWrapper(
organizeId, certificateType, certificateImage, issueDate, expireDate, isLongTerm, status
);
int retryRows = certificateInstanceMapper.update(null, retryWrapper);
if(retryRows <= 0){
log.error("upsertBusinessInstance fail.insertEntity:{}",insertEntity);
return null;
}
CertificateInstanceEntity latest = queryLatestBusinessInstance(organizeId);
ServiceException.notNull(latest, "营业执照实例重试更新成功但未查询到数据");
return latest.getId();
}
}
/**
* 初始化单个门店默认证照记录。
*/
private void initStoreDefaultCertificate(String storeId, String certificateType) {
LambdaQueryWrapper<CertificateInstanceEntity> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(CertificateInstanceEntity::getEnabledMark, 0);
queryWrapper.eq(CertificateInstanceEntity::getSubjectType, SUBJECT_TYPE_STORE);
queryWrapper.eq(CertificateInstanceEntity::getSubjectId, storeId);
queryWrapper.eq(CertificateInstanceEntity::getCertificateType, certificateType);
queryWrapper.last("limit 1");
CertificateInstanceEntity existed = certificateInstanceMapper.selectOne(queryWrapper);
if (existed != null) {
return;
}
CertificateInstanceEntity insertEntity = new CertificateInstanceEntity();
insertEntity.setId(FtbUtil.getId());
insertEntity.setCertificateType(certificateType);
insertEntity.setSubjectType(SUBJECT_TYPE_STORE);
insertEntity.setSubjectId(storeId);
insertEntity.setIsLongTerm(IS_LONG_TERM_NO);
insertEntity.setStatus(STATUS_MISSING);
insertEntity.setEnabledMark(0);
try {
certificateInstanceMapper.insert(insertEntity);
} catch (DuplicateKeyException e) {
log.warn("初始化门店默认证照出现重复键忽略。storeId={}, certificateType={}", storeId, certificateType, e);
}
}
/**
* 保存或更新营业执照扩展表信息。
*/
private void upsertBusinessExt(String instanceId, CertificateOrganizeBusinessLicenseVO req, Date establishDate) {
String companyName = trimToNull(req.getCompanyName());
String companyAddress = trimToNull(req.getCompanyAddress());
String legalRepresentative = trimToNull(req.getLegalRepresentative());
LambdaUpdateWrapper<CertificateBusinessLicenseExtEntity> updateWrapper = buildBusinessExtUpdateWrapper(
instanceId, companyName, companyAddress, legalRepresentative, establishDate
);
int updateRows = certificateBusinessLicenseExtMapper.update(null, updateWrapper);
if (updateRows > 0) {
return;
}
CertificateBusinessLicenseExtEntity insertEntity = new CertificateBusinessLicenseExtEntity();
insertEntity.setId(FtbUtil.getId());
insertEntity.setInstanceId(instanceId);
insertEntity.setCompanyName(companyName);
insertEntity.setCompanyAddress(companyAddress);
insertEntity.setLegalRepresentative(legalRepresentative);
insertEntity.setEstablishDate(establishDate);
insertEntity.setEnabledMark(0);
try {
certificateBusinessLicenseExtMapper.insert(insertEntity);
} catch (DuplicateKeyException e) {
log.warn("营业执照扩展插入出现重复键执行重试更新。instanceId={}", instanceId, e);
LambdaUpdateWrapper<CertificateBusinessLicenseExtEntity> retryWrapper = buildBusinessExtUpdateWrapper(
instanceId, companyName, companyAddress, legalRepresentative, establishDate
);
int retryRows = certificateBusinessLicenseExtMapper.update(null, retryWrapper);
ServiceException.isTrue(retryRows > 0, "营业执照扩展保存失败,请稍后重试");
}
}
/**
* 组装营业执照返回对象。
*/
private CertificateOrganizeBusinessLicenseVO convertToVO(String organizeId,
CertificateInstanceEntity instance,
CertificateBusinessLicenseExtEntity ext) {
CertificateOrganizeBusinessLicenseVO vo = new CertificateOrganizeBusinessLicenseVO();
vo.setOrganizeId(organizeId);
vo.setIsLongTerm(instance == null || instance.getIsLongTerm() == null ? 0 : instance.getIsLongTerm());
if (instance != null) {
vo.setCertificateImage(instance.getCertificateImage());
vo.setIssueDate(formatDate(instance.getIssueDate()));
vo.setExpireDate(formatDate(instance.getExpireDate()));
vo.setBusinessTerm(buildBusinessTerm(instance.getIssueDate(), instance.getExpireDate(), vo.getIsLongTerm()));
} else {
vo.setBusinessTerm("[]");
}
if (ext != null) {
vo.setCompanyName(ext.getCompanyName());
vo.setLegalRepresentative(ext.getLegalRepresentative());
vo.setCompanyAddress(ext.getCompanyAddress());
vo.setEstablishDate(formatDate(ext.getEstablishDate()));
}
return vo;
}
/**
* 计算证照状态。
*/
private Integer calculateStatus(Integer isLongTerm, Date expireDate) {
if (Integer.valueOf(1).equals(isLongTerm)) {
return STATUS_NORMAL;
}
if (expireDate == null) {
return STATUS_MISSING;
}
Date today = DateUtil.beginOfDay(DateUtil.date());
Date target = DateUtil.beginOfDay(expireDate);
if (target.before(today)) {
return STATUS_EXPIRED;
}
long daysDiff = DateUtil.betweenDay(today, target, false);
if (daysDiff <= NEAR_EXPIRE_DAYS) {
return STATUS_NEAR_EXPIRE;
}
return STATUS_NORMAL;
}
/**
* 解析日期字符串。
*/
private Date parseDate(String dateText, String fieldName) {
if (StrUtil.isBlank(dateText)) {
return null;
}
int strLen = dateText.length();
if(StringUtil.isNumeric(dateText) && (strLen == 10 || strLen == 13)){
long date = Long.parseLong(dateText);
if(dateText.length() == 10){
date = date * 1000;
}
return new Date(date);
}
try {
return DateUtil.parseDate(StrUtil.trim(dateText));
} catch (Exception e) {
throw new ServiceException(fieldName + "格式不正确正确格式为yyyy-MM-dd");
}
}
/**
* 格式化日期。
*/
private String formatDate(Date date) {
return date == null ? null : DateUtil.formatDate(date);
}
/**
* 组装营业期限展示文案。
*/
private String buildBusinessTerm(Date issueDate, Date expireDate, Integer isLongTerm) {
if (Integer.valueOf(1).equals(isLongTerm)) {
return "[]";
}
if (expireDate == null) {
return "[]";
}
String issueText = issueDate == null ? "" : DateUtil.formatDate(issueDate);
return "[\""+issueText + "\",\"" + DateUtil.formatDate(expireDate)+"\"]";
}
/**
* 构建营业执照主表更新条件。
*/
private LambdaUpdateWrapper<CertificateInstanceEntity> buildBusinessInstanceUpdateWrapper(String organizeId,
String certificateType,
String certificateImage,
Date issueDate,
Date expireDate,
Integer isLongTerm,
Integer status) {
LambdaUpdateWrapper<CertificateInstanceEntity> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(CertificateInstanceEntity::getSubjectId, organizeId);
updateWrapper.eq(CertificateInstanceEntity::getCertificateType, certificateType);
updateWrapper.set(CertificateInstanceEntity::getSubjectType, SUBJECT_TYPE_ORGANIZE);
updateWrapper.set(CertificateInstanceEntity::getEnabledMark, 0);
updateWrapper.set(CertificateInstanceEntity::getCertificateImage, certificateImage);
updateWrapper.set(CertificateInstanceEntity::getIssueDate, issueDate);
updateWrapper.set(CertificateInstanceEntity::getExpireDate, expireDate);
if(Objects.nonNull(expireDate)){
updateWrapper.set(CertificateInstanceEntity::getExpireTimestamp, expireDate.getTime());
}
updateWrapper.set(CertificateInstanceEntity::getIsLongTerm, isLongTerm);
updateWrapper.set(CertificateInstanceEntity::getStatus, status);
return updateWrapper;
}
/**
* 构建营业执照扩展表更新条件。
*/
private LambdaUpdateWrapper<CertificateBusinessLicenseExtEntity> buildBusinessExtUpdateWrapper(String instanceId,
String companyName,
String companyAddress,
String legalRepresentative,
Date establishDate) {
LambdaUpdateWrapper<CertificateBusinessLicenseExtEntity> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(CertificateBusinessLicenseExtEntity::getEnabledMark, 0);
updateWrapper.eq(CertificateBusinessLicenseExtEntity::getInstanceId, instanceId);
updateWrapper.set(CertificateBusinessLicenseExtEntity::getCompanyName, companyName);
updateWrapper.set(CertificateBusinessLicenseExtEntity::getCompanyAddress, companyAddress);
updateWrapper.set(CertificateBusinessLicenseExtEntity::getLegalRepresentative, legalRepresentative);
updateWrapper.set(CertificateBusinessLicenseExtEntity::getEstablishDate, establishDate);
return updateWrapper;
}
/**
* 去首尾空格,空串返回 null。
*/
private String trimToNull(String text) {
String value = StrUtil.trim(text);
return StrUtil.isBlank(value) ? null : value;
}
}

View File

@@ -0,0 +1,481 @@
package jnpf.certificate.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jnpf.certificate.mapper.CertificateBusinessLicenseExtMapper;
import jnpf.certificate.mapper.CertificateHygieneLicenseExtMapper;
import jnpf.certificate.mapper.CertificateInstanceItemMapper;
import jnpf.certificate.mapper.CertificateInstanceMapper;
import jnpf.certificate.service.CertificateManageService;
import jnpf.model.certificate.po.CertificateBusinessLicenseExtEntity;
import jnpf.model.certificate.po.CertificateHygieneLicenseExtEntity;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.certificate.po.CertificateInstanceItemEntity;
import jnpf.model.certificate.req.CertificateInstanceItemReq;
import jnpf.model.certificate.req.app.CertificateAppBusinessLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHealthCertificateUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHygieneLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppStoreCustomUpdateReq;
import jnpf.model.certificate.vo.CertificateInstanceItemVO;
import jnpf.model.certificate.vo.app.CertificateAppBusinessLicenseDetailVO;
import jnpf.model.certificate.vo.app.CertificateAppCertificateDetailVO;
import jnpf.model.certificate.vo.app.HealthCertificateDetailVO;
import jnpf.model.certificate.vo.app.CertificateAppHygieneLicenseDetailVO;
import jnpf.model.certificate.vo.app.CertificateAppStoreCustomDetailVO;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.util.FtbUtil;
import jnpf.util.ServiceException;
import jnpf.util.UserProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
/**
* App端证照管理服务实现。
*/
@Service
public class CertificateManageServiceImpl implements CertificateManageService {
private static final int STATUS_MISSING = 1;
private static final int STATUS_EXPIRED = 2;
private static final int STATUS_NEAR_EXPIRE = 3;
private static final int STATUS_NORMAL = 4;
private static final int NEAR_EXPIRE_DAYS = 30;
@Autowired
private CertificateInstanceMapper certificateInstanceMapper;
@Autowired
private CertificateBusinessLicenseExtMapper certificateBusinessLicenseExtMapper;
@Autowired
private CertificateHygieneLicenseExtMapper certificateHygieneLicenseExtMapper;
@Autowired
private CertificateInstanceItemMapper certificateInstanceItemMapper;
/**
* 根据证照实例ID查询详情按证照类型返回不同明细对象。
*/
@Override
public CertificateAppCertificateDetailVO queryInfo(String certificateInstanceId) {
ServiceException.isTrue(StrUtil.isNotBlank(certificateInstanceId), "证照实例ID不能为空");
CertificateInstanceEntity entity = queryActiveEntity(StrUtil.trim(certificateInstanceId));
Optional<CertificateTypeEnum> certificateTypeEnumOptional = CertificateTypeEnum.getByType(entity.getCertificateType());
ServiceException.isTrue(certificateTypeEnumOptional.isPresent(), "不支持的证照类型");
CertificateTypeEnum certificateTypeEnum = certificateTypeEnumOptional.get();
Integer status = calculateStatus(entity.getIsLongTerm(), entity.getExpireDate());
CertificateAppCertificateDetailVO result = new CertificateAppCertificateDetailVO();
result.setCertificateInstanceId(entity.getId());
result.setCertificateType(certificateTypeEnum.getType());
result.setStatus(status);
result.setStatusName(resolveStatusName(status));
if (CertificateTypeEnum.HEALTH_CERTIFICATE.equals(certificateTypeEnum)) {
result.setDetail(buildHealthDetail(entity));
return result;
}
if (CertificateTypeEnum.BUSINESS_LICENSE.equals(certificateTypeEnum)) {
result.setDetail(buildBusinessLicenseDetail(entity));
return result;
}
if (CertificateTypeEnum.HYGIENE_LICENSE.equals(certificateTypeEnum)) {
result.setDetail(buildHygieneLicenseDetail(entity));
return result;
}
result.setDetail(buildStoreCustomDetail(entity));
return result;
}
/**
* 更新健康证数据。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void updateHealthCertificate(CertificateAppHealthCertificateUpdateReq req) {
ServiceException.notNull(req, "请求参数不能为空");
CertificateInstanceEntity entity = queryAndAssertType(req.getCertificateInstanceId(), CertificateTypeEnum.HEALTH_CERTIFICATE, "健康证");
Date issueDate = parseDate(req.getIssueDate(), "发证日期");
Date expireDate = parseDate(req.getExpireDate(), "到期日期");
//原生app增量更新可能不传值则使用数据库值
if(Objects.isNull(issueDate)){
issueDate = entity.getIssueDate();
}
if(Objects.isNull(expireDate)){
expireDate = entity.getExpireDate();
}
if(Objects.nonNull(issueDate) && Objects.nonNull(expireDate) && issueDate.after(expireDate)){
throw new ServiceException("发证日期不能大于到期日期");
}
entity.setCertificateImage(trimToNull(req.getCertificateImage()));
entity.setIssueDate(issueDate);
entity.setExpireDate(expireDate);
entity.setIsLongTerm(0);
entity.setStatus(calculateStatus(entity.getIsLongTerm(), entity.getExpireDate()));
int r = certificateInstanceMapper.updateById(entity);
if(r <= 0){
return;
}
}
/**
* 更新营业执照数据,并同步营业执照扩展表。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void updateBusinessLicense(CertificateAppBusinessLicenseUpdateReq req) {
ServiceException.notNull(req, "请求参数不能为空");
CertificateInstanceEntity entity = queryAndAssertType(req.getCertificateInstanceId(), CertificateTypeEnum.BUSINESS_LICENSE, "营业执照");
Date establishDate = parseDate(req.getEstablishDate(), "成立时间");
Date issueDate = parseDate(req.getIssueDate(), "发证日期");
Date expireDate = parseDate(req.getExpireDate(), "到期日期");
Integer isLongTerm = req.getIsLongTerm() == null ? 0 : req.getIsLongTerm();
if (Integer.valueOf(1).equals(isLongTerm)) {
expireDate = null;
}
entity.setCertificateImage(trimToNull(req.getCertificateImage()));
entity.setCertificateNo(trimToNull(req.getCertificateNo()));
entity.setIssueDate(issueDate);
entity.setExpireDate(expireDate);
entity.setIsLongTerm(isLongTerm);
entity.setStatus(calculateStatus(entity.getIsLongTerm(), entity.getExpireDate()));
certificateInstanceMapper.updateById(entity);
saveBusinessLicenseExt(entity.getId(), req, establishDate);
}
/**
* 更新食品经营许可证数据,并同步食品许可扩展表。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void updateHygieneLicense(CertificateAppHygieneLicenseUpdateReq req) {
ServiceException.notNull(req, "请求参数不能为空");
CertificateInstanceEntity entity = queryAndAssertType(req.getCertificateInstanceId(), CertificateTypeEnum.HYGIENE_LICENSE, "食品经营许可证");
Date issueDate = parseDate(req.getIssueDate(), "发证日期");
Date expireDate = parseDate(req.getExpireDate(), "到期日期");
entity.setCertificateImage(trimToNull(req.getCertificateImage()));
entity.setIssueDate(issueDate);
entity.setExpireDate(expireDate);
entity.setIsLongTerm(0);
entity.setStatus(calculateStatus(entity.getIsLongTerm(), entity.getExpireDate()));
certificateInstanceMapper.updateById(entity);
saveHygieneLicenseExt(entity.getId(), req);
}
/**
* 更新门店自定义证照数据,并重写自定义字段明细。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStoreCustomCertificate(CertificateAppStoreCustomUpdateReq req) {
ServiceException.notNull(req, "请求参数不能为空");
CertificateInstanceEntity entity = queryAndAssertType(req.getCertificateInstanceId(), CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE, "门店自定义证照");
Date expireDate = parseDate(req.getExpireDate(), "到期日期");
entity.setCertificateImage("");
entity.setIssueDate(null);
entity.setExpireDate(expireDate);
entity.setIsLongTerm(0);
entity.setStatus(calculateStatus(entity.getIsLongTerm(), entity.getExpireDate()));
certificateInstanceMapper.updateById(entity);
deleteAndSaveStoreCustomItems(entity.getId(), req.getItemList());
}
/**
* 构建健康证明细返回对象。
*/
private HealthCertificateDetailVO buildHealthDetail(CertificateInstanceEntity entity) {
HealthCertificateDetailVO detailVO = new HealthCertificateDetailVO();
detailVO.setCertificateImage(entity.getCertificateImage());
detailVO.setIssueDate(entity.getIssueDate());
detailVO.setExpireDate(entity.getExpireDate());
detailVO.setStatus(entity.getStatus());
return detailVO;
}
/**
* 构建营业执照明细返回对象。
*/
private CertificateAppBusinessLicenseDetailVO buildBusinessLicenseDetail(CertificateInstanceEntity entity) {
CertificateBusinessLicenseExtEntity extEntity = queryBusinessLicenseExt(entity.getId());
CertificateAppBusinessLicenseDetailVO detailVO = new CertificateAppBusinessLicenseDetailVO();
detailVO.setCertificateImage(entity.getCertificateImage());
detailVO.setCertificateNo(entity.getCertificateNo());
detailVO.setIssueDate(entity.getIssueDate());
detailVO.setExpireDate(entity.getExpireDate());
detailVO.setIsLongTerm(entity.getIsLongTerm());
detailVO.setStatus(entity.getStatus());
if (extEntity != null) {
detailVO.setCompanyName(extEntity.getCompanyName());
detailVO.setLegalRepresentative(extEntity.getLegalRepresentative());
detailVO.setEstablishDate(extEntity.getEstablishDate());
detailVO.setBusinessScope(extEntity.getBusinessScope());
detailVO.setAddress(extEntity.getCompanyAddress());
}
return detailVO;
}
/**
* 构建食品经营许可证明细返回对象。
*/
private CertificateAppHygieneLicenseDetailVO buildHygieneLicenseDetail(CertificateInstanceEntity entity) {
CertificateHygieneLicenseExtEntity extEntity = queryHygieneLicenseExt(entity.getId());
CertificateAppHygieneLicenseDetailVO detailVO = new CertificateAppHygieneLicenseDetailVO();
detailVO.setCertificateImage(entity.getCertificateImage());
detailVO.setIssueDate(entity.getIssueDate());
detailVO.setExpireDate(entity.getExpireDate());
detailVO.setStatus(entity.getStatus());
if (extEntity != null) {
detailVO.setBusinessProject(extEntity.getBusinessProject());
}
return detailVO;
}
/**
* 构建门店自定义证照明细返回对象。
*/
private CertificateAppStoreCustomDetailVO buildStoreCustomDetail(CertificateInstanceEntity entity) {
CertificateAppStoreCustomDetailVO detailVO = new CertificateAppStoreCustomDetailVO();
detailVO.setCertificateImage(entity.getCertificateImage());
detailVO.setExpireDate(entity.getExpireDate());
LambdaQueryWrapper<CertificateInstanceItemEntity> itemWrapper = Wrappers.lambdaQuery();
itemWrapper.eq(CertificateInstanceItemEntity::getInstanceId, entity.getId());
itemWrapper.eq(CertificateInstanceItemEntity::getEnabledMark, 0);
itemWrapper.orderByAsc(CertificateInstanceItemEntity::getSorts);
List<CertificateInstanceItemEntity> itemList = certificateInstanceItemMapper.selectList(itemWrapper);
if (CollUtil.isNotEmpty(itemList)) {
detailVO.setItemList(itemList.stream().map(CertificateInstanceItemVO::convert).collect(Collectors.toList()));
}
return detailVO;
}
/**
* 保存营业执照扩展信息,不存在则新增,存在则更新。
*/
private void saveBusinessLicenseExt(String instanceId, CertificateAppBusinessLicenseUpdateReq req, Date establishDate) {
CertificateBusinessLicenseExtEntity extEntity = queryBusinessLicenseExt(instanceId);
if (extEntity == null) {
extEntity = new CertificateBusinessLicenseExtEntity();
extEntity.setId(FtbUtil.getId());
extEntity.setInstanceId(instanceId);
extEntity.setEnabledMark(0);
fillBusinessLicenseExt(extEntity, req, establishDate);
certificateBusinessLicenseExtMapper.insert(extEntity);
return;
}
fillBusinessLicenseExt(extEntity, req, establishDate);
certificateBusinessLicenseExtMapper.update(null,new LambdaUpdateWrapper<CertificateBusinessLicenseExtEntity>()
.eq(CertificateBusinessLicenseExtEntity::getId, extEntity.getId())
.set(CertificateBusinessLicenseExtEntity::getCompanyName,extEntity.getCompanyName())
.set(CertificateBusinessLicenseExtEntity::getCompanyAddress,extEntity.getCompanyAddress())
.set(CertificateBusinessLicenseExtEntity::getBusinessScope,extEntity.getBusinessScope())
.set(CertificateBusinessLicenseExtEntity::getEstablishDate,extEntity.getEstablishDate())
.set(CertificateBusinessLicenseExtEntity::getLegalRepresentative,extEntity.getLegalRepresentative())
.set(CertificateBusinessLicenseExtEntity::getLastModifyUserId, UserProvider.getLoginUserId())
.set(CertificateBusinessLicenseExtEntity::getLastModifyTime,new Date()));
}
/**
* 填充营业执照扩展字段。
*/
private void fillBusinessLicenseExt(CertificateBusinessLicenseExtEntity extEntity, CertificateAppBusinessLicenseUpdateReq req, Date establishDate) {
extEntity.setCompanyName(trimToNull(req.getCompanyName()));
extEntity.setLegalRepresentative(trimToNull(req.getLegalRepresentative()));
extEntity.setEstablishDate(establishDate);
extEntity.setBusinessScope(trimToNull(req.getBusinessScope()));
extEntity.setCompanyAddress(req.getAddress());
}
/**
* 保存食品经营许可扩展信息,不存在则新增,存在则更新。
*/
private void saveHygieneLicenseExt(String instanceId, CertificateAppHygieneLicenseUpdateReq req) {
CertificateHygieneLicenseExtEntity extEntity = queryHygieneLicenseExt(instanceId);
if (extEntity == null) {
extEntity = new CertificateHygieneLicenseExtEntity();
extEntity.setId(FtbUtil.getId());
extEntity.setInstanceId(instanceId);
extEntity.setEnabledMark(0);
extEntity.setBusinessProject(trimToNull(req.getBusinessProject()));
certificateHygieneLicenseExtMapper.insert(extEntity);
return;
}
extEntity.setBusinessProject(trimToNull(req.getBusinessProject()));
certificateHygieneLicenseExtMapper.update(null,new LambdaUpdateWrapper<CertificateHygieneLicenseExtEntity>()
.eq(CertificateHygieneLicenseExtEntity::getId, extEntity.getId())
.set(CertificateHygieneLicenseExtEntity::getBusinessProject,extEntity.getBusinessProject()));
}
/**
* 删除旧的门店自定义明细并保存新的明细列表。
*/
private void deleteAndSaveStoreCustomItems(String instanceId, List<CertificateInstanceItemReq> itemList) {
LambdaQueryWrapper<CertificateInstanceItemEntity> deleteWrapper = Wrappers.lambdaQuery();
deleteWrapper.eq(CertificateInstanceItemEntity::getInstanceId, instanceId);
certificateInstanceItemMapper.delete(deleteWrapper);
List<CertificateInstanceItemReq> safeList = itemList == null ? Collections.emptyList() : itemList;
long defaultSort = 1L;
for (CertificateInstanceItemReq itemReq : safeList) {
if (itemReq == null || StrUtil.isBlank(itemReq.getItemName())) {
continue;
}
CertificateInstanceItemEntity itemEntity = new CertificateInstanceItemEntity();
itemEntity.setId(FtbUtil.getId());
itemEntity.setInstanceId(instanceId);
itemEntity.setTemplateItemId(trimToNull(itemReq.getTemplateItemId()));
itemEntity.setItemName(trimToNull(itemReq.getItemName()));
itemEntity.setItemType(itemReq.getItemType());
itemEntity.setItemValue(trimToNull(itemReq.getItemValue()));
itemEntity.setSorts(itemReq.getSorts() == null ? defaultSort : itemReq.getSorts());
itemEntity.setEnabledMark(0);
certificateInstanceItemMapper.insert(itemEntity);
defaultSort++;
}
}
/**
* 根据实例ID查询营业执照扩展信息。
*/
private CertificateBusinessLicenseExtEntity queryBusinessLicenseExt(String instanceId) {
LambdaQueryWrapper<CertificateBusinessLicenseExtEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateBusinessLicenseExtEntity::getInstanceId, instanceId);
wrapper.eq(CertificateBusinessLicenseExtEntity::getEnabledMark, 0);
wrapper.orderByDesc(CertificateBusinessLicenseExtEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateBusinessLicenseExtEntity::getCreatorTime);
List<CertificateBusinessLicenseExtEntity> extList = certificateBusinessLicenseExtMapper.selectList(wrapper);
return CollUtil.isEmpty(extList) ? null : extList.get(0);
}
/**
* 根据实例ID查询食品许可扩展信息。
*/
private CertificateHygieneLicenseExtEntity queryHygieneLicenseExt(String instanceId) {
LambdaQueryWrapper<CertificateHygieneLicenseExtEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateHygieneLicenseExtEntity::getInstanceId, instanceId);
wrapper.eq(CertificateHygieneLicenseExtEntity::getEnabledMark, 0);
wrapper.orderByDesc(CertificateHygieneLicenseExtEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateHygieneLicenseExtEntity::getCreatorTime);
List<CertificateHygieneLicenseExtEntity> extList = certificateHygieneLicenseExtMapper.selectList(wrapper);
return CollUtil.isEmpty(extList) ? null : extList.get(0);
}
/**
* 查询有效证照实例。
*/
private CertificateInstanceEntity queryActiveEntity(String id) {
CertificateInstanceEntity entity = certificateInstanceMapper.selectById(id);
ServiceException.notNull(entity, "证照数据不存在");
ServiceException.isTrue(Integer.valueOf(0).equals(entity.getEnabledMark()), "证照数据不存在");
return entity;
}
/**
* 查询实例并校验证照类型。
*/
private CertificateInstanceEntity queryAndAssertType(String certificateInstanceId, CertificateTypeEnum expectType, String typeName) {
ServiceException.isTrue(StrUtil.isNotBlank(certificateInstanceId), "证照实例ID不能为空");
CertificateInstanceEntity entity = queryActiveEntity(StrUtil.trim(certificateInstanceId));
ServiceException.isTrue(StrUtil.equalsIgnoreCase(StrUtil.trim(entity.getCertificateType()), expectType.getType()),
"当前证照不是" + typeName);
return entity;
}
/**
* 解析日期字符串。
*/
private Date parseDate(String dateText, String fieldName) {
if (StrUtil.isBlank(dateText)) {
return null;
}
try {
return DateUtil.parseDate(StrUtil.trim(dateText));
} catch (Exception e) {
throw new ServiceException(fieldName + "格式不正确正确格式为yyyy-MM-dd");
}
}
/**
* 解析日期时间戳
*/
private Date parseDate(Long dateTimestamp, String fieldName) {
if (Objects.isNull(dateTimestamp) || dateTimestamp <= 0L) {
return null;
}
try {
return new Date(dateTimestamp);
} catch (Exception e) {
throw new ServiceException(fieldName + "格式不正确正确格式为yyyy-MM-dd");
}
}
/**
* 计算证照状态。
*/
private Integer calculateStatus(Integer isLongTerm, Date expireDate) {
if (Integer.valueOf(1).equals(isLongTerm)) {
return STATUS_NORMAL;
}
if (expireDate == null) {
return STATUS_MISSING;
}
Date today = DateUtil.beginOfDay(DateUtil.date());
Date target = DateUtil.beginOfDay(expireDate);
if (target.before(today)) {
return STATUS_EXPIRED;
}
long daysDiff = DateUtil.betweenDay(today, target, false);
if (daysDiff <= NEAR_EXPIRE_DAYS) {
return STATUS_NEAR_EXPIRE;
}
return STATUS_NORMAL;
}
/**
* 状态转中文名称。
*/
private String resolveStatusName(Integer status) {
if (Integer.valueOf(STATUS_MISSING).equals(status)) {
return "缺失";
}
if (Integer.valueOf(STATUS_EXPIRED).equals(status)) {
return "过期";
}
if (Integer.valueOf(STATUS_NEAR_EXPIRE).equals(status)) {
return "临期";
}
if (Integer.valueOf(STATUS_NORMAL).equals(status)) {
return "正常";
}
return "-";
}
/**
* 字符串去空格空串按null处理。
*/
private String trimToNull(String value) {
String trimValue = StrUtil.trim(value);
return StrUtil.isBlank(trimValue) ? null : trimValue;
}
}

View File

@@ -0,0 +1,906 @@
package jnpf.certificate.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.seata.spring.annotation.GlobalTransactional;
import jnpf.base.ActionResult;
import jnpf.certificate.mapper.CertificateBusinessLicenseExtMapper;
import jnpf.certificate.mapper.CertificateHygieneLicenseExtMapper;
import jnpf.certificate.mapper.CertificateInstanceItemMapper;
import jnpf.certificate.mapper.CertificateInstanceMapper;
import jnpf.certificate.service.CertificateStoreService;
import jnpf.exception.HandleException;
import jnpf.model.certificate.po.CertificateBusinessLicenseExtEntity;
import jnpf.model.certificate.po.CertificateHygieneLicenseExtEntity;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.certificate.po.CertificateInstanceItemEntity;
import jnpf.model.certificate.req.CertificateInstanceItemReq;
import jnpf.model.certificate.req.CertificateStoreSaveReq;
import jnpf.model.certificate.req.app.CertificateAppBusinessLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppHygieneLicenseUpdateReq;
import jnpf.model.certificate.req.app.CertificateAppStoreCustomUpdateReq;
import jnpf.model.certificate.vo.CertificateStoreAndCertificatesVO;
import jnpf.model.certificate.vo.CertificateStoreTabVO;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoEntity;
import jnpf.model.storecertificatephoto.po.StoreCertificatePhotoItemEntity;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoItemVO;
import jnpf.model.storecertificatephoto.vo.StoreCertificatePhotoVO;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.permission.OrganizeApi;
import jnpf.permission.dto.v2.organzie.SaveStoreDTO;
import jnpf.storecertificatephoto.helper.StoreCertificatePhotoHelper;
import jnpf.storecertificatephoto.mapper.StoreCertificatePhotoItemMapper;
import jnpf.storecertificatephoto.mapper.StoreCertificatePhotoMapper;
import jnpf.util.FtbUtil;
import jnpf.util.ServiceException;
import jnpf.util.UserProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.*;
import java.util.stream.Collectors;
/**
* 门店证照服务实现。
*/
@Slf4j
@Service
public class CertificateStoreServiceImpl implements CertificateStoreService {
private static final int STATUS_MISSING = 1;
private static final int STATUS_EXPIRED = 2;
private static final int STATUS_NEAR_EXPIRE = 3;
private static final int STATUS_NORMAL = 4;
private static final int SUBJECT_TYPE_STORE = 3;
private static final int NEAR_EXPIRE_DAYS = 30;
@Autowired
private OrganizeApi organizeApi;
@Autowired
private CertificateInstanceMapper certificateInstanceMapper;
@Autowired
private CertificateBusinessLicenseExtMapper certificateBusinessLicenseExtMapper;
@Autowired
private CertificateHygieneLicenseExtMapper certificateHygieneLicenseExtMapper;
@Autowired
private CertificateInstanceItemMapper certificateInstanceItemMapper;
@Autowired
private StoreCertificatePhotoMapper storeCertificatePhotoMapper;
@Autowired
private StoreCertificatePhotoItemMapper storeCertificatePhotoItemMapper;
@Autowired
private StoreCertificatePhotoHelper storeCertificatePhotoHelper;
/**
* 保存门店及门店证照。
* 先调用组织服务保存门店,再保存证照实例及扩展表。
*/
@Override
public String saveStoreAndCertificates(CertificateStoreSaveReq req) {
ServiceException.notNull(req, "请求参数不能为空");
SaveStoreDTO store = req.getStore();
ServiceException.notNull(store, "门店信息不能为空");
validCustomerCertificates(req.getStoreCustomCertificates());
String storeId = saveStore(store);
saveBusinessLicense(storeId,store.getDisabled(), req.getBusinessLicense());
saveHygieneLicense(storeId,store.getDisabled(), req.getHygieneLicense());
saveStoreCustomCertificates(storeId, store.getDisabled(),req.getStoreCustomCertificates());
return storeId;
}
private void validCustomerCertificates(Collection<CertificateAppStoreCustomUpdateReq> storeCustomCertificates) {
if(CollectionUtil.isEmpty(storeCustomCertificates)){
return;
}
for (CertificateAppStoreCustomUpdateReq req : storeCustomCertificates){
ServiceException.notNull(req.getTemplateId(), "门店自定义证照模板id不能为空");
}
}
@Override
public boolean deleteStore(String id) throws HandleException {
ActionResult<Boolean> actionResult = organizeApi.deleteStore(id);
if(Objects.isNull(actionResult) || actionResult.getCode() != 200){
log.error("deleteStore fail.id:{},actionResult:{}",id,actionResult);
throw new HandleException("组织服务异常");
}
if(!actionResult.getData()){
log.error("deleteStore organize fail.id:{}",id);
throw new HandleException("删除失败!");
}
certificateInstanceMapper.update(new CertificateInstanceEntity(),new LambdaUpdateWrapper<CertificateInstanceEntity>()
.eq(CertificateInstanceEntity::getSubjectId, id)
.set(CertificateInstanceEntity::getEnabledMark,1));
return true;
}
/**
* 根据门店ID查询门店及门店证照详情。
*/
@Override
public CertificateStoreAndCertificatesVO getStoreAndCertificates(String storeId) {
ServiceException.isTrue(StrUtil.isNotBlank(storeId), "门店ID不能为空");
String targetStoreId = StrUtil.trim(storeId);
CertificateStoreAndCertificatesVO result = new CertificateStoreAndCertificatesVO();
result.setBusinessLicense(queryBusinessLicense(targetStoreId));
result.setHygieneLicense(queryHygieneLicense(targetStoreId));
result.setStoreCustomCertificates(queryStoreCustomCertificates(targetStoreId));
return result;
}
@Override
public List<CertificateStoreTabVO> queryStoreCertificateTabList() {
List<CertificateStoreTabVO> result = new ArrayList<>();
List<CertificateTypeEnum> defaultTypeList = Arrays.asList(
CertificateTypeEnum.BUSINESS_LICENSE,
CertificateTypeEnum.HYGIENE_LICENSE
);
for (CertificateTypeEnum certificateType : defaultTypeList) {
CertificateStoreTabVO tabVO = new CertificateStoreTabVO();
tabVO.setLabel(certificateType.getLabel());
tabVO.setKey(certificateType.getType());
tabVO.setType(certificateType.getType());
result.add(tabVO);
}
List<StoreCertificatePhotoVO> templateList = queryStoreCustomTemplateList(1);
if (CollUtil.isEmpty(templateList)) {
return result;
}
for (StoreCertificatePhotoVO template : templateList) {
if (template == null || StrUtil.isBlank(template.getId()) || StrUtil.isBlank(template.getCertificateName())) {
continue;
}
CertificateStoreTabVO tabVO = new CertificateStoreTabVO();
tabVO.setLabel(StrUtil.trim(template.getCertificateName()));
tabVO.setKey(StrUtil.trim(template.getId()));
tabVO.setType(CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType());
tabVO.setStoreCustomConfig(template);
result.add(tabVO);
}
return result;
}
/**
* 调用组织服务保存门店并返回门店ID。
*/
private String saveStore(SaveStoreDTO storeDTO) {
ActionResult<String> actionResult;
try {
actionResult = organizeApi.saveStore(storeDTO);
} catch (Exception e) {
throw new ServiceException("保存门店失败: " + e.getMessage());
}
ServiceException.notNull(actionResult, "保存门店失败");
ServiceException.isTrue(ActionResult.success().getCode().equals(actionResult.getCode()),
StrUtil.blankToDefault(actionResult.getMsg(), "保存门店失败"));
String storeId = StrUtil.trim(actionResult.getData());
if (StrUtil.isBlank(storeId)) {
storeId = StrUtil.trim(storeDTO.getId());
}
ServiceException.isTrue(StrUtil.isNotBlank(storeId), "保存门店成功但未返回门店ID");
return storeId;
}
/**
* 批量查询启用中的门店自定义证照模板及明细。
*/
private List<StoreCertificatePhotoVO> queryStoreCustomTemplateList(Integer status) {
LambdaQueryWrapper<StoreCertificatePhotoEntity> templateWrapper = Wrappers.lambdaQuery();
templateWrapper.eq(StoreCertificatePhotoEntity::getEnabledMark, 0);
if(Objects.nonNull(status)){
templateWrapper.eq(StoreCertificatePhotoEntity::getStatus, status);
}
templateWrapper.orderByDesc(StoreCertificatePhotoEntity::getCreatorTime);
List<StoreCertificatePhotoEntity> templateEntities = storeCertificatePhotoMapper.selectList(templateWrapper);
if (CollUtil.isEmpty(templateEntities)) {
return Collections.emptyList();
}
List<String> templateIds = templateEntities.stream()
.map(StoreCertificatePhotoEntity::getId)
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.collect(Collectors.toList());
Map<String, List<StoreCertificatePhotoItemEntity>> itemMap = queryStoreCustomTemplateItemMap(templateIds);
return templateEntities.stream()
.map(entity -> buildStoreCustomTemplateVO(entity, itemMap))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* 按模板ID列表查询模板明细并按模板分组。
*/
private Map<String, List<StoreCertificatePhotoItemEntity>> queryStoreCustomTemplateItemMap(List<String> templateIds) {
if (CollUtil.isEmpty(templateIds)) {
return Collections.emptyMap();
}
LambdaQueryWrapper<StoreCertificatePhotoItemEntity> itemWrapper = Wrappers.lambdaQuery();
itemWrapper.in(StoreCertificatePhotoItemEntity::getPhotoId, templateIds);
itemWrapper.eq(StoreCertificatePhotoItemEntity::getEnabledMark, 0);
itemWrapper.orderByAsc(StoreCertificatePhotoItemEntity::getPhotoId)
.orderByAsc(StoreCertificatePhotoItemEntity::getItemType)
.orderByAsc(StoreCertificatePhotoItemEntity::getSorts);
List<StoreCertificatePhotoItemEntity> itemEntities = storeCertificatePhotoItemMapper.selectList(itemWrapper);
if (CollUtil.isEmpty(itemEntities)) {
return Collections.emptyMap();
}
return itemEntities.stream().collect(Collectors.groupingBy(StoreCertificatePhotoItemEntity::getPhotoId));
}
/**
* 构建门店自定义证照模板返回对象。
*/
private StoreCertificatePhotoVO buildStoreCustomTemplateVO(StoreCertificatePhotoEntity entity,
Map<String, List<StoreCertificatePhotoItemEntity>> itemMap) {
if (entity == null || StrUtil.isBlank(entity.getId())) {
return null;
}
StoreCertificatePhotoVO vo = StoreCertificatePhotoVO.convert(entity);
List<StoreCertificatePhotoItemEntity> itemEntities = itemMap.getOrDefault(entity.getId(), Collections.emptyList());
List<StoreCertificatePhotoItemVO> imageItemList = new ArrayList<>();
List<StoreCertificatePhotoItemVO> textItemList = new ArrayList<>();
for (StoreCertificatePhotoItemEntity itemEntity : itemEntities) {
StoreCertificatePhotoItemVO itemVO = StoreCertificatePhotoItemVO.convert(itemEntity);
if (itemVO == null) {
continue;
}
if (Integer.valueOf(1).equals(itemEntity.getItemType())) {
imageItemList.add(itemVO);
continue;
}
if (Integer.valueOf(2).equals(itemEntity.getItemType())) {
textItemList.add(itemVO);
}
}
imageItemList.sort(Comparator.comparing(item -> item.getSorts() == null ? Long.MAX_VALUE : item.getSorts()));
textItemList.sort(Comparator.comparing(item -> item.getSorts() == null ? Long.MAX_VALUE : item.getSorts()));
vo.setImageItemList(imageItemList);
vo.setTextItemList(textItemList);
return vo;
}
/**
* 保存营业执照实例和扩展数据。
*/
private void saveBusinessLicense(String storeId,Integer disable, CertificateAppBusinessLicenseUpdateReq req) {
if (req == null) {
req = new CertificateAppBusinessLicenseUpdateReq();
}
CertificateInstanceEntity instance = findOrCreateSingleInstance(
storeId,
req.getCertificateInstanceId(),
CertificateTypeEnum.BUSINESS_LICENSE.getType()
);
Date issueDate = parseDate(req.getIssueDate(), "营业执照发证日期");
Date expireDate = parseDate(req.getExpireDate(), "营业执照到期日期");
Date establishDate = parseDate(req.getEstablishDate(), "营业执照成立时间");
Integer isLongTerm = req.getIsLongTerm() == null ? 0 : req.getIsLongTerm();
if (Integer.valueOf(1).equals(isLongTerm)) {
expireDate = null;
}
instance.setIssueDate(issueDate);
instance.setExpireDate(expireDate);
if(Objects.nonNull(expireDate)){
instance.setExpireTimestamp(expireDate.getTime());
}else{
instance.setExpireTimestamp(0);
}
instance.setIsLongTerm(isLongTerm);
instance.setCertificateNo(trimToNull(req.getCertificateNo()));
instance.setCertificateImage(trimToNull(req.getCertificateImage()));
instance.setStatus(calculateStatus(instance.getIsLongTerm(), instance.getExpireDate()));
instance.setStoreDisable(buildStoreDisable(disable));
saveOrUpdateInstance(instance);
upsertBusinessLicenseExt(instance.getId(), req, establishDate);
}
private Integer buildStoreDisable(Integer disable) {
if(Objects.isNull(disable)){
return 0;
}
return disable == 1?1:0;
}
/**
* 查询门店营业执照详情。
*/
private CertificateAppBusinessLicenseUpdateReq queryBusinessLicense(String storeId) {
CertificateInstanceEntity instance = findLatestByStoreAndType(storeId, CertificateTypeEnum.BUSINESS_LICENSE.getType(),null);
if (instance == null) {
return null;
}
CertificateBusinessLicenseExtEntity extEntity = findBusinessExt(instance.getId());
CertificateAppBusinessLicenseUpdateReq result = new CertificateAppBusinessLicenseUpdateReq();
result.setCertificateInstanceId(instance.getId());
result.setCertificateImage(instance.getCertificateImage());
result.setCertificateNo(instance.getCertificateNo());
result.setIssueDate(formatDate(instance.getIssueDate()));
result.setExpireDate(formatDate(instance.getExpireDate()));
result.setIsLongTerm(instance.getIsLongTerm() == null ? 0 : instance.getIsLongTerm());
if (extEntity != null) {
result.setCompanyName(extEntity.getCompanyName());
result.setLegalRepresentative(extEntity.getLegalRepresentative());
result.setEstablishDate(formatDate(extEntity.getEstablishDate()));
result.setBusinessScope(extEntity.getBusinessScope());
result.setAddress(extEntity.getCompanyAddress());
}
return result;
}
/**
* 保存食品经营许可证实例和扩展数据。
*/
private void saveHygieneLicense(String storeId, Integer storeDisable,CertificateAppHygieneLicenseUpdateReq req) {
if (req == null) {
req = new CertificateAppHygieneLicenseUpdateReq();
}
CertificateInstanceEntity instance = findOrCreateSingleInstance(
storeId,
req.getCertificateInstanceId(),
CertificateTypeEnum.HYGIENE_LICENSE.getType()
);
Date issueDate = parseDate(req.getIssueDate(), "食品经营许可证发证日期");
Date expireDate = parseDate(req.getExpireDate(), "食品经营许可证到期日期");
instance.setIssueDate(issueDate);
instance.setExpireDate(expireDate);
if(Objects.nonNull(expireDate)){
instance.setExpireTimestamp(expireDate.getTime());
}
instance.setIsLongTerm(0);
instance.setCertificateImage(trimToNull(req.getCertificateImage()));
instance.setStatus(calculateStatus(instance.getIsLongTerm(), instance.getExpireDate()));
instance.setStoreDisable(buildStoreDisable(storeDisable));
saveOrUpdateInstance(instance);
upsertHygieneLicenseExt(instance.getId(), req);
}
/**
* 查询门店食品经营许可证详情。
*/
private CertificateAppHygieneLicenseUpdateReq queryHygieneLicense(String storeId) {
CertificateInstanceEntity instance = findLatestByStoreAndType(storeId, CertificateTypeEnum.HYGIENE_LICENSE.getType(),null);
if (instance == null) {
return null;
}
CertificateHygieneLicenseExtEntity extEntity = findHygieneExt(instance.getId());
CertificateAppHygieneLicenseUpdateReq result = new CertificateAppHygieneLicenseUpdateReq();
result.setCertificateInstanceId(instance.getId());
result.setCertificateImage(instance.getCertificateImage());
result.setIssueDate(formatDate(instance.getIssueDate()));
result.setExpireDate(formatDate(instance.getExpireDate()));
if (extEntity != null) {
result.setBusinessProject(extEntity.getBusinessProject());
}
return result;
}
/**
* 保存门店自定义证照实例和明细数据。
*/
private void saveStoreCustomCertificates(String storeId,Integer disabled, Collection<CertificateAppStoreCustomUpdateReq> reqList) {
if (CollUtil.isEmpty(reqList)) {
return;
}
Map<String,String> templateNameById = storeCertificatePhotoHelper.buildStoreCertificateIdAndNames(reqList.stream()
.map(CertificateAppStoreCustomUpdateReq::getTemplateId)
.collect(Collectors.toSet()),1);
for (CertificateAppStoreCustomUpdateReq req : reqList) {
if (req == null) {
continue;
}
//如果是禁用状态,则跳过
String templateId = req.getTemplateId();
int templateStatus = 1;
if(!templateNameById.containsKey(templateId)){
templateStatus = 0;
continue;
}
CertificateInstanceEntity instance = findOrCreateCustomInstance(storeId, req.getCertificateInstanceId(), templateId);
Date expireDate = parseDate(req.getExpireDate(), "门店自定义证照到期日期");
instance.setCertificateImage(buildCustomCertificateImage(req.getItemList()));
instance.setIssueDate(null);
instance.setExpireDate(expireDate);
if(Objects.nonNull(expireDate)){
instance.setExpireTimestamp(expireDate.getTime());
}
instance.setIsLongTerm(0);
instance.setStatus(calculateStatus(instance.getIsLongTerm(), instance.getExpireDate()));
instance.setTemplateStatus(templateStatus);
instance.setStoreDisable(buildStoreDisable(disabled));
saveOrUpdateInstance(instance);
rewriteCustomItems(instance.getId(), req.getItemList());
}
}
private String buildCustomCertificateImage(List<CertificateInstanceItemReq> itemList) {
if(CollectionUtil.isEmpty(itemList)){
return "";
}
for (CertificateInstanceItemReq item : itemList) {
if (item == null) {
continue;
}
if (item.getItemType() == 1) {
return item.getItemValue();
}
}
return "";
}
/**
* 查询门店自定义证照详情集合。
*/
private List<CertificateAppStoreCustomUpdateReq> queryStoreCustomCertificates(String storeId) {
LambdaQueryWrapper<CertificateInstanceEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateInstanceEntity::getEnabledMark, 0);
wrapper.eq(CertificateInstanceEntity::getSubjectType, SUBJECT_TYPE_STORE);
wrapper.eq(CertificateInstanceEntity::getSubjectId, storeId);
wrapper.eq(CertificateInstanceEntity::getCertificateType, CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType());
wrapper.orderByDesc(CertificateInstanceEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateInstanceEntity::getCreatorTime);
List<CertificateInstanceEntity> instanceList = certificateInstanceMapper.selectList(wrapper);
if (CollUtil.isEmpty(instanceList)) {
return Collections.emptyList();
}
Set<String> enabledTemplateIds = storeCertificatePhotoMapper.selectList(new LambdaQueryWrapper<StoreCertificatePhotoEntity>()
.select(StoreCertificatePhotoEntity::getId)
.in(StoreCertificatePhotoEntity::getId,instanceList.stream()
.map(CertificateInstanceEntity::getTemplateId)
.collect(Collectors.toSet()))
.eq(StoreCertificatePhotoEntity::getEnabledMark,0)
.eq(StoreCertificatePhotoEntity::getStatus,1))
.stream()
.map(StoreCertificatePhotoEntity::getId)
.collect(Collectors.toSet());
Map<String, List<CertificateInstanceItemEntity>> itemMap = queryStoreCustomItemMap(instanceList.stream()
.filter(instance->{
String templateId = instance.getTemplateId();
if(StringUtils.isBlank(templateId)){
return true;
}
return enabledTemplateIds.contains(templateId);
})
.map(CertificateInstanceEntity::getId)
.collect(Collectors.toSet()));
return instanceList.stream()
.filter(instance->{
String templateId = instance.getTemplateId();
if(StringUtils.isBlank(templateId)){
return true;
}
return enabledTemplateIds.contains(templateId);
})
.map(instance -> convertStoreCustomCertificate(instance, itemMap))
.collect(Collectors.toList());
}
/**
* 按证照实例ID批量查询自定义证照明细并分组。
*/
private Map<String, List<CertificateInstanceItemEntity>> queryStoreCustomItemMap(Collection<String> instanceIds) {
if (CollUtil.isEmpty(instanceIds)) {
return Collections.emptyMap();
}
LambdaQueryWrapper<CertificateInstanceItemEntity> itemWrapper = Wrappers.lambdaQuery();
itemWrapper.in(CertificateInstanceItemEntity::getInstanceId, instanceIds);
itemWrapper.eq(CertificateInstanceItemEntity::getEnabledMark, 0);
itemWrapper.orderByAsc(CertificateInstanceItemEntity::getInstanceId);
itemWrapper.orderByAsc(CertificateInstanceItemEntity::getSorts);
List<CertificateInstanceItemEntity> itemEntities = certificateInstanceItemMapper.selectList(itemWrapper);
if (CollUtil.isEmpty(itemEntities)) {
return Collections.emptyMap();
}
return itemEntities.stream()
.collect(Collectors.groupingBy(CertificateInstanceItemEntity::getInstanceId));
}
/**
* 根据证照实例ID或门店+证照类型查询单条实例,不存在则创建。
*/
private CertificateInstanceEntity findOrCreateSingleInstance(String storeId, String certificateInstanceId, String certificateType) {
CertificateInstanceEntity instance = findByInstanceId(certificateInstanceId, certificateType);
if (instance == null) {
instance = findLatestByStoreAndType(storeId, certificateType,null);
}
if (instance == null) {
instance = createNewInstance(storeId, certificateType);
}
normalizeStoreInstance(instance, storeId, certificateType);
return instance;
}
/**
* 根据证照实例ID查询自定义证照实例不存在则创建新实例。
*/
private CertificateInstanceEntity findOrCreateCustomInstance(String storeId, String certificateInstanceId, String templateId) {
String certificateType = CertificateTypeEnum.STORE_CUSTOM_CERTIFICATE.getType();
CertificateInstanceEntity instance = findByInstanceId(certificateInstanceId, certificateType);
if (instance == null) {
instance = createNewInstance(storeId, certificateType);
instance.setTemplateId(templateId);
}
normalizeStoreInstance(instance, storeId, certificateType);
return instance;
}
/**
* 按证照实例ID查询。
*/
private CertificateInstanceEntity findByInstanceId(String certificateInstanceId, String certificateType) {
if (StrUtil.isBlank(certificateInstanceId)) {
return null;
}
String instanceId = StrUtil.trim(certificateInstanceId);
CertificateInstanceEntity instance = certificateInstanceMapper.selectById(instanceId);
ServiceException.isTrue(instance != null && Integer.valueOf(0).equals(instance.getEnabledMark()), "证照实例不存在");
ServiceException.isTrue(StrUtil.equalsIgnoreCase(StrUtil.trim(instance.getCertificateType()), certificateType), "证照类型不匹配");
return instance;
}
/**
* 按门店和证照类型查询最新实例。
*/
private CertificateInstanceEntity findLatestByStoreAndType(String storeId, String certificateType,String templateId) {
LambdaQueryWrapper<CertificateInstanceEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateInstanceEntity::getEnabledMark, 0);
wrapper.eq(CertificateInstanceEntity::getSubjectType, SUBJECT_TYPE_STORE);
wrapper.eq(CertificateInstanceEntity::getSubjectId, storeId);
wrapper.eq(CertificateInstanceEntity::getCertificateType, certificateType);
if(StringUtils.isNotBlank(templateId)){
wrapper.eq(CertificateInstanceEntity::getTemplateId, templateId);
}
wrapper.orderByDesc(CertificateInstanceEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateInstanceEntity::getCreatorTime);
List<CertificateInstanceEntity> list = certificateInstanceMapper.selectList(wrapper);
return CollUtil.isEmpty(list) ? null : list.get(0);
}
/**
* 构建新证照实例。
*/
private CertificateInstanceEntity createNewInstance(String storeId, String certificateType) {
CertificateInstanceEntity instance = new CertificateInstanceEntity();
instance.setSubjectType(SUBJECT_TYPE_STORE);
instance.setSubjectId(storeId);
instance.setCertificateType(certificateType);
instance.setEnabledMark(0);
instance.setTemplateId("");
instance.setCreatorUserId(UserProvider.getLoginUserId());
instance.setCreatorTime(new Date());
instance.setLastModifyUserId(UserProvider.getLoginUserId());
instance.setLastModifyTime(new Date());
return instance;
}
/**
* 规范化门店证照实例字段。
*/
private void normalizeStoreInstance(CertificateInstanceEntity instance, String storeId, String certificateType) {
instance.setSubjectType(SUBJECT_TYPE_STORE);
instance.setSubjectId(storeId);
instance.setCertificateType(certificateType);
if (instance.getEnabledMark() == null) {
instance.setEnabledMark(0);
}
}
/**
* 保存或更新证照实例。
*/
private void saveOrUpdateInstance(CertificateInstanceEntity instance) {
if(StringUtils.isNotBlank(instance.getId())){
updateInstanceByWrapper(instance);
return;
}
instance.setId(FtbUtil.getId());
try {
certificateInstanceMapper.insert(instance);
} catch (DuplicateKeyException e) {
updateInstance(instance);
} catch (DataIntegrityViolationException e) {
if (isDuplicateKeyException(e)) {
updateInstance(instance);
return;
}
throw e;
} catch (Exception e) {
if (isDuplicateKeyException(e)) {
updateInstance(instance);
return;
}
throw e;
}
// catch (SQLIntegrityConstraintViolationException e){
// String msg = e.getMessage();
// if (msg.contains("Duplicate entry")){
// updateInstance(instance);
// return;
// }
// throw e;
// }
}
/**
* 判断异常链中是否包含唯一键冲突。
* 兼容 Seata/MyBatis 对 JDBC 异常的包装场景。
*/
private boolean isDuplicateKeyException(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
if (current instanceof DuplicateKeyException || current instanceof SQLIntegrityConstraintViolationException) {
return true;
}
String message = current.getMessage();
if (StringUtils.isNotBlank(message)
&& StringUtils.containsIgnoreCase(message, "Duplicate entry")
&& StringUtils.containsIgnoreCase(message, "for key")) {
return true;
}
current = current.getCause();
}
return false;
}
private void updateInstance(CertificateInstanceEntity instance) {
if(Objects.isNull(instance)){
return;
}
CertificateInstanceEntity dbInstance = findLatestByStoreAndType(instance.getSubjectId(), instance.getCertificateType(),instance.getTemplateId());
if(Objects.isNull(dbInstance)){
log.error("updateInstance fail.findLatestByStoreAndType null.instance:{}",instance);
return;
}
instance.setId(dbInstance.getId());
updateInstanceByWrapper(instance);
}
private void updateInstanceByWrapper(CertificateInstanceEntity instance) {
LambdaUpdateWrapper<CertificateInstanceEntity> updateWrapper = new LambdaUpdateWrapper<CertificateInstanceEntity>().eq(CertificateInstanceEntity::getId,instance.getId())
.set(CertificateInstanceEntity::getCertificateImage, instance.getCertificateImage())
.set(CertificateInstanceEntity::getIssueDate, instance.getIssueDate())
.set(CertificateInstanceEntity::getExpireDate, instance.getExpireDate())
.set(CertificateInstanceEntity::getExpireTimestamp, instance.getExpireTimestamp())
.set(CertificateInstanceEntity::getStatus, instance.getStatus())
.set(CertificateInstanceEntity::getLastModifyTime, instance.getLastModifyTime())
.set(CertificateInstanceEntity::getLastModifyUserId, instance.getLastModifyUserId())
.set(CertificateInstanceEntity::getEnabledMark, instance.getEnabledMark())
.set(CertificateInstanceEntity::getCreatorTime, instance.getCreatorTime())
.set(CertificateInstanceEntity::getCreatorUserId, instance.getCreatorUserId())
.set(CertificateInstanceEntity::getStoreDisable,instance.getStoreDisable());
Integer templateStatus = instance.getTemplateStatus();
if(Objects.nonNull(templateStatus)){
updateWrapper.set(CertificateInstanceEntity::getTemplateStatus,templateStatus);
}
int r = certificateInstanceMapper.update(null,updateWrapper);
if(r<=0){
log.error("saveOrUpdateInstance insert fail.update also fail.instance:{}",instance);
}
}
/**
* 保存营业执照扩展表。
*/
private void upsertBusinessLicenseExt(String instanceId, CertificateAppBusinessLicenseUpdateReq req, Date establishDate) {
CertificateBusinessLicenseExtEntity extEntity = new CertificateBusinessLicenseExtEntity();
extEntity.setId(FtbUtil.getId());
extEntity.setInstanceId(instanceId);
extEntity.setEnabledMark(0);
fillBusinessExt(extEntity, req, establishDate);
try {
certificateBusinessLicenseExtMapper.insert(extEntity);
}catch (DuplicateKeyException e){
int r = certificateBusinessLicenseExtMapper.update(null,new LambdaUpdateWrapper<CertificateBusinessLicenseExtEntity>()
.eq(CertificateBusinessLicenseExtEntity::getInstanceId,instanceId)
.eq(CertificateBusinessLicenseExtEntity::getEnabledMark,0)
.set(CertificateBusinessLicenseExtEntity::getCompanyName,extEntity.getCompanyName())
.set(CertificateBusinessLicenseExtEntity::getCompanyAddress,extEntity.getCompanyAddress())
.set(CertificateBusinessLicenseExtEntity::getLegalRepresentative,extEntity.getLegalRepresentative())
.set(CertificateBusinessLicenseExtEntity::getEstablishDate,extEntity.getEstablishDate())
.set(CertificateBusinessLicenseExtEntity::getBusinessScope,extEntity.getBusinessScope())
.set(CertificateBusinessLicenseExtEntity::getCreatorTime,extEntity.getCreatorTime())
.set(CertificateBusinessLicenseExtEntity::getCreatorUserId,extEntity.getCreatorUserId())
.set(CertificateBusinessLicenseExtEntity::getLastModifyTime,extEntity.getLastModifyTime())
.set(CertificateBusinessLicenseExtEntity::getLastModifyUserId,extEntity.getLastModifyUserId())
);
if(r <= 0){
log.error("upsertBusinessLicenseExt insert fail.update row count is:{}.instanceId:{},extEntity:{}",r,instanceId,extEntity);
}
}
}
/**
* 保存食品经营许可证扩展表。
*/
private void upsertHygieneLicenseExt(String instanceId, CertificateAppHygieneLicenseUpdateReq req) {
CertificateHygieneLicenseExtEntity extEntity = findHygieneExt(instanceId);
if (extEntity == null) {
extEntity = new CertificateHygieneLicenseExtEntity();
extEntity.setId(FtbUtil.getId());
extEntity.setInstanceId(instanceId);
extEntity.setEnabledMark(0);
extEntity.setBusinessProject(trimToNull(req.getBusinessProject()));
certificateHygieneLicenseExtMapper.insert(extEntity);
return;
}
extEntity.setBusinessProject(trimToNull(req.getBusinessProject()));
certificateHygieneLicenseExtMapper.updateById(extEntity);
}
/**
* 重写门店自定义证照明细。
*/
private void rewriteCustomItems(String instanceId, List<CertificateInstanceItemReq> itemList) {
LambdaQueryWrapper<CertificateInstanceItemEntity> deleteWrapper = Wrappers.lambdaQuery();
deleteWrapper.eq(CertificateInstanceItemEntity::getInstanceId, instanceId);
certificateInstanceItemMapper.delete(deleteWrapper);
List<CertificateInstanceItemReq> safeItemList = itemList == null ? Collections.emptyList() : itemList;
long sort = 1L;
for (CertificateInstanceItemReq itemReq : safeItemList) {
if (itemReq == null || StrUtil.isBlank(itemReq.getItemName())) {
continue;
}
CertificateInstanceItemEntity itemEntity = new CertificateInstanceItemEntity();
itemEntity.setId(FtbUtil.getId());
itemEntity.setInstanceId(instanceId);
itemEntity.setTemplateItemId(trimToNull(itemReq.getTemplateItemId()));
itemEntity.setItemName(trimToNull(itemReq.getItemName()));
itemEntity.setItemType(itemReq.getItemType());
itemEntity.setItemValue(trimToNull(itemReq.getItemValue()));
itemEntity.setSorts(itemReq.getSorts() == null ? sort : itemReq.getSorts());
itemEntity.setEnabledMark(0);
certificateInstanceItemMapper.insert(itemEntity);
sort++;
}
}
/**
* 查询营业执照扩展数据。
*/
private CertificateBusinessLicenseExtEntity findBusinessExt(String instanceId) {
LambdaQueryWrapper<CertificateBusinessLicenseExtEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateBusinessLicenseExtEntity::getInstanceId, instanceId);
wrapper.eq(CertificateBusinessLicenseExtEntity::getEnabledMark, 0);
wrapper.orderByDesc(CertificateBusinessLicenseExtEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateBusinessLicenseExtEntity::getCreatorTime);
List<CertificateBusinessLicenseExtEntity> extList = certificateBusinessLicenseExtMapper.selectList(wrapper);
return CollUtil.isEmpty(extList) ? null : extList.get(0);
}
/**
* 查询食品经营许可证扩展数据。
*/
private CertificateHygieneLicenseExtEntity findHygieneExt(String instanceId) {
LambdaQueryWrapper<CertificateHygieneLicenseExtEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(CertificateHygieneLicenseExtEntity::getInstanceId, instanceId);
wrapper.eq(CertificateHygieneLicenseExtEntity::getEnabledMark, 0);
wrapper.orderByDesc(CertificateHygieneLicenseExtEntity::getLastModifyTime);
wrapper.orderByDesc(CertificateHygieneLicenseExtEntity::getCreatorTime);
List<CertificateHygieneLicenseExtEntity> extList = certificateHygieneLicenseExtMapper.selectList(wrapper);
return CollUtil.isEmpty(extList) ? null : extList.get(0);
}
/**
* 将自定义证照实例转换为查询返回对象。
*/
private CertificateAppStoreCustomUpdateReq convertStoreCustomCertificate(
CertificateInstanceEntity instance,
Map<String, List<CertificateInstanceItemEntity>> itemMap) {
CertificateAppStoreCustomUpdateReq result = new CertificateAppStoreCustomUpdateReq();
result.setCertificateInstanceId(instance.getId());
result.setExpireDate(formatDate(instance.getExpireDate()));
result.setTemplateId(instance.getTemplateId());
List<CertificateInstanceItemEntity> itemEntities = itemMap.getOrDefault(instance.getId(), Collections.emptyList());
if (CollUtil.isEmpty(itemEntities)) {
return result;
}
result.setItemList(itemEntities.stream().map(entity -> {
CertificateInstanceItemReq itemReq = new CertificateInstanceItemReq();
itemReq.setTemplateItemId(entity.getTemplateItemId());
itemReq.setItemName(entity.getItemName());
itemReq.setItemType(entity.getItemType());
itemReq.setItemValue(entity.getItemValue());
itemReq.setSorts(entity.getSorts());
return itemReq;
}).filter(Objects::nonNull).collect(Collectors.toList()));
return result;
}
/**
* 填充营业执照扩展字段。
*/
private void fillBusinessExt(CertificateBusinessLicenseExtEntity extEntity, CertificateAppBusinessLicenseUpdateReq req, Date establishDate) {
extEntity.setCompanyName(trimToNull(req.getCompanyName()));
extEntity.setLegalRepresentative(trimToNull(req.getLegalRepresentative()));
extEntity.setEstablishDate(establishDate);
extEntity.setBusinessScope(trimToNull(req.getBusinessScope()));
extEntity.setCompanyAddress(req.getAddress());
extEntity.setCreatorTime(new Date());
extEntity.setCreatorUserId(UserProvider.getLoginUserId());
extEntity.setLastModifyTime(new Date());
extEntity.setLastModifyUserId(UserProvider.getLoginUserId());
}
/**
* 解析日期文本,格式为 yyyy-MM-dd。
*/
private Date parseDate(String dateText, String fieldName) {
if (StrUtil.isBlank(dateText)) {
return null;
}
try {
return DateUtil.parseDate(StrUtil.trim(dateText));
} catch (Exception e) {
throw new ServiceException(fieldName + "格式不正确正确格式为yyyy-MM-dd");
}
}
/**
* 日期格式化为 yyyy-MM-dd 字符串。
*/
private String formatDate(Date date) {
if (date == null) {
return null;
}
return DateUtil.formatDate(date);
}
/**
* 根据结束时间计算证照状态。
*/
private Integer calculateStatus(Integer isLongTerm, Date expireDate) {
if (Integer.valueOf(1).equals(isLongTerm)) {
return STATUS_NORMAL;
}
if (expireDate == null) {
return STATUS_MISSING;
}
Date today = DateUtil.beginOfDay(DateUtil.date());
Date target = DateUtil.beginOfDay(expireDate);
if (target.before(today)) {
return STATUS_EXPIRED;
}
long daysDiff = DateUtil.betweenDay(today, target, false);
if (daysDiff <= NEAR_EXPIRE_DAYS) {
return STATUS_NEAR_EXPIRE;
}
return STATUS_NORMAL;
}
/**
* 字符串去首尾空格空串转为null。
*/
private String trimToNull(String value) {
String trimValue = StrUtil.trim(value);
return StrUtil.isBlank(trimValue) ? null : trimValue;
}
}

View File

@@ -0,0 +1,17 @@
package jnpf.certificate.util;
public class CertificateStatusUtils {
public static String buildStatusName(Integer status){
switch (status){
case 1:
return "缺失";
case 2:
return "过期";
case 3:
return "临期";
case 4:
return "正常";
}
return "";
}
}

View File

@@ -0,0 +1,18 @@
package jnpf.certificate.util;
import jnpf.permission.eum.v2.FtbStoreTagEnum;
import java.util.Objects;
public class StoreTypeUtils {
public static String buildStoreType(String storeType) {
if(Objects.isNull(storeType)){
return "";
}
FtbStoreTagEnum storeTypeEnum = FtbStoreTagEnum.fromName(storeType);
if(Objects.isNull(storeTypeEnum)){
return "";
}
return storeTypeEnum.getLabel();
}
}

View File

@@ -0,0 +1,17 @@
package jnpf.certificate.util;
public class SubjectNameUtils {
//主体类型1为员工、2为公司、3为门店
public static String getSubjectName(Integer subjectType) {
switch (subjectType){
case 1:
return "员工";
case 2:
return "公司";
case 3:
return "门店";
default:
return "";
}
}
}

View File

@@ -0,0 +1,14 @@
package jnpf.certificate.util;
import jnpf.model.enums.StaffWorkerStatus;
import jnpf.util.StringUtil;
public class WorkerStatusUtils {
//301、预入职 302、试用 303、正式 304、待离职 305 离职
public static String buildStatusName(String workerStatus) {
if(StringUtil.isBlank(workerStatus)){
return "";
}
return StaffWorkerStatus.getWorkerStatusNameByValue(workerStatus);
}
}