Ver Fonte

feature: 发新卡的初始化业务实现

luo.yibo@datuai.com há 1 ano atrás
pai
commit
3ce3dfdd68

+ 6 - 2
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -96,7 +96,7 @@ public interface CacheNames {
     /**
      * 用户卡片
      */
-    String PT_USER_CARD= "pt_user_card";
+    String PT_USER_CARD = "pt_user_card";
 
     /**
      * 营业时段/餐类
@@ -120,9 +120,13 @@ public interface CacheNames {
     /**
      * 卡流水号
      */
-    String  CARD_NO = "card_no";
+    String CARD_NO = "card_no";
     /**
      * 一卡通账户
      */
     String PT_USER_ACCOUNT = "pt_user_account";
+    /**
+     * 工作站
+     */
+    String T_PT_WORKSTATION = "t_pt_workstation";
 }

+ 100 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/DefaultConstants.java

@@ -0,0 +1,100 @@
+package org.dromara.common.core.constant;
+
+/**
+ * name: DefaultConstants
+ * package: org.dromara.server.common.constant
+ * description: 缺省数据常量
+ * date: 2024-10-23 19:56:12 19:56
+ *
+ * @author luoyibo
+ * @version 0.1
+ * @since JDK 1.8
+ */
+public interface DefaultConstants {
+    /**
+     * 教职工岗位编码
+     */
+    String TEACHER_CODE="12";
+    /**
+     * 学员岗位编码
+     */
+    String TRAINEE_CODE="14";
+    /**
+     * 研究生岗位编码
+     */
+    String GRADUATE_CODE="16";
+
+    /**
+     * 租户Id
+     */
+    String TENANT_ID = "20200813044411";
+    /**
+     * 岗位Id
+     */
+    Long POST_ID = 111L;
+
+    /**
+     * 登录密码
+     */
+    String LOGIN_PASSWORD = "dtSchool@2024";
+    /**
+     * 日期格式
+     */
+    String DATE_FORMAT = "yyyy-MM-dd";
+
+    /**
+     * 日期时间格式
+     */
+    String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * 时间格式
+     */
+    String TIME_FORMAT = "HH:mm:ss";
+
+    /**
+     * 消费机接收的时间格式
+     */
+    String TERM_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
+    /**
+     * 父部门Id(学校)
+     */
+    Long PARENT_DEPT_ID = 100L;
+
+    /**
+     * 研究生年份父部门Id(研究生部)
+     */
+    Long GRADUATE_PARENT_DEPT_ID = 1850788795766460417L;
+    /**
+     * 培训班年份父部门Id(培训班)
+     */
+    Long TRAIN_PARENT_DEPT_ID = 1850788897130205186L;
+    /**
+     * 部门类型代码
+     */
+    String DEPT_DEPT_TYPE = "03";
+
+    /**
+     * 年份对应部门类型代码
+     */
+    String YEAR_DEPT_TYPE = "04";
+    /**
+     * 培训班级对应部门类型代码
+     */
+    String CLASS_DEPT_TYPE = "05";
+    /**
+     * 研究生班级对应的部门类型代码
+     */
+    String GRADUATE_DEPT_TYPE = "06";
+    /**
+     * 教职工默认角色
+     */
+    Long TEACHER_ROLE_ID = 1833306814969163778L;
+    /**
+     * 学员默认角色
+     */
+    Long TRAIN_ROLE_ID = 1833306897299156993L;
+    /**
+     * 研究生默认角色
+     */
+    Long GRADUATE_ROLE_ID = 1844275170961874946L;
+}

+ 3 - 12
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java

@@ -1,9 +1,6 @@
 package org.dromara.common.core.exception;
 
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 import java.io.Serial;
 
@@ -24,6 +21,7 @@ public final class ServiceException extends RuntimeException {
     /**
      * 错误码
      */
+    @Getter
     private Integer code;
 
     /**
@@ -34,6 +32,7 @@ public final class ServiceException extends RuntimeException {
     /**
      * 错误明细,内部调试错误
      */
+    @Getter
     private String detailMessage;
 
     public ServiceException(String message) {
@@ -45,19 +44,11 @@ public final class ServiceException extends RuntimeException {
         this.code = code;
     }
 
-    public String getDetailMessage() {
-        return detailMessage;
-    }
-
     @Override
     public String getMessage() {
         return message;
     }
 
-    public Integer getCode() {
-        return code;
-    }
-
     public ServiceException setMessage(String message) {
         this.message = message;
         return this;

+ 5 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/domain/bo/PtWorkstationBo.java

@@ -94,5 +94,10 @@ public class PtWorkstationBo extends BaseEntity {
      */
     private String remark;
 
+    /**
+     * 租户Id
+     */
+    private String tenantId;
+
 
 }

+ 7 - 5
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/domain/vo/PtWorkstationVo.java

@@ -1,16 +1,15 @@
 package org.dromara.backstage.basics.domain.vo;
 
-import org.dromara.backstage.basics.domain.PtWorkstation;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
-import org.dromara.common.excel.annotation.ExcelDictFormat;
-import org.dromara.common.excel.convert.ExcelDictConvert;
 import io.github.linpeilie.annotations.AutoMapper;
 import lombok.Data;
+import org.dromara.backstage.basics.domain.PtWorkstation;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
 
 import java.io.Serial;
 import java.io.Serializable;
-import java.util.Date;
 
 
 
@@ -107,6 +106,9 @@ public class PtWorkstationVo implements Serializable {
      */
     @ExcelProperty(value = "工作站说明")
     private String remark;
-
+    /**
+     * 租户Id
+     */
+    private String tenantId;
 
 }

+ 16 - 3
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/service/IPtWorkstationService.java

@@ -1,10 +1,9 @@
 package org.dromara.backstage.basics.service;
 
-import org.dromara.backstage.basics.domain.PtWorkstation;
-import org.dromara.backstage.basics.domain.vo.PtWorkstationVo;
 import org.dromara.backstage.basics.domain.bo.PtWorkstationBo;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.backstage.basics.domain.vo.PtWorkstationVo;
 import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
 
 import java.util.Collection;
 import java.util.List;
@@ -66,4 +65,18 @@ public interface IPtWorkstationService {
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 根据工作站业务对象查询工作站信息
+     * @param bo 工作站业务对象
+     * @return 工作站信息
+     */
+    PtWorkstationVo queryVoByBo(PtWorkstationBo bo);
+    /**
+     * 根据工作站编号与所属租户Id查询工作站信息
+     * @param stationNumb 工作站编号
+     * @param tenantId 租户Id
+     * @return 工作站信息
+     */
+    PtWorkstationVo queryVoByNumber(Long stationNumb,String tenantId);
 }

+ 1 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/service/impl/PtParameterServiceImpl.java

@@ -157,5 +157,6 @@ public class PtParameterServiceImpl implements IPtParameterService {
         if(count > 0){
             throw new ServiceException("系统参数编码已存在!");
         }
+
     }
 }

+ 64 - 17
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/service/impl/PtWorkstationServiceImpl.java

@@ -1,24 +1,29 @@
 package org.dromara.backstage.basics.service.impl;
 
-import org.dromara.common.core.exception.ServiceException;
-import org.dromara.common.core.utils.MapstructUtils;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.common.mybatis.core.page.PageQuery;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Service;
+import org.dromara.backstage.basics.domain.PtWorkstation;
 import org.dromara.backstage.basics.domain.bo.PtWorkstationBo;
 import org.dromara.backstage.basics.domain.vo.PtWorkstationVo;
-import org.dromara.backstage.basics.domain.PtWorkstation;
 import org.dromara.backstage.basics.mapper.PtWorkstationMapper;
 import org.dromara.backstage.basics.service.IPtWorkstationService;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.springframework.stereotype.Service;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Collection;
+import java.util.Objects;
 
 /**
  * 工作站Service业务层处理
@@ -39,7 +44,7 @@ public class PtWorkstationServiceImpl implements IPtWorkstationService {
      * @return 工作站
      */
     @Override
-    public PtWorkstationVo queryById(Long stationId){
+    public PtWorkstationVo queryById(Long stationId) {
         return baseMapper.selectVoById(stationId);
     }
 
@@ -76,6 +81,8 @@ public class PtWorkstationServiceImpl implements IPtWorkstationService {
         lqw.like(StringUtils.isNotBlank(bo.getStationName()), PtWorkstation::getStationName, bo.getStationName());
         lqw.eq(StringUtils.isNotBlank(bo.getStationType()), PtWorkstation::getStationType, bo.getStationType());
         lqw.eq(StringUtils.isNotBlank(bo.getOnLine()), PtWorkstation::getOnLine, bo.getOnLine());
+        lqw.eq(StringUtils.isNotBlank(bo.getTenantId()), PtWorkstation::getTenantId, bo.getTenantId());
+        lqw.eq(bo.getStationNumb() != null, PtWorkstation::getStationNumb, bo.getStationNumb());
         return lqw;
     }
 
@@ -112,14 +119,14 @@ public class PtWorkstationServiceImpl implements IPtWorkstationService {
     /**
      * 保存前的数据校验
      */
-    private void validEntityBeforeSave(PtWorkstation entity){
-        //编号重复校验
+    private void validEntityBeforeSave(PtWorkstation entity) {
+        // 编号重复校验
         Long count = baseMapper.selectCount(Wrappers.<PtWorkstation>lambdaQuery()
-            .eq(PtWorkstation::getStationNumb, entity.getStationNumb())
-            .ne(entity.getStationId() != null, PtWorkstation::getStationId, entity.getStationId())
+                                                .eq(PtWorkstation::getStationNumb, entity.getStationNumb())
+                                                .ne(entity.getStationId() != null, PtWorkstation::getStationId, entity.getStationId())
         );
 
-        if(count > 0){
+        if (count > 0) {
             throw new ServiceException("工作站编号已存在!");
         }
     }
@@ -133,9 +140,49 @@ public class PtWorkstationServiceImpl implements IPtWorkstationService {
      */
     @Override
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
-        if(isValid){
-            //TODO 做一些业务上的校验,判断是否需要校验
+        if (isValid) {
+            // TODO 做一些业务上的校验,判断是否需要校验
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    /**
+     * 根据工作站业务对象查询工作站信息
+     *
+     * @param bo 工作站业务对象
+     * @return 工作站信息
+     */
+    @Override
+    public PtWorkstationVo queryVoByBo(PtWorkstationBo bo) {
+        LambdaQueryWrapper<PtWorkstation> lqw = buildQueryWrapper(bo);
+        PtWorkstationVo vo = baseMapper.selectVoOne(lqw);
+        if (ObjUtil.isNotEmpty(vo)) {
+            List<PtWorkstationVo> redisList = RedisUtils.getCacheList(CacheNames.T_PT_WORKSTATION);
+            if (CollectionUtil.isNotEmpty(redisList)) {
+                RedisUtils.addCacheList(CacheNames.T_PT_WORKSTATION, vo);
+            } else {
+                redisList.add(vo);
+                RedisUtils.setCacheList(CacheNames.T_PT_WORKSTATION, redisList);
+            }
+        }
+        return vo;
+    }
+
+    @Override
+    public PtWorkstationVo queryVoByNumber(Long stationNumb, String tenantId) {
+        PtWorkstationVo vo;
+        List<PtWorkstationVo> redisList = RedisUtils.getCacheList(CacheNames.T_PT_WORKSTATION);
+        if (CollectionUtil.isNotEmpty(redisList)) {
+            vo = redisList.stream().filter(
+                p -> Objects.equals(p.getStationNumb(), stationNumb) && Objects.equals(p.getTenantId(), tenantId)).findFirst().orElse(null);
+            if (ObjUtil.isNotNull(vo)) {
+                return vo;
+            }
+        }
+
+        PtWorkstationBo bo = new PtWorkstationBo();
+        bo.setStationNumb(stationNumb);
+        bo.setTenantId(tenantId);
+        return this.queryVoByBo(bo);
+    }
 }

+ 242 - 14
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/card/CardBusiness.java

@@ -1,19 +1,28 @@
 package org.dromara.backstage.business.card;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.backstage.basics.domain.vo.PtWorkstationVo;
+import org.dromara.backstage.basics.service.IPtParameterService;
+import org.dromara.backstage.basics.service.IPtWorkstationService;
 import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
+import org.dromara.backstage.cardCenter.domain.vo.PtCardVo;
 import org.dromara.backstage.cardCenter.service.IPtCardService;
 import org.dromara.backstage.payment.domain.bo.PtBagBo;
 import org.dromara.backstage.payment.domain.vo.PtBagVo;
 import org.dromara.backstage.payment.domain.vo.PtUserAccountVo;
 import org.dromara.backstage.payment.service.IPtBagService;
 import org.dromara.backstage.payment.service.IPtUserAccountService;
+import org.dromara.common.core.constant.DefaultConstants;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.ResultInfo;
 import org.dromara.common.core.enums.CardOpenEnum;
 import org.dromara.common.core.enums.CardStatusEnum;
+import org.dromara.common.core.enums.ResultCodeEnum;
 import org.dromara.common.core.enums.UserAccountStatusEnum;
 import org.springframework.stereotype.Service;
 
@@ -38,24 +47,27 @@ public class CardBusiness {
     private final IPtUserAccountService userAccountService;
     private final CardNoBusiness cardNoBusiness;
     private final IPtBagService ptBagService;
-    public R<String> openVirtualCard(PtCardBo cardBo){
+    private final IPtWorkstationService workstationService;
+    private final IPtParameterService ptParameterService;
+
+    public R<String> openVirtualCard(PtCardBo cardBo) {
         String resultMsg;
-        //验证是否存在开通虚拟卡的账户
+        // 验证是否存在开通虚拟卡的账户
         PtUserAccountVo userAccountVo = userAccountService.queryById(cardBo.getUserId());
-        if(userAccountVo == null){
+        if (userAccountVo == null) {
             resultMsg = MessageFormat.format("[开通虚拟卡]-[失败]-[无此账户,账户Id:{0}]", cardBo.getUserId());
             log.info(resultMsg);
             return R.fail(resultMsg);
         }
-        if(Integer.parseInt(userAccountVo.getAccountStatus())!= UserAccountStatusEnum.IS_OPEN.code()){
+        if (Integer.parseInt(userAccountVo.getAccountStatus()) != UserAccountStatusEnum.IS_OPEN.code()) {
             resultMsg = MessageFormat.format("[开通虚拟卡]-[失败]-[此账户尚未开户,账户Id:{0}]", cardBo.getUserId());
             log.info(resultMsg);
             return R.fail(resultMsg);
         }
-        //检查当前账户是否存在正常、挂失或冻结的卡片,如果有这些卡片,表明账户是发过卡的,无须再开通虚拟卡
+        // 检查当前账户是否存在正常、挂失或冻结的卡片,如果有这些卡片,表明账户是发过卡的,无须再开通虚拟卡
         String cardInfo = ptCardService.selectAccountCardByIds(cardBo.getUserId().toString());
-        if(CardOpenEnum.NO.message().equals(cardInfo)){
-            //没有卡片,开通虚拟卡
+        if (CardOpenEnum.NO.message().equals(cardInfo)) {
+            // 没有卡片,开通虚拟卡
             PtCardBo addBo = BeanUtil.copyProperties(cardBo, PtCardBo.class);
             addBo.setMainCard("Y");
             addBo.setStatus(CardStatusEnum.NORMAL.code().toString());
@@ -63,14 +75,14 @@ public class CardBusiness {
             addBo.setFactoryId(0L);
             addBo.setChangeTime(DateUtil.date());
 
-            if(ptCardService.insertByBo(addBo)){
-                //写卡片表成功,检查一下是否还需要初始化钱包表
+            if (ptCardService.insertByBo(addBo)) {
+                // 写卡片表成功,检查一下是否还需要初始化钱包表
                 PtBagBo bagBo = new PtBagBo();
                 bagBo.setUserId(cardBo.getUserId());
                 List<PtBagVo> bagVos = ptBagService.queryList(bagBo);
-                if(bagVos==null || bagVos.isEmpty()){
-                    //如果没有钱包数据,初始化钱包数据
-                    if(ptBagService.initAccountBag(cardBo.getUserId())){
+                if (bagVos == null || bagVos.isEmpty()) {
+                    // 如果没有钱包数据,初始化钱包数据
+                    if (ptBagService.initAccountBag(cardBo.getUserId())) {
                         resultMsg = MessageFormat.format("[开通虚拟卡]-[成功]-[开卡账户Id:{0}]", cardBo.getUserId());
                         return R.ok(resultMsg);
                     } else {
@@ -79,7 +91,7 @@ public class CardBusiness {
                         return R.fail(resultMsg);
                     }
                 } else {
-                    //已有钱包数据,不需要再初始化钱包,直接返回成功
+                    // 已有钱包数据,不需要再初始化钱包,直接返回成功
                     resultMsg = MessageFormat.format("[开通虚拟卡]-[成功]-[开卡账户Id:{0}]", cardBo.getUserId());
                     return R.ok(resultMsg);
                 }
@@ -89,9 +101,225 @@ public class CardBusiness {
                 return R.fail(resultMsg);
             }
         } else {
-            //已开过卡了,无须再开虚拟卡,直接返回成功
+            // 已开过卡了,无须再开虚拟卡,直接返回成功
             resultMsg = MessageFormat.format("[开通虚拟卡]-[成功]-[账户已有卡片,开卡账户Id:{0}]", cardBo.getUserId());
             return R.ok(resultMsg);
         }
     }
+
+    public R<ResultInfo> initEntityCard(PtCardBo cardBo) {
+        PtUserAccountVo userAccountVo = new PtUserAccountVo();
+        PtWorkstationVo workstationVo = new PtWorkstationVo();
+        // 基本的参数校验
+        R<ResultInfo> result = checkParam(cardBo, userAccountVo, workstationVo);
+        if (R.isError(result)) {
+            return result;
+        }
+        // 发卡业务逻辑校验
+        // 1.物理卡号校验
+        result = checkFactoryId(cardBo.getFactoryId());
+        if (R.isError(result)) {
+            return result;
+        }
+        // 用户持有卡片情况校验
+        PtCardBo queryBo = new PtCardBo();
+        queryBo.setUserId(userAccountVo.getUserId());
+        List<PtCardVo> uesrCardList = ptCardService.queryList(queryBo);
+        result = switch (cardBo.getOperateType()) {
+            // 发卡
+            case 2 -> checkUserNewCard(uesrCardList, cardBo);
+            // 换卡
+            case 3 -> checkUserChangeCard(uesrCardList, cardBo);
+            // 补卡
+            case 4 -> checkUserReIssueCard(uesrCardList, cardBo);
+            default -> R.ok(new ResultInfo(ResultCodeEnum.SUCCESS));
+        };
+        // 所有逻辑校验通过,入库
+        cardBo.setCardNo(cardNoBusiness.getCardNo());
+        return result;
+    }
+
+    private R<ResultInfo> checkParam(PtCardBo cardBo, PtUserAccountVo userAccountVo, PtWorkstationVo workstationVo) {
+        Long factoryId = cardBo.getFactoryId();
+        Long userId = cardBo.getUserId();
+        String mainCard = cardBo.getMainCard();
+        Long stationNumb = cardBo.getStationNumb();
+        Integer operateType = cardBo.getOperateType();
+        String tenantId = cardBo.getTenantId() == null ? DefaultConstants.TENANT_ID : cardBo.getTenantId();
+
+        if (ObjectUtil.isEmpty(factoryId) || factoryId == 0) {
+            return R.fail(new ResultInfo(ResultCodeEnum.PARAM_IS_INVALID, "物理卡号必须大于0"));
+        }
+        if (ObjectUtil.isEmpty(userId) || userId == 0) {
+            return R.fail(new ResultInfo(ResultCodeEnum.PARAM_IS_INVALID, "人员Id必须大于0"));
+        }
+        if (ObjectUtil.isEmpty(mainCard)) {
+            return R.fail(new ResultInfo(ResultCodeEnum.PARAM_IS_INVALID, "需要设置主副卡类型:Y为主卡,N为副卡"));
+        }
+        if (ObjectUtil.isEmpty(stationNumb) || stationNumb == 0) {
+            return R.fail(new ResultInfo(ResultCodeEnum.PARAM_IS_INVALID, "工作站编号必须大于0"));
+        }
+        if (ObjectUtil.isEmpty(operateType)) {
+            return R.fail(new ResultInfo(ResultCodeEnum.PARAM_IS_INVALID, "操作类型必须为[2-发卡|3-换卡|4-补卡]中至少一种"));
+        }
+        PtUserAccountVo accountVo = userAccountService.queryById(userId);
+        if (ObjectUtil.isEmpty(accountVo)) {
+            return R.fail(new ResultInfo(ResultCodeEnum.DATA_NOT_FOUND, MessageFormat.format("没有和Id[{0}]对应的人员信息", userId)));
+        }
+        PtWorkstationVo workstation = workstationService.queryVoByNumber(stationNumb, tenantId);
+        if (ObjectUtil.isEmpty(workstation)) {
+            return R.fail(new ResultInfo(ResultCodeEnum.DATA_NOT_FOUND, String.format("没有和编号[%d]对应的工作站", stationNumb)));
+        }
+
+        BeanUtil.copyProperties(accountVo, userAccountVo);
+        BeanUtil.copyProperties(workstation, workstationVo);
+
+        return R.ok(new ResultInfo(ResultCodeEnum.SUCCESS));
+    }
+
+    /**
+     * 检查物理卡号的使用情况
+     * 1.物理卡号在系统不存在,可以使用
+     * 2.物理卡号存在,系统参数不支持复用卡片则不可使用
+     * 3.物理卡号存在,系统参数支持复用,但此卡在系统中的状态为正常也不可使用
+     *
+     * @param factoryId 物理卡号
+     * @return 检查结果
+     */
+    private R<ResultInfo> checkFactoryId(Long factoryId) {
+        // 卡片可复用
+        String isReuse = "1";
+        // 根据物理卡号查询发卡信息
+        PtCardBo bo = new PtCardBo();
+        bo.setFactoryId(factoryId);
+        List<PtCardVo> cardVos = ptCardService.queryList(bo);
+        if (CollectionUtil.isNotEmpty(cardVos)) {
+            // 物理卡号有使用时获取卡片否可复用的系统参数
+            String reuseCard = ptParameterService.selectParamByCode("REUSE_CARD");
+            if (ObjectUtil.equal(reuseCard, isReuse)) {
+                // 可复用,检查当前卡片是否有状态为正常的对应卡片,如有则不能再使用
+                if (cardVos.stream().anyMatch(p -> ObjectUtil.equal(p.getStatus(), CardStatusEnum.NORMAL.code()))) {
+                    new ResultInfo(ResultCodeEnum.DATA_ALREADY_EXISTED, MessageFormat.format("此卡在正常使用,物理卡号[{0}],不能重新发卡", factoryId));
+                }
+            } else {
+                // 不能复用
+                return R.fail(
+                    new ResultInfo(ResultCodeEnum.DATA_ALREADY_EXISTED, MessageFormat.format("物理卡号[{0}]已使用过,系统不支持卡片复用", factoryId)));
+            }
+        }
+        return R.ok(new ResultInfo(ResultCodeEnum.SUCCESS));
+    }
+
+    /**
+     * 检查用户的新卡情况
+     * 1.如果当前用户没有卡片,可以发卡
+     * 2.如果当前用户有正常的主卡,而且是将要发的主卡,则不能发卡
+     * 3.如果当前用户是有虚拟卡了,则则虚拟卡转为实体卡(将虚拟卡对应的cardI和cardNo赋给新卡)
+     *
+     * @param userCardList 用户持卡列表
+     * @param cardBo       发卡业务对象
+     * @return 检查结果
+     */
+    private R<ResultInfo> checkUserNewCard(List<PtCardVo> userCardList, PtCardBo cardBo) {
+        if (CollectionUtil.isEmpty(userCardList)) {
+            // 如人员已有正常的主卡,不能再发主卡
+            if (userCardList.stream().anyMatch(p -> ObjectUtil.equals(p.getMainCard(), "Y")
+                                                        && ObjectUtil.equals(p.getStatus(), CardStatusEnum.NORMAL.code())
+                                                        && p.getFactoryId() > 0 && cardBo.getMainCard().equals("Y"))) {
+                return R.fail(new ResultInfo(ResultCodeEnum.DATA_ALREADY_EXISTED, MessageFormat.format("Id为[{0}]的人员已有正常主卡,无法再发新卡",
+                                                                                                       cardBo.getUserId())));
+            }
+            // 如果人员已发虚拟卡,此时将虚拟卡转为实体卡
+            userCardList.stream().filter(p -> ObjectUtil.equals(p.getMainCard(), "Y")
+                                                  && ObjectUtil.equals(p.getStatus(), CardStatusEnum.NORMAL.code())
+                                                  && p.getFactoryId() == 0).findFirst().ifPresent(k -> {
+                cardBo.setCardNo(k.getCardNo());
+                cardBo.setCardId(k.getCardId());
+                cardBo.setStatus(k.getStatus());
+                cardBo.setMainCard(k.getMainCard());
+            });
+        }
+
+        return R.ok(new ResultInfo(ResultCodeEnum.SUCCESS));
+    }
+
+    private R<ResultInfo> checkUserChangeCard(List<PtCardVo> userCardList, PtCardBo cardBo) {
+        Long userId = cardBo.getUserId();
+        if (CollectionUtil.isEmpty(userCardList)) {
+            return R.fail(new ResultInfo(ResultCodeEnum.DATA_NOT_FOUND, MessageFormat.format("Id为[{0}]的人员没有发过卡,无法换卡", userId)));
+        }
+        Long oldCardNo = cardBo.getOldCardNo();
+        Long oldFactoryId = cardBo.getOldFactoryId();
+        PtCardVo oldCardVo = new PtCardVo();
+        // 换卡时,必须有原卡的发卡记录
+        R<ResultInfo> result = checkUserOldCard(userCardList, oldCardNo, oldFactoryId, oldCardVo);
+        if (R.isError(result)) {
+            return R.fail(MessageFormat.format("无法换卡,{0}", result.getData()));
+        }
+        // 换卡时,原卡必须是正常状态
+        if (ObjectUtil.notEqual(oldCardVo.getStatus(), CardStatusEnum.NORMAL.code())) {
+            return R.fail(
+                new ResultInfo(ResultCodeEnum.DATA_NOT_FOUND,
+                               MessageFormat.format("物理卡号[{0}]和卡流水号[{1}]对应旧卡是正常卡片", oldFactoryId, oldCardNo)));
+        }
+        // 将原卡的信息复制到新卡 卡类、有效期和是否主卡
+        userCardList.stream().filter(
+            p -> ObjectUtil.equals(p.getCardNo(), oldCardNo) && ObjectUtil.equals(p.getFactoryId(), oldFactoryId)).findFirst().ifPresent(k -> {
+            cardBo.setCardType(k.getCardType());
+            cardBo.setLifespan(k.getLifespan());
+            cardBo.setMainCard(k.getMainCard());
+        });
+        return R.ok(new ResultInfo(ResultCodeEnum.SUCCESS));
+    }
+
+    private R<ResultInfo> checkUserReIssueCard(List<PtCardVo> userCardList, PtCardBo cardBo) {
+        Long userId = cardBo.getUserId();
+        if (CollectionUtil.isEmpty(userCardList)) {
+            return R.fail(new ResultInfo(ResultCodeEnum.DATA_NOT_FOUND, MessageFormat.format("Id为[{0}]的人员没有发过卡,无法补卡", userId)));
+        }
+        Long oldCardNo = cardBo.getOldCardNo();
+        Long oldFactoryId = cardBo.getOldFactoryId();
+        PtCardVo oldCardVo = new PtCardVo();
+        // 补卡时,必须有原卡的发卡记录
+        R<ResultInfo> result = checkUserOldCard(userCardList, oldCardNo, oldFactoryId, oldCardVo);
+        if (R.isError(result)) {
+            return R.fail(MessageFormat.format("无法补卡,{0}", result.getData()));
+        }
+        // 补卡时,原卡必须是挂失状态
+        if (ObjectUtil.notEqual(oldCardVo.getStatus(), CardStatusEnum.LOCK.code())) {
+            return R.fail(
+                new ResultInfo(ResultCodeEnum.DATA_NOT_FOUND,
+                               MessageFormat.format("没有物理卡号[{0}]和卡流水号[{1}]对应的挂失卡片", oldFactoryId, oldCardNo)));
+        }
+        // 如果待补的旧卡是主卡,检查人员是否有正常主卡
+        if (ObjectUtil.equals(oldCardVo.getMainCard(), "Y")) {
+            if (userCardList.stream().anyMatch(
+                p -> ObjectUtil.equals(p.getStatus(), CardStatusEnum.NORMAL.code()) && ObjectUtil.equals(p.getMainCard(), "Y"))) {
+                return R.fail(new ResultInfo(ResultCodeEnum.DATA_ALREADY_EXISTED,
+                                             MessageFormat.format("物理卡号[{0}]和卡流水号[{1}]的挂失卡片为主卡,但人员已有正常的主卡", oldFactoryId,
+                                                                  oldCardNo)));
+            }
+        }
+        return R.ok(new ResultInfo(ResultCodeEnum.SUCCESS));
+    }
+
+    private R<ResultInfo> checkUserOldCard(List<PtCardVo> userCardList, Long oldCardNo, Long oldFactoryId, PtCardVo oldCardVo) {
+        if (ObjectUtil.isEmpty(oldCardNo) || oldCardNo == 0) {
+            return R.fail(new ResultInfo(ResultCodeEnum.PARAM_IS_INVALID, "旧卡的卡流水号必须大于0"));
+        }
+        if (ObjectUtil.isEmpty(oldFactoryId) || oldFactoryId == 0) {
+            return R.fail(new ResultInfo(ResultCodeEnum.PARAM_IS_INVALID, "旧卡的物理卡号必须大于0"));
+        }
+        if (userCardList.stream().noneMatch(p -> ObjectUtil.equals(p.getCardNo(), oldCardNo) && ObjectUtil.equals(p.getFactoryId(), oldFactoryId))) {
+            return R.fail(
+                new ResultInfo(ResultCodeEnum.DATA_NOT_FOUND,
+                               MessageFormat.format("无物理卡号[{0}]和卡流水号[{1}]对应的旧卡记录", oldFactoryId, oldCardNo)));
+        }
+        // 取出旧卡信息
+        PtCardVo oldCard = userCardList.stream().filter(
+            p -> ObjectUtil.equals(p.getCardNo(), oldCardNo) && ObjectUtil.equals(p.getFactoryId(), oldFactoryId)).findFirst().orElse(null);
+
+        BeanUtil.copyProperties(oldCard, oldCardVo);
+        return R.ok(new ResultInfo(ResultCodeEnum.SUCCESS));
+    }
 }

+ 41 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/CardApiController.java

@@ -0,0 +1,41 @@
+package org.dromara.backstage.cardCenter.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.backstage.business.card.CardBusiness;
+import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
+import org.dromara.backstage.cardCenter.domain.vo.InitCardVo;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.ResultInfo;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.web.core.BaseController;
+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;
+
+/**
+ * @ClassName CardApiController
+ * @Description 提供给自助客户端对卡片操作的接口
+ *              因为自有的客户端不需要登录验证所以单独提供接口
+ * @Author luoyibo
+ * @Date 2024-11-07 11:27
+ * @Version 1.0
+ * @since jdk17
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/card")
+public class CardApiController extends BaseController {
+    private final CardBusiness cardBusiness;
+    @PostMapping("/api/v1/newcard")
+    public InitCardVo initNewCard(@RequestBody @Validated(AddGroup.class)  PtCardBo bo) {
+        R<ResultInfo> result = cardBusiness.initEntityCard(bo);
+        if(R.isSuccess(result)) {
+            return new InitCardVo();
+        }
+        throw  new ServiceException(result.getData().getDetail(),result.getCode());
+    }
+}

+ 37 - 15
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/domain/bo/PtCardBo.java

@@ -1,15 +1,15 @@
 package org.dromara.backstage.cardCenter.domain.bo;
 
-import org.dromara.backstage.cardCenter.domain.PtCard;
-import org.dromara.common.mybatis.core.domain.BaseEntity;
-import org.dromara.common.core.validate.AddGroup;
-import org.dromara.common.core.validate.EditGroup;
 import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import jakarta.validation.constraints.*;
+import org.dromara.backstage.cardCenter.domain.PtCard;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
 import java.util.Date;
-import com.fasterxml.jackson.annotation.JsonFormat;
 
 /**
  * 账户卡片业务对象 t_pt_card
@@ -30,25 +30,24 @@ public class PtCardBo extends BaseEntity {
     /**
      * 所属账户Id
      */
-    @NotNull(message = "所属账户Id不能为空", groups = { AddGroup.class, EditGroup.class })
+    @NotNull(message = "所属账户Id不能为空", groups = {AddGroup.class, EditGroup.class})
     private Long userId;
-
-    /**
-     * 卡流水号
-     */
-    private Long cardNo;
-
     /**
      * 卡片类型
      */
-    @NotNull(message = "卡片类型不能为空", groups = { AddGroup.class, EditGroup.class })
+    @NotNull(message = "卡片类型不能为空", groups = {AddGroup.class, EditGroup.class})
     private Long cardType;
 
     /**
      * 卡片有效期
      */
-    @NotNull(message = "卡片有效期不能为空", groups = { AddGroup.class, EditGroup.class })
+    @NotNull(message = "卡片有效期不能为空", groups = {AddGroup.class, EditGroup.class})
     private Date lifespan;
+    /*
+      卡流水号
+     */
+    private Long cardNo;
+
 
     /**
      * 物理卡号
@@ -100,5 +99,28 @@ public class PtCardBo extends BaseEntity {
      */
     private Long lastMeal;
 
+    // region 以下字段为发卡时需要传入
+    /**
+    * 卡片操作类型
+    * {@link org.dromara.common.core.enums.CardOperateEnum}
+    */
+    private Integer operateType;
+    /**
+     * 工作站编号,发卡时需要
+     */
+    private Long stationNumb;
+    /**
+    * 租户Id
+    */
+    private String tenantId;
 
+    /**
+     * 原卡卡流水号
+     */
+    private Long oldCardNo;
+    /**
+     * 原卡物理卡号
+     */
+    private Long oldFactoryId;
+    // endregion
 }

+ 35 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/domain/vo/InitCardVo.java

@@ -0,0 +1,35 @@
+package org.dromara.backstage.cardCenter.domain.vo;
+
+import lombok.Data;
+import org.dromara.backstage.payment.domain.vo.PtBagVo;
+import org.dromara.backstage.payment.domain.vo.PtUserAccountInfoVo;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @ClassName InitCardVo
+ * @Description 取卡时初始化的卡片信息,原一卡通系统需要
+ * @Author luoyibo
+ * @Date 2024-11-07 11:06
+ * @Version 1.0
+ * @since jdk17
+ */
+@Data
+public class InitCardVo implements Serializable {
+    @Serial
+    private static final long serialVersionUID = -6032228869217179343L;
+    /**
+    * 卡片信息
+    */
+    private PtCardVo card;
+    /**
+    * 账户信息
+    */
+    private PtUserAccountInfoVo user;
+    /**
+    * 钱包信息
+    */
+    private List<PtBagVo> bags;
+}