wjj пре 21 часа
родитељ
комит
494f5d1193

+ 103 - 0
fs-company/src/main/java/com/fs/company/controller/patient/FsProjectRedPacketRecordController.java

@@ -0,0 +1,103 @@
+package com.fs.company.controller.patient;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.patient.domain.FsProjectRedPacketRecord;
+import com.fs.patient.service.IFsProjectRedPacketRecordService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 理疗红包记录Controller
+ * 
+ * @author fs
+ * @date 2026-04-23
+ */
+@RestController
+@RequestMapping("/patient/projectRecord")
+public class FsProjectRedPacketRecordController extends BaseController
+{
+    @Autowired
+    private IFsProjectRedPacketRecordService fsProjectRedPacketRecordService;
+
+    /**
+     * 查询理疗红包记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('patient:projectRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsProjectRedPacketRecord fsProjectRedPacketRecord)
+    {
+        startPage();
+        List<FsProjectRedPacketRecord> list = fsProjectRedPacketRecordService.selectFsProjectRedPacketRecordList(fsProjectRedPacketRecord);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出理疗红包记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('patient:projectRecord:export')")
+    @Log(title = "理疗红包记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsProjectRedPacketRecord fsProjectRedPacketRecord)
+    {
+        List<FsProjectRedPacketRecord> list = fsProjectRedPacketRecordService.selectFsProjectRedPacketRecordList(fsProjectRedPacketRecord);
+        ExcelUtil<FsProjectRedPacketRecord> util = new ExcelUtil<FsProjectRedPacketRecord>(FsProjectRedPacketRecord.class);
+        return util.exportExcel(list, "理疗红包记录数据");
+    }
+
+    /**
+     * 获取理疗红包记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('patient:projectRecord:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsProjectRedPacketRecordService.selectFsProjectRedPacketRecordById(id));
+    }
+
+    /**
+     * 新增理疗红包记录
+     */
+    @PreAuthorize("@ss.hasPermi('patient:projectRecord:add')")
+    @Log(title = "理疗红包记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsProjectRedPacketRecord fsProjectRedPacketRecord)
+    {
+        return toAjax(fsProjectRedPacketRecordService.insertFsProjectRedPacketRecord(fsProjectRedPacketRecord));
+    }
+
+    /**
+     * 修改理疗红包记录
+     */
+    @PreAuthorize("@ss.hasPermi('patient:projectRecord:edit')")
+    @Log(title = "理疗红包记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsProjectRedPacketRecord fsProjectRedPacketRecord)
+    {
+        return toAjax(fsProjectRedPacketRecordService.updateFsProjectRedPacketRecord(fsProjectRedPacketRecord));
+    }
+
+    /**
+     * 删除理疗红包记录
+     */
+    @PreAuthorize("@ss.hasPermi('patient:projectRecord:remove')")
+    @Log(title = "理疗红包记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsProjectRedPacketRecordService.deleteFsProjectRedPacketRecordByIds(ids));
+    }
+}

+ 15 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java

@@ -21,6 +21,8 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
+import com.fs.patient.domain.FsProjectRedPacketRecord;
+import com.fs.patient.mapper.FsProjectRedPacketRecordMapper;
 import com.fs.qw.domain.BindPhoneRedPacketRecord;
 import com.fs.qw.domain.SignRedPacketRecord;
 import com.fs.qw.mapper.BindPhoneRedPacketRecordMapper;
@@ -59,6 +61,9 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
 
     @Autowired
     private SignRedPacketRecordMapper signRedPacketRecordMapper;
+
+    @Autowired
+    private FsProjectRedPacketRecordMapper projectRedPacketRecordMapper;
     /**
      * 查询短链课程看课记录
      *
@@ -166,7 +171,16 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
                 signRedPacketRecordMapper.updateSignRedPacketRecord(record);
                 return R.ok();
             }
-        }else {
+        } else if (outBatchNo.contains("projectPacket")) {
+            FsProjectRedPacketRecord redPacketRecord = projectRedPacketRecordMapper.selectFsProjectRedPacketRecordByOutBatchNo(outBatchNo);
+            if (redPacketRecord != null) {
+                redPacketRecord.setCollectType(1);
+                redPacketRecord.setUpdateTime(new Date());
+                redPacketRecord.setBatchId(batchId);
+                projectRedPacketRecordMapper.updateFsProjectRedPacketRecord(redPacketRecord);
+                return R.ok();
+            }
+        } else {
             //看课红包回调
             FsCourseRedPacketLog log = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogByBatchNo(outBatchNo);
             if (log!=null){

+ 2 - 0
fs-service/src/main/java/com/fs/his/param/WxSendRedPacketParam.java

@@ -39,4 +39,6 @@ public class WxSendRedPacketParam implements Serializable {
      * */
     private Long sourceType;
 
+    private boolean projectFlag;
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -707,6 +707,8 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             request.setOutBillNo("phonePacket" + code);
         } else if (param.getSourceType() != null) {
             request.setOutBillNo("articlePacket" + code);
+        } else if (ObjectUtil.isNotNull(param.isProjectFlag()) && param.isProjectFlag()) {
+            request.setOutBillNo("projectPacket" + code);
         } else {
             request.setOutBillNo("fsCourse" + code);
         }

+ 84 - 0
fs-service/src/main/java/com/fs/patient/domain/FsProjectRedPacketRecord.java

@@ -0,0 +1,84 @@
+package com.fs.patient.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 理疗红包记录对象 fs_project_red_packet_record
+ *
+ * @author fs
+ * @date 2026-04-23
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsProjectRedPacketRecord extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 销售id */
+    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 理疗id */
+    @Excel(name = "理疗id")
+    private Long projectId;
+
+    /** 红包金额 */
+    @Excel(name = "红包金额")
+    private BigDecimal amount;
+
+    /** 批次单号 */
+    @Excel(name = "批次单号")
+    private String outBatchNo;
+
+    /** 红包返回结果 */
+    @Excel(name = "红包返回结果")
+    private String result;
+
+    /** 微信批次ID */
+    @Excel(name = "微信批次ID")
+    private String batchId;
+
+    /** 点击红包领取按钮标识 0-未生成 1-生成 */
+    @Excel(name = "点击红包领取按钮标识 0-未生成 1-生成")
+    private Integer collectTag;
+
+    /** 发放时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "发放时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date sendTime;
+
+    /** 领取时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "领取时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date collectTime;
+
+    /** 领取状态(0-已发放 1-已领取 2-已失效) */
+    @Excel(name = "领取状态(0-已发放 1-已领取 2-已失效)")
+    private Integer collectType;
+
+    /** 领取用户名称 */
+    @Excel(name = "领取用户名称")
+    private String userName;
+
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+
+    /** 公司名称 */
+    @Excel(name = "公司名称")
+    private String companyName;
+
+
+}

+ 72 - 0
fs-service/src/main/java/com/fs/patient/mapper/FsProjectRedPacketRecordMapper.java

@@ -0,0 +1,72 @@
+package com.fs.patient.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.patient.domain.FsProjectRedPacketRecord;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 理疗红包记录Mapper接口
+ * 
+ * @author fs
+ * @date 2026-04-23
+ */
+public interface FsProjectRedPacketRecordMapper extends BaseMapper<FsProjectRedPacketRecord>{
+    /**
+     * 查询理疗红包记录
+     * 
+     * @param id 理疗红包记录主键
+     * @return 理疗红包记录
+     */
+    FsProjectRedPacketRecord selectFsProjectRedPacketRecordById(Long id);
+
+    /**
+     * 查询理疗红包记录列表
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 理疗红包记录集合
+     */
+    List<FsProjectRedPacketRecord> selectFsProjectRedPacketRecordList(FsProjectRedPacketRecord fsProjectRedPacketRecord);
+
+    /**
+     * 新增理疗红包记录
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 结果
+     */
+    int insertFsProjectRedPacketRecord(FsProjectRedPacketRecord fsProjectRedPacketRecord);
+
+    /**
+     * 修改理疗红包记录
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 结果
+     */
+    int updateFsProjectRedPacketRecord(FsProjectRedPacketRecord fsProjectRedPacketRecord);
+
+    /**
+     * 删除理疗红包记录
+     * 
+     * @param id 理疗红包记录主键
+     * @return 结果
+     */
+    int deleteFsProjectRedPacketRecordById(Long id);
+
+    /**
+     * 批量删除理疗红包记录
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsProjectRedPacketRecordByIds(Long[] ids);
+
+
+    FsProjectRedPacketRecord selectTodayFsProjectRedPacketRecordByUserId(Long userId);
+
+    @Select("SELECT * FROM fs_project_red_packet_record WHERE user_id = #{userId} AND project_id = #{projectId} LIMIT 1")
+    FsProjectRedPacketRecord selectFsProjectRedPacketByUserIdAndProjectId(@Param("userId") Long userId ,@Param("projectId") Long projectId);
+
+    @Select("SELECT * FROM fs_project_red_packet_record WHERE out_batch_no = #{outBatchNo}")
+    FsProjectRedPacketRecord selectFsProjectRedPacketRecordByOutBatchNo(@Param("outBatchNo") String outBatchNo);
+}

+ 16 - 0
fs-service/src/main/java/com/fs/patient/param/FsProjectSendRewardUParam.java

@@ -0,0 +1,16 @@
+package com.fs.patient.param;
+
+import lombok.Data;
+
+@Data
+public class FsProjectSendRewardUParam {
+    //理疗id
+    private Long projectId;
+    //用户id
+    private Long userId;
+    //小程序id
+    private String appId;
+    //患者基础信息id
+    private Long patientId;
+
+}

+ 8 - 0
fs-service/src/main/java/com/fs/patient/service/IFsPatientBaseInfoService.java

@@ -7,6 +7,7 @@ import com.fs.his.param.FsPatientBaseInfoListDParam;
 import com.fs.his.vo.FsPatientBaseInfoListDVO;
 import com.fs.patient.domain.FsPatientBaseInfo;
 import com.fs.patient.param.BindPatientParam;
+import com.fs.patient.param.FsProjectSendRewardUParam;
 
 /**
  * 患者基本信息Service接口
@@ -77,4 +78,11 @@ public interface IFsPatientBaseInfoService extends IService<FsPatientBaseInfo>{
     List<FsPatientBaseInfoListDVO> selectFsPatientBaseInfoListDVO(FsPatientBaseInfoListDParam param);
 
     R diagnose(FsPatientBaseInfo param);
+
+    /**
+     * 发放理疗红包奖励
+     * @param param
+     * @return
+     */
+    R sendProjectReward(FsProjectSendRewardUParam param);
 }

+ 61 - 0
fs-service/src/main/java/com/fs/patient/service/IFsProjectRedPacketRecordService.java

@@ -0,0 +1,61 @@
+package com.fs.patient.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.patient.domain.FsProjectRedPacketRecord;
+
+/**
+ * 理疗红包记录Service接口
+ * 
+ * @author fs
+ * @date 2026-04-23
+ */
+public interface IFsProjectRedPacketRecordService extends IService<FsProjectRedPacketRecord>{
+    /**
+     * 查询理疗红包记录
+     * 
+     * @param id 理疗红包记录主键
+     * @return 理疗红包记录
+     */
+    FsProjectRedPacketRecord selectFsProjectRedPacketRecordById(Long id);
+
+    /**
+     * 查询理疗红包记录列表
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 理疗红包记录集合
+     */
+    List<FsProjectRedPacketRecord> selectFsProjectRedPacketRecordList(FsProjectRedPacketRecord fsProjectRedPacketRecord);
+
+    /**
+     * 新增理疗红包记录
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 结果
+     */
+    int insertFsProjectRedPacketRecord(FsProjectRedPacketRecord fsProjectRedPacketRecord);
+
+    /**
+     * 修改理疗红包记录
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 结果
+     */
+    int updateFsProjectRedPacketRecord(FsProjectRedPacketRecord fsProjectRedPacketRecord);
+
+    /**
+     * 批量删除理疗红包记录
+     * 
+     * @param ids 需要删除的理疗红包记录主键集合
+     * @return 结果
+     */
+    int deleteFsProjectRedPacketRecordByIds(Long[] ids);
+
+    /**
+     * 删除理疗红包记录信息
+     * 
+     * @param id 理疗红包记录主键
+     * @return 结果
+     */
+    int deleteFsProjectRedPacketRecordById(Long id);
+}

+ 192 - 1
fs-service/src/main/java/com/fs/patient/service/impl/FsPatientBaseInfoServiceImpl.java

@@ -1,10 +1,13 @@
 package com.fs.patient.service.impl;
 
-import java.util.Collections;
+import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
@@ -12,24 +15,38 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.core.config.WxMaConfiguration;
+import com.fs.course.domain.FsCourseRedPacketLog;
 import com.fs.his.domain.FsDoctor;
 import com.fs.his.domain.FsProject;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserWx;
+import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.FsPatientBaseInfoListDParam;
+import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsDoctorService;
 import com.fs.his.service.IFsProjectService;
+import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.service.IFsUserWxService;
 import com.fs.his.vo.FsPatientBaseInfoListDVO;
+import com.fs.patient.domain.FsProjectRedPacketRecord;
+import com.fs.patient.mapper.FsProjectRedPacketRecordMapper;
 import com.fs.patient.param.BindPatientParam;
+import com.fs.patient.param.FsProjectSendRewardUParam;
 import com.fs.patient.vo.PatientBaseInfoVO;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
+import com.fs.system.service.ISysConfigService;
+import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxErrorException;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.patient.mapper.FsPatientBaseInfoMapper;
 import com.fs.patient.domain.FsPatientBaseInfo;
 import com.fs.patient.service.IFsPatientBaseInfoService;
+import org.springframework.util.ObjectUtils;
 
 /**
  * 患者基本信息Service业务层处理
@@ -53,6 +70,21 @@ public class FsPatientBaseInfoServiceImpl extends ServiceImpl<FsPatientBaseInfoM
 
     @Autowired
     private IFsProjectService projectService;
+
+    @Autowired
+    private FsProjectRedPacketRecordMapper projectRedPacketRecordMapper;
+
+    @Autowired
+    private FsUserMapper userMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private IFsUserWxService fsUserWxService;
+
+    @Autowired
+    private IFsStorePaymentService fsStorePaymentService;
     /**
      * 查询患者基本信息
      * 
@@ -213,6 +245,25 @@ public class FsPatientBaseInfoServiceImpl extends ServiceImpl<FsPatientBaseInfoM
             patientBaseInfoVO.setProject(fsProject);
         }
 
+        //红包配置信息
+        BigDecimal amount = new BigDecimal("0.1");
+        boolean openFlag = false;
+        String json = getProjectRedPacketConfig();
+        if(StringUtils.isNotEmpty(json)) {
+            try {
+                JSONObject jsonObject = JSON.parseObject(json);
+                String open = jsonObject.getString("open");
+                if ("1".equals(open)) {
+                    openFlag = true;
+                }
+                amount = jsonObject.getBigDecimal("amount");
+            } catch (Exception e) {
+                log.error("解析理疗红包配置失败, json={}", json, e);
+                amount = new BigDecimal("0.1");
+            }
+        }
+        patientBaseInfoVO.setAmount(amount);
+        patientBaseInfoVO.setOpenFlag(openFlag);
         return R.ok().put("data", patientBaseInfoVO);
     }
 
@@ -274,4 +325,144 @@ public class FsPatientBaseInfoServiceImpl extends ServiceImpl<FsPatientBaseInfoM
         }
         return R.error("诊断失败");
     }
+
+    @Override
+    public R sendProjectReward(FsProjectSendRewardUParam param) {
+        FsPatientBaseInfo fsPatientBaseInfo = fsPatientBaseInfoMapper.selectFsPatientBaseInfoById(param.getPatientId());
+        if (fsPatientBaseInfo == null) {
+            return R.error("患者信息错误,领取失败");
+        }
+        CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(fsPatientBaseInfo.getCompanyUserId());
+        if (companyUser == null) {
+            return R.error("销售信息错误,领取失败");
+        }
+        FsProject fsProject = projectService.selectFsProjectById(param.getProjectId());
+        if (fsProject == null) {
+            return R.error("理疗信息错误,领取失败");
+        }
+        //获取今日理疗红包记录
+        FsProjectRedPacketRecord redPacketRecord = projectRedPacketRecordMapper.selectTodayFsProjectRedPacketRecordByUserId(param.getUserId());
+        if (!ObjectUtils.isEmpty(redPacketRecord)) {
+            if (redPacketRecord.getCollectType() == 1) {
+                return R.error("今日已领取过红包!");
+            }
+            if (redPacketRecord.getCollectType() == 0) {
+                if (!Objects.equals(param.getProjectId(), redPacketRecord.getProjectId())) {
+                    return R.error("当日只能领取一次理疗红包!");
+                }
+            }
+        }
+        // 获取用户信息
+        FsUser user = userMapper.selectFsUserByUserId(param.getUserId());
+        if (user != null && user.getStatus()==0){
+            return R.error("会员被停用,无权限,请联系客服!");
+        }
+
+        FsProjectRedPacketRecord record = projectRedPacketRecordMapper.selectFsProjectRedPacketByUserIdAndProjectId(param.getUserId(), param.getProjectId());
+        if (record != null && record.getCollectType() == 1) {
+            return R.error("已领取过该理疗红包,不可重复领取");
+        }
+        if (record != null && record.getCollectType() == 0 && StringUtils.isNotEmpty(record.getResult())) {
+            try {
+                //返回已有生成的红包数据
+                return JSON.parseObject(record.getResult(), R.class);
+            } catch (Exception e) {
+                log.error("理疗红包=====》转换已有红包数据失败:{}",e.getMessage());
+                return R.error("操作频繁,请稍后再试!");
+            }
+        }
+        //发送红包
+        return sendProjectRedPacket(param,user,companyUser);
+    }
+
+
+    private R sendProjectRedPacket(FsProjectSendRewardUParam param,FsUser user,CompanyUser companyUser){
+        //默认0.1红包数据
+        BigDecimal amount;
+        String json = getProjectRedPacketConfig();
+        if(StringUtils.isNotEmpty(json)) {
+            try {
+                JSONObject jsonObject = JSON.parseObject(json);
+                String open = jsonObject.getString("open");
+                if (!"1".equals(open)) {
+                    return R.error("未开启领取功能,领取失败");
+                }
+                amount = jsonObject.getBigDecimal("amount");
+            } catch (Exception e) {
+                log.error("解析理疗红包配置失败, json={}", json, e);
+                amount = new BigDecimal("0.1");
+            }
+        } else {
+            return R.error("获取理疗红包配置错误");
+        }
+        // 准备发送红包参数
+        WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
+        //处理多小程序问题
+        FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
+        if (fsUserWx ==null){
+            try {
+                handleFsUserWx(user,param.getAppId());
+            }catch (Exception e){
+                log.error("【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId());
+            }
+        }else {
+            packetParam.setOpenId(fsUserWx.getOpenId());
+        }
+        packetParam.setRedPacketMode(1);
+        packetParam.setAppId(param.getAppId());
+        packetParam.setMpAppId(param.getAppId());
+        packetParam.setUser(user);
+        packetParam.setProjectFlag(true);
+        //小程序
+        packetParam.setSource(2);
+        packetParam.setCompanyId(companyUser.getCompanyId());
+
+        // 发送红包
+        R sendRedPacket = fsStorePaymentService.sendRedPacket(packetParam);
+        if (sendRedPacket.get("code").equals(200)) {
+            FsProjectRedPacketRecord redPacketRecord = new FsProjectRedPacketRecord();
+            TransferBillsResult transferBillsResult;
+            if (sendRedPacket.get("isNew").equals(1)){
+                transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
+                redPacketRecord.setResult(JSON.toJSONString(sendRedPacket));
+                redPacketRecord.setOutBatchNo(transferBillsResult.getOutBillNo());
+            }else {
+                redPacketRecord.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                redPacketRecord.setBatchId(sendRedPacket.get("batchId").toString());
+            }
+            // 添加红包记录
+            redPacketRecord.setCompanyId(companyUser.getCompanyId());
+            redPacketRecord.setCompanyUserId(companyUser.getUserId());
+            redPacketRecord.setCompanyName(companyUser.getCompanyName());
+            redPacketRecord.setUserId(param.getUserId());
+            redPacketRecord.setUserName(user.getNickName());
+            redPacketRecord.setProjectId(param.getProjectId());
+            redPacketRecord.setCollectType(0);
+            redPacketRecord.setSendTime(new Date());
+            redPacketRecord.setCreateTime(new Date());
+            redPacketRecord.setAmount(amount);
+            projectRedPacketRecordMapper.insertFsProjectRedPacketRecord(redPacketRecord);
+            return sendRedPacket;
+        } else {
+            return R.error("奖励发送失败,请联系客服");
+        }
+    }
+
+    private void handleFsUserWx(FsUser user, String appId) {
+        FsUserWx fsUserWx = new FsUserWx();
+        fsUserWx.setType(1);
+        fsUserWx.setFsUserId(user.getUserId());
+        fsUserWx.setAppId(appId);
+        fsUserWx.setOpenId(user.getCourseMaOpenId());
+        fsUserWx.setUnionId(user.getUnionId());
+        fsUserWx.setCreateTime(new Date());
+        fsUserWx.setUpdateTime(new Date());
+        fsUserWxService.saveOrUpdateByUniqueKey(fsUserWx);
+
+        log.info("【更新或插入用户与小程序{}的绑定关系】:{}", appId, user.getUserId());
+    }
+
+    private String getProjectRedPacketConfig(){
+        return configService.selectConfigByKey("projectRedPacket.config");
+    }
 }

+ 94 - 0
fs-service/src/main/java/com/fs/patient/service/impl/FsProjectRedPacketRecordServiceImpl.java

@@ -0,0 +1,94 @@
+package com.fs.patient.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.patient.mapper.FsProjectRedPacketRecordMapper;
+import com.fs.patient.domain.FsProjectRedPacketRecord;
+import com.fs.patient.service.IFsProjectRedPacketRecordService;
+
+/**
+ * 理疗红包记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-04-23
+ */
+@Service
+public class FsProjectRedPacketRecordServiceImpl extends ServiceImpl<FsProjectRedPacketRecordMapper, FsProjectRedPacketRecord> implements IFsProjectRedPacketRecordService {
+
+    /**
+     * 查询理疗红包记录
+     * 
+     * @param id 理疗红包记录主键
+     * @return 理疗红包记录
+     */
+    @Override
+    public FsProjectRedPacketRecord selectFsProjectRedPacketRecordById(Long id)
+    {
+        return baseMapper.selectFsProjectRedPacketRecordById(id);
+    }
+
+    /**
+     * 查询理疗红包记录列表
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 理疗红包记录
+     */
+    @Override
+    public List<FsProjectRedPacketRecord> selectFsProjectRedPacketRecordList(FsProjectRedPacketRecord fsProjectRedPacketRecord)
+    {
+        return baseMapper.selectFsProjectRedPacketRecordList(fsProjectRedPacketRecord);
+    }
+
+    /**
+     * 新增理疗红包记录
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsProjectRedPacketRecord(FsProjectRedPacketRecord fsProjectRedPacketRecord)
+    {
+        fsProjectRedPacketRecord.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsProjectRedPacketRecord(fsProjectRedPacketRecord);
+    }
+
+    /**
+     * 修改理疗红包记录
+     * 
+     * @param fsProjectRedPacketRecord 理疗红包记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsProjectRedPacketRecord(FsProjectRedPacketRecord fsProjectRedPacketRecord)
+    {
+        fsProjectRedPacketRecord.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsProjectRedPacketRecord(fsProjectRedPacketRecord);
+    }
+
+    /**
+     * 批量删除理疗红包记录
+     * 
+     * @param ids 需要删除的理疗红包记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsProjectRedPacketRecordByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsProjectRedPacketRecordByIds(ids);
+    }
+
+    /**
+     * 删除理疗红包记录信息
+     * 
+     * @param id 理疗红包记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsProjectRedPacketRecordById(Long id)
+    {
+        return baseMapper.deleteFsProjectRedPacketRecordById(id);
+    }
+}

+ 8 - 0
fs-service/src/main/java/com/fs/patient/vo/PatientBaseInfoVO.java

@@ -4,6 +4,8 @@ import com.fs.common.annotation.Excel;
 import com.fs.his.domain.FsProject;
 import lombok.Data;
 
+import java.math.BigDecimal;
+
 @Data
 public class PatientBaseInfoVO {
 
@@ -47,4 +49,10 @@ public class PatientBaseInfoVO {
 
     /** 状态 0-未绑定 1-已绑定 */
     private Integer status;
+
+    //红包金额
+    private BigDecimal amount;
+
+    //是否可领取红包
+    private Boolean openFlag;
 }

+ 136 - 0
fs-service/src/main/resources/mapper/patient/FsProjectRedPacketRecordMapper.xml

@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.patient.mapper.FsProjectRedPacketRecordMapper">
+    
+    <resultMap type="FsProjectRedPacketRecord" id="FsProjectRedPacketRecordResult">
+        <result property="id"    column="id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="projectId"    column="project_id"    />
+        <result property="amount"    column="amount"    />
+        <result property="outBatchNo"    column="out_batch_no"    />
+        <result property="result"    column="result"    />
+        <result property="batchId"    column="batch_id"    />
+        <result property="collectTag"    column="collect_tag"    />
+        <result property="sendTime"    column="send_time"    />
+        <result property="collectTime"    column="collect_time"    />
+        <result property="collectType"    column="collect_type"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="userName"    column="user_name"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyName"    column="company_name"    />
+    </resultMap>
+
+    <sql id="selectFsProjectRedPacketRecordVo">
+        select id, user_id, company_user_id, project_id, amount, out_batch_no, result, batch_id, collect_tag, send_time, collect_time, collect_type, update_time, create_time, user_name, company_id, company_name from fs_project_red_packet_record
+    </sql>
+
+    <select id="selectFsProjectRedPacketRecordList" parameterType="FsProjectRedPacketRecord" resultMap="FsProjectRedPacketRecordResult">
+        <include refid="selectFsProjectRedPacketRecordVo"/>
+        <where>  
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="projectId != null "> and project_id = #{projectId}</if>
+            <if test="amount != null "> and amount = #{amount}</if>
+            <if test="outBatchNo != null  and outBatchNo != ''"> and out_batch_no = #{outBatchNo}</if>
+            <if test="result != null  and result != ''"> and result = #{result}</if>
+            <if test="batchId != null  and batchId != ''"> and batch_id = #{batchId}</if>
+            <if test="collectTag != null "> and collect_tag = #{collectTag}</if>
+            <if test="sendTime != null "> and send_time = #{sendTime}</if>
+            <if test="collectTime != null "> and collect_time = #{collectTime}</if>
+            <if test="collectType != null "> and collect_type = #{collectType}</if>
+            <if test="userName != null  and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="companyName != null  and companyName != ''"> and company_name like concat('%', #{companyName}, '%')</if>
+        </where>
+    </select>
+    
+    <select id="selectFsProjectRedPacketRecordById" parameterType="Long" resultMap="FsProjectRedPacketRecordResult">
+        <include refid="selectFsProjectRedPacketRecordVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertFsProjectRedPacketRecord" parameterType="FsProjectRedPacketRecord">
+        insert into fs_project_red_packet_record
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="projectId != null">project_id,</if>
+            <if test="amount != null">amount,</if>
+            <if test="outBatchNo != null">out_batch_no,</if>
+            <if test="result != null">result,</if>
+            <if test="batchId != null">batch_id,</if>
+            <if test="collectTag != null">collect_tag,</if>
+            <if test="sendTime != null">send_time,</if>
+            <if test="collectTime != null">collect_time,</if>
+            <if test="collectType != null">collect_type,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="userName != null">user_name,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyName != null">company_name,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="projectId != null">#{projectId},</if>
+            <if test="amount != null">#{amount},</if>
+            <if test="outBatchNo != null">#{outBatchNo},</if>
+            <if test="result != null">#{result},</if>
+            <if test="batchId != null">#{batchId},</if>
+            <if test="collectTag != null">#{collectTag},</if>
+            <if test="sendTime != null">#{sendTime},</if>
+            <if test="collectTime != null">#{collectTime},</if>
+            <if test="collectType != null">#{collectType},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="userName != null">#{userName},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyName != null">#{companyName},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsProjectRedPacketRecord" parameterType="FsProjectRedPacketRecord">
+        update fs_project_red_packet_record
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="projectId != null">project_id = #{projectId},</if>
+            <if test="amount != null">amount = #{amount},</if>
+            <if test="outBatchNo != null">out_batch_no = #{outBatchNo},</if>
+            <if test="result != null">result = #{result},</if>
+            <if test="batchId != null">batch_id = #{batchId},</if>
+            <if test="collectTag != null">collect_tag = #{collectTag},</if>
+            <if test="sendTime != null">send_time = #{sendTime},</if>
+            <if test="collectTime != null">collect_time = #{collectTime},</if>
+            <if test="collectType != null">collect_type = #{collectType},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="userName != null">user_name = #{userName},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyName != null">company_name = #{companyName},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsProjectRedPacketRecordById" parameterType="Long">
+        delete from fs_project_red_packet_record where id = #{id}
+    </delete>
+
+    <delete id="deleteFsProjectRedPacketRecordByIds" parameterType="String">
+        delete from fs_project_red_packet_record where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="selectTodayFsProjectRedPacketRecordByUserId"  parameterType="Long" resultMap="FsProjectRedPacketRecordResult">
+        <include refid="selectFsProjectRedPacketRecordVo"/>
+        where user_id = #{userId} AND DATE(create_time) = CURDATE() LIMIT 1
+    </select>
+</mapper>

+ 11 - 0
fs-user-app/src/main/java/com/fs/app/controller/PatientBaseInfoController.java

@@ -1,8 +1,10 @@
 package com.fs.app.controller;
 
 import com.fs.app.annotation.Login;
+import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.patient.param.BindPatientParam;
+import com.fs.patient.param.FsProjectSendRewardUParam;
 import com.fs.patient.service.IFsPatientBaseInfoService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -33,4 +35,13 @@ public class PatientBaseInfoController extends  AppBaseController {
         param.setUserId(Long.parseLong(getUserId()));
         return fsPatientBaseInfoService.patientBindUser(param);
     }
+
+    @Login
+    @ApiOperation("发放理疗奖励")
+    @PostMapping("/sendProjectReward")
+    @RepeatSubmit
+    public R sendProjectReward(@RequestBody FsProjectSendRewardUParam param){
+        param.setUserId(Long.parseLong(getUserId()));
+        return fsPatientBaseInfoService.sendProjectReward(param);
+    }
 }