Pārlūkot izejas kodu

谷养正道商城 积分改造

wangxy 1 nedēļu atpakaļ
vecāks
revīzija
0ef3c7023a
66 mainītis faili ar 4770 papildinājumiem un 8 dzēšanām
  1. 31 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyStatisticsController.java
  2. 8 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java
  3. 88 0
      fs-admin/src/main/java/com/fs/his/controller/FsGuCoinGoodsController.java
  4. 85 0
      fs-admin/src/main/java/com/fs/his/controller/FsGuCoinOrderController.java
  5. 71 0
      fs-admin/src/main/java/com/fs/his/controller/FsUserGuCoinLogsController.java
  6. 63 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  7. 4 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseTrafficLogController.java
  8. 59 2
      fs-company/src/main/java/com/fs/company/controller/store/FsFinanceOrderController.java
  9. 50 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsGuCoinGoodsController.java
  10. 113 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsGuCoinOrderController.java
  11. 27 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreStatisticsScrmController.java
  12. 101 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsUserGuCoinController.java
  13. 20 0
      fs-service/src/main/java/com/fs/course/mapper/FeiShuCourseWatchLogMapper.java
  14. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  15. 3 2
      fs-service/src/main/java/com/fs/course/param/FsCourseTrafficLogParam.java
  16. 12 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  17. 25 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  18. 104 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  19. 10 0
      fs-service/src/main/java/com/fs/erp/service/IErpOrderService.java
  20. 239 0
      fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java
  21. 42 0
      fs-service/src/main/java/com/fs/his/domain/FsGuCoinCart.java
  22. 81 0
      fs-service/src/main/java/com/fs/his/domain/FsGuCoinGoods.java
  23. 138 0
      fs-service/src/main/java/com/fs/his/domain/FsGuCoinOrder.java
  24. 4 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  25. 75 0
      fs-service/src/main/java/com/fs/his/domain/FsUserGuCoinLogs.java
  26. 37 0
      fs-service/src/main/java/com/fs/his/dto/ComputeGuCoinOrderMoneyDTO.java
  27. 1 0
      fs-service/src/main/java/com/fs/his/enums/BusinessTypeEnum.java
  28. 34 0
      fs-service/src/main/java/com/fs/his/enums/GuCoinLogTypeEnum.java
  29. 33 0
      fs-service/src/main/java/com/fs/his/mapper/FsGuCoinCartMapper.java
  30. 41 0
      fs-service/src/main/java/com/fs/his/mapper/FsGuCoinGoodsMapper.java
  31. 48 0
      fs-service/src/main/java/com/fs/his/mapper/FsGuCoinOrderMapper.java
  32. 31 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserGuCoinLogsMapper.java
  33. 5 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  34. 26 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinCartOrderCreateParam.java
  35. 32 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinCartParam.java
  36. 17 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinGoodsParam.java
  37. 26 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderComputeParam.java
  38. 27 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderCreateParam.java
  39. 23 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderDoPayParam.java
  40. 22 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderListUParam.java
  41. 29 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderManuallyParam.java
  42. 21 0
      fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderParam.java
  43. 1 1
      fs-service/src/main/java/com/fs/his/param/FsIntegralOrderCreateParam.java
  44. 33 0
      fs-service/src/main/java/com/fs/his/param/FsUserAddGuCoinParam.java
  45. 22 0
      fs-service/src/main/java/com/fs/his/param/FsUserGuCoinLogsParam.java
  46. 60 0
      fs-service/src/main/java/com/fs/his/service/IFsGuCoinCartService.java
  47. 25 0
      fs-service/src/main/java/com/fs/his/service/IFsGuCoinGoodsService.java
  48. 103 0
      fs-service/src/main/java/com/fs/his/service/IFsGuCoinOrderService.java
  49. 74 0
      fs-service/src/main/java/com/fs/his/service/IFsUserGuCoinLogsService.java
  50. 119 0
      fs-service/src/main/java/com/fs/his/service/impl/FsGuCoinCartServiceImpl.java
  51. 51 0
      fs-service/src/main/java/com/fs/his/service/impl/FsGuCoinGoodsServiceImpl.java
  52. 958 0
      fs-service/src/main/java/com/fs/his/service/impl/FsGuCoinOrderServiceImpl.java
  53. 264 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserGuCoinLogsServiceImpl.java
  54. 52 0
      fs-service/src/main/java/com/fs/his/vo/FeiShuCourseReportVO.java
  55. 84 0
      fs-service/src/main/java/com/fs/his/vo/FeiShuWatchLogReportVO.java
  56. 53 0
      fs-service/src/main/java/com/fs/his/vo/FsGuCoinCartVO.java
  57. 135 0
      fs-service/src/main/resources/mapper/course/FeiShuCourseWatchLogMapper.xml
  58. 5 2
      fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  59. 81 0
      fs-service/src/main/resources/mapper/his/FsGuCoinCartMapper.xml
  60. 118 0
      fs-service/src/main/resources/mapper/his/FsGuCoinGoodsMapper.xml
  61. 173 0
      fs-service/src/main/resources/mapper/his/FsGuCoinOrderMapper.xml
  62. 99 0
      fs-service/src/main/resources/mapper/his/FsUserGuCoinLogsMapper.xml
  63. 7 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  64. 335 0
      fs-user-app/src/main/java/com/fs/app/controller/GuCoinController.java
  65. 5 0
      fs-user-app/src/main/java/com/fs/app/controller/HuifuPayController.java
  66. 1 1
      fs-watch/src/main/java/com/fs/FsWatchApplication.java

+ 31 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyStatisticsController.java

@@ -881,6 +881,37 @@ public class CompanyStatisticsController extends BaseController {
         return util.exportExcelSelectedColumns(list, "会员看课统计报表", allFields);
     }
 
+    /**
+     * 飞书看课统计报表(仅公司维度,feishu_course_watch_log 表)
+     */
+    @GetMapping("/feiShuCourseReport")
+    public TableDataInfo selectFeiShuCourseReportVO(FsCourseWatchLogStatisticsListParam param) {
+        List<FeiShuCourseReportVO> allList = fsCourseWatchLogService.selectFeiShuCourseReportVO(param);
+        int total = allList.size();
+        int pageNum = param.getPageNum() == null ? 1 : param.getPageNum().intValue();
+        int pageSize = param.getPageSize() == null ? 10 : param.getPageSize().intValue();
+        int fromIndex = (pageNum - 1) * pageSize;
+        int toIndex = Math.min(fromIndex + pageSize, total);
+        List<FeiShuCourseReportVO> pagedList = fromIndex < total
+                ? allList.subList(fromIndex, toIndex) : new ArrayList<FeiShuCourseReportVO>();
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(200);
+        rspData.setMsg("查询成功");
+        rspData.setRows(pagedList);
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 导出飞书看课统计报表
+     */
+    @GetMapping("/exportFeiShuCourseReport")
+    public AjaxResult exportFeiShuCourseReport(FsCourseWatchLogStatisticsListParam param) {
+        List<FeiShuCourseReportVO> list = fsCourseWatchLogService.selectFeiShuCourseReportVO(param);
+        ExcelUtil<FeiShuCourseReportVO> util = new ExcelUtil<FeiShuCourseReportVO>(FeiShuCourseReportVO.class);
+        return util.exportExcel(list, "飞书看课统计报表");
+    }
+
     /**
      * 用户统计报表
      *

+ 8 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java

@@ -50,6 +50,10 @@ public class FsCourseTrafficLogController extends BaseController
     public TableDataInfo list(FsCourseTrafficLogParam param)
     {
         startPage();
+        // typeFlag 默认查询类型 0(小程序) 和 2
+        if (param.getTypeFlag() == null || param.getTypeFlag().isEmpty()) {
+            param.setTypeFlag(java.util.Arrays.asList(0, 2));
+        }
         List<FsCourseTrafficLogListVO> list = fsCourseTrafficLogService.selectTrafficNew(param);
         return getDataTable(list);
     }
@@ -69,6 +73,10 @@ public class FsCourseTrafficLogController extends BaseController
             param.setYear(Integer.parseInt(parts[0]));
             param.setMonth(Integer.parseInt(parts[1]));
         }
+        // typeFlag 默认查询类型 0(小程序) 和 2
+        if (param.getTypeFlag() == null || param.getTypeFlag().isEmpty()) {
+            param.setTypeFlag(java.util.Arrays.asList(0, 2));
+        }
         List<FsCourseTrafficLogListVO> list = fsCourseTrafficLogService.selectTrafficNew(param);
         //格式化每个记录的流量为 GB 字符串
         for (FsCourseTrafficLogListVO vo : list) {

+ 88 - 0
fs-admin/src/main/java/com/fs/his/controller/FsGuCoinGoodsController.java

@@ -0,0 +1,88 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.his.domain.FsGuCoinGoods;
+import com.fs.his.param.FsGuCoinGoodsParam;
+import com.fs.his.service.IFsGuCoinGoodsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 谷币商城商品Controller(总后台)
+ * 谷养正道商城专属商品管理
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@RestController
+@RequestMapping("/his/guCoinGoods")
+public class FsGuCoinGoodsController extends BaseController
+{
+    @Autowired
+    private IFsGuCoinGoodsService fsGuCoinGoodsService;
+
+    /**
+     * 查询谷币商品列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinGoods:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsGuCoinGoodsParam param)
+    {
+        startPage();
+        List<FsGuCoinGoods> list = fsGuCoinGoodsService.selectFsGuCoinGoodsList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取谷币商品详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinGoods:query')")
+    @GetMapping(value = "/{goodsId}")
+    public AjaxResult getInfo(@PathVariable("goodsId") Long goodsId)
+    {
+        return AjaxResult.success(fsGuCoinGoodsService.selectFsGuCoinGoodsByGoodsId(goodsId));
+    }
+
+    /**
+     * 新增谷币商品
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinGoods:add')")
+    @Log(title = "谷币商品", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsGuCoinGoods fsGuCoinGoods)
+    {
+        fsGuCoinGoods.setCreateBy(SecurityUtils.getUsername());
+        return toAjax(fsGuCoinGoodsService.insertFsGuCoinGoods(fsGuCoinGoods));
+    }
+
+    /**
+     * 修改谷币商品
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinGoods:edit')")
+    @Log(title = "谷币商品", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsGuCoinGoods fsGuCoinGoods)
+    {
+        fsGuCoinGoods.setUpdateBy(SecurityUtils.getUsername());
+        return toAjax(fsGuCoinGoodsService.updateFsGuCoinGoods(fsGuCoinGoods));
+    }
+
+    /**
+     * 删除谷币商品
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinGoods:remove')")
+    @Log(title = "谷币商品", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{goodsIds}")
+    public AjaxResult remove(@PathVariable Long[] goodsIds)
+    {
+        return toAjax(fsGuCoinGoodsService.deleteFsGuCoinGoodsByGoodsIds(goodsIds));
+    }
+}

+ 85 - 0
fs-admin/src/main/java/com/fs/his/controller/FsGuCoinOrderController.java

@@ -0,0 +1,85 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.his.domain.FsGuCoinOrder;
+import com.fs.his.param.FsGuCoinOrderParam;
+import com.fs.his.service.IFsGuCoinOrderService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 谷币商城订单Controller(总后台)
+ * 谷养正道商城订单管理
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@RestController
+@RequestMapping("/his/guCoinOrder")
+public class FsGuCoinOrderController extends BaseController
+{
+    @Autowired
+    private IFsGuCoinOrderService fsGuCoinOrderService;
+
+    /**
+     * 查询谷币订单列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsGuCoinOrderParam param)
+    {
+        startPage();
+        List<FsGuCoinOrder> list = fsGuCoinOrderService.selectFsGuCoinOrderList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取谷币订单详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:query')")
+    @GetMapping(value = "/{orderId}")
+    public AjaxResult getInfo(@PathVariable("orderId") Long orderId)
+    {
+        return AjaxResult.success(fsGuCoinOrderService.selectFsGuCoinOrderByOrderId(orderId));
+    }
+
+    /**
+     * 发货
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:sendGoods')")
+    @Log(title = "谷币订单", businessType = BusinessType.UPDATE)
+    @PutMapping("/sendGoods")
+    public AjaxResult sendGoods(@RequestBody FsGuCoinOrder fsGuCoinOrder)
+    {
+        return toAjax(fsGuCoinOrderService.updateFsGuCoinOrder(fsGuCoinOrder));
+    }
+
+    /**
+     * 修改谷币订单
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:edit')")
+    @Log(title = "谷币订单", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsGuCoinOrder fsGuCoinOrder)
+    {
+        return toAjax(fsGuCoinOrderService.updateFsGuCoinOrder(fsGuCoinOrder));
+    }
+
+    /**
+     * 删除谷币订单
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:remove')")
+    @Log(title = "谷币订单", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{orderIds}")
+    public AjaxResult remove(@PathVariable Long[] orderIds)
+    {
+        return toAjax(fsGuCoinOrderService.deleteFsGuCoinOrderByOrderIds(orderIds));
+    }
+}

+ 71 - 0
fs-admin/src/main/java/com/fs/his/controller/FsUserGuCoinLogsController.java

@@ -0,0 +1,71 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.his.enums.GuCoinLogTypeEnum;
+import com.fs.his.param.FsUserAddGuCoinParam;
+import com.fs.his.param.FsUserGuCoinLogsParam;
+import com.fs.his.service.IFsUserGuCoinLogsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 会员谷币记录Controller(总后台)
+ * 总后台录入会员谷币(填写发放备注、消费金额,留存操作日志,支持溯源对账)
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@RestController
+@RequestMapping("/his/userGuCoinLogs")
+public class FsUserGuCoinLogsController extends BaseController
+{
+    @Autowired
+    private IFsUserGuCoinLogsService fsUserGuCoinLogsService;
+
+    /**
+     * 查询会员谷币记录列表(溯源对账)
+     */
+    @PreAuthorize("@ss.hasPermi('his:userGuCoinLogs:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserGuCoinLogsParam param)
+    {
+        startPage();
+        List<com.fs.his.domain.FsUserGuCoinLogs> list = fsUserGuCoinLogsService.selectFsUserGuCoinLogsList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 总后台录入会员谷币
+     * 填写发放备注、消费金额,留存操作日志,支持溯源对账
+     */
+    @PreAuthorize("@ss.hasPermi('his:userGuCoinLogs:add')")
+    @Log(title = "总后台录入会员谷币", businessType = BusinessType.INSERT)
+    @PostMapping("/addGuCoin")
+    public R addGuCoin(@RequestBody @Validated FsUserAddGuCoinParam param) {
+        return fsUserGuCoinLogsService.addGuCoin(param,
+                GuCoinLogTypeEnum.TYPE_4.getValue(),
+                SecurityUtils.getUserId(),
+                SecurityUtils.getUsername(),
+                1);
+    }
+
+    /**
+     * 获取谷币记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:userGuCoinLogs:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsUserGuCoinLogsService.selectFsUserGuCoinLogsById(id));
+    }
+}

+ 63 - 0
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -245,6 +245,12 @@ public class Task {
     @Autowired
     private IFsIntegralOrderService fsIntegralOrderService;
 
+    @Autowired
+    private FsGuCoinOrderMapper fsGuCoinOrderMapper;
+
+    @Autowired
+    private IFsGuCoinOrderService fsGuCoinOrderService;
+
     @Autowired
     private IFsCourseCheckinActivityService fsCourseCheckinActivityService;
 
@@ -843,6 +849,57 @@ public class Task {
         }
     }
 
+    //谷币订单发货状态同步
+    public void deliveryGuCoinOrder() throws InterruptedException {
+        List<FsGuCoinOrder> fsGuCoinOrders = fsGuCoinOrderMapper.selectJstOmsGuCoinOrderdeliveryOp();
+
+        //聚水潭
+        ArrayList<Long> o_ids = new ArrayList<>();
+        for (FsGuCoinOrder fsGuCoinOrder : fsGuCoinOrders) {
+            o_ids.add(Long.parseLong(fsGuCoinOrder.getExtendOrderId()));
+        }
+        if (!CollectionUtils.isEmpty(o_ids)) {
+            jstErpGuCoinOrderDelivery(o_ids);
+        }
+    }
+
+    private void jstErpGuCoinOrderDelivery(List<Long> o_ids) throws InterruptedException {
+        if (CollectionUtils.isEmpty(o_ids)) return;
+
+        List<List<Long>> batches = Lists.partition(o_ids, 100);
+        for (List<Long> batch : batches) {
+            int pageIndex = 1;
+            boolean has_next = false;
+            do {
+                Thread.sleep(300);
+                ErpOrderQueryRequert request = new ErpOrderQueryRequert();
+                request.setPage_size(100);
+                request.setPage_index(pageIndex);
+                request.setShop_id(shopCode);
+                request.setO_ids(batch); // 设置当前批次
+                // 谷币仅聚水潭,直接用 jSTOrderService,避免依赖 erpType 配置
+                ErpOrderQueryResponse response = jSTOrderService.getGuCoinOrder(request, "gucoin");
+                has_next = response.hasNextSafe();
+
+                if (response.getOrders() != null) {
+                    for (ErpOrderQuery orderQuery : response.getOrders()) {
+                        if (orderQuery.getDeliverys() != null && !orderQuery.getDeliverys().isEmpty()) {
+                            for (ErpDeliverys delivery : orderQuery.getDeliverys()) {
+                                if (delivery.getDelivery() && StringUtils.isNotEmpty(delivery.getMail_no())) {
+                                    // 更新谷币订单状态(含快递鸟订阅)并删除 REDIS
+                                    fsGuCoinOrderService.deliveryGuCoinOrderOrder(orderQuery.getCode(),
+                                            delivery.getMail_no(), delivery.getExpress_code(), delivery.getExpress_name());
+                                    redisCache.deleteObject("delivery" + ":" + orderQuery.getO_id());
+                                }
+                            }
+                        }
+                    }
+                }
+                pageIndex++;
+            } while (has_next);
+        }
+    }
+
 
     public void deliveryOpScrm()
     {
@@ -1850,6 +1907,12 @@ public class Task {
         fsIntegralOrderService.createOmsIntegralOrder(omsList);
     }
 
+    //谷币订单推送聚水潭
+    public void createOmsGuCoinOrder() throws ParseException, InterruptedException {
+        List<Long> omsList = fsGuCoinOrderMapper.selectFsGuCoinOrderToJst();
+        fsGuCoinOrderService.createOmsGuCoinOrder(omsList);
+    }
+
     /**
      * 看课打卡状态自动更新
      */

+ 4 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseTrafficLogController.java

@@ -63,6 +63,10 @@ public class FsCourseTrafficLogController extends BaseController
             //普通销售只能看自己的数据
             param.setCompanyUserId(loginUser.getUser().getUserId());
         }
+        // typeFlag 默认查询类型 0(小程序) 和 2
+        if (param.getTypeFlag() == null || param.getTypeFlag().isEmpty()) {
+            param.setTypeFlag(java.util.Arrays.asList(0, 2));
+        }
         List<FsCourseTrafficLogListVO> list = fsCourseTrafficLogService.selectTrafficNew(param);
         return getDataTable(list);
     }

+ 59 - 2
fs-company/src/main/java/com/fs/company/controller/store/FsFinanceOrderController.java

@@ -24,7 +24,10 @@ import org.springframework.web.multipart.MultipartFile;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 @RestController
 @RequestMapping("/store/financeOrder")
@@ -153,7 +156,8 @@ public class FsFinanceOrderController extends BaseController {
     }
 
     /**
-     * 预览导入数据(解析Excel并匹配订单,不执行写入)
+     * 预览导入数据(解析Excel并匹配订单,不执行写入)。
+     * 返回结构与正式导入对齐:含 list/total/successCount/failCount,并在存在失败记录时生成失败 Excel。
      */
     @Log(title = "财务回款导入预览", businessType = BusinessType.OTHER)
     @PreAuthorize("@ss.hasPermi('store:financeOrder:import')")
@@ -197,7 +201,33 @@ public class FsFinanceOrderController extends BaseController {
             previewList.add(preview);
         }
 
-        return AjaxResult.success(previewList);
+        int total = previewList.size();
+        int successCount = (int) previewList.stream().filter(FsFinanceOrderImportPreviewDTO::isMatched).count();
+        int failCount = total - successCount;
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("list", previewList);
+        data.put("total", total);
+        data.put("successCount", successCount);
+        data.put("failCount", failCount);
+
+        String msg = "共 " + total + " 条,有效 " + successCount + " 条,无效 " + failCount + " 条";
+
+        // 存在失败记录时,生成与正式导入一致的失败 Excel 文件
+        if (failCount > 0) {
+            List<FsFinanceOrderImportFailDTO> failList = previewList.stream()
+                    .filter(p -> !p.isMatched())
+                    .map(p -> new FsFinanceOrderImportFailDTO(
+                            p.getOrderCode(), p.getDeliverySn(),
+                            p.getTotalPrice(), p.getCollectionPrice(),
+                            p.getReturnPrice(), p.getFailReason()))
+                    .collect(Collectors.toList());
+            ExcelUtil<FsFinanceOrderImportFailDTO> failUtil = new ExcelUtil<>(FsFinanceOrderImportFailDTO.class);
+            AjaxResult failResult = failUtil.exportExcel(failList, "回款导入预览失败记录");
+            data.put("failFile", failResult.get("msg"));
+        }
+
+        return AjaxResult.success(msg, data);
     }
 
     /** 根据快递单号匹配订单 */
@@ -238,4 +268,31 @@ public class FsFinanceOrderController extends BaseController {
         int rows = fsExternalOrderMapper.updateFsExternalOrder(fsExternalOrder);
         return toAjax(rows);
     }
+
+    /**
+     * 批量转未回款:将选中订单置为「未回款」状态(status=3 已发货,等价于 status!=4)。
+     * 仅修改 status,不动 return_price;对已是未回款的记录幂等处理。
+     */
+    @Log(title = "批量转未回款", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:financeOrder:edit')")
+    @PostMapping("/batchReturnUnpaid")
+    public AjaxResult batchReturnUnpaid(@RequestBody List<Long> orderIds) {
+        if (CollectionUtils.isEmpty(orderIds)) {
+            return AjaxResult.error("请至少选择一条订单");
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        String updateBy = loginUser.getUser().getUserName();
+        int successCount = 0;
+        for (Long orderId : orderIds) {
+            FsExternalOrder update = new FsExternalOrder();
+            update.setOrderId(orderId);
+            update.setStatus(3);              // 已发货 = 未回款(status != 4)
+            update.setUpdateBy(updateBy);
+            int rows = fsExternalOrderMapper.updateFsExternalOrder(update);
+            if (rows > 0) {
+                successCount++;
+            }
+        }
+        return AjaxResult.success("成功转未回款 " + successCount + " 条,失败 " + (orderIds.size() - successCount) + " 条");
+    }
 }

+ 50 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsGuCoinGoodsController.java

@@ -0,0 +1,50 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.his.domain.FsGuCoinGoods;
+import com.fs.his.param.FsGuCoinGoodsParam;
+import com.fs.his.service.IFsGuCoinGoodsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 谷币商城商品Controller(销售后台)
+ * 谷养正道商城专属商品查看(只读,商品维护在总后台)
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@RestController
+@RequestMapping("/his/guCoinGoods")
+public class FsGuCoinGoodsController extends BaseController
+{
+    @Autowired
+    private IFsGuCoinGoodsService fsGuCoinGoodsService;
+
+    /**
+     * 查询谷币商品列表(只读)
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinGoods:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsGuCoinGoodsParam param)
+    {
+        startPage();
+        List<FsGuCoinGoods> list = fsGuCoinGoodsService.selectFsGuCoinGoodsList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取谷币商品详细信息(只读)
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinGoods:query')")
+    @GetMapping(value = "/{goodsId}")
+    public AjaxResult getInfo(@PathVariable("goodsId") Long goodsId)
+    {
+        return AjaxResult.success(fsGuCoinGoodsService.selectFsGuCoinGoodsByGoodsId(goodsId));
+    }
+}

+ 113 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsGuCoinOrderController.java

@@ -0,0 +1,113 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsGuCoinOrder;
+import com.fs.his.param.FsGuCoinOrderManuallyParam;
+import com.fs.his.param.FsGuCoinOrderParam;
+import com.fs.his.service.IFsGuCoinOrderService;
+import com.fs.common.utils.ServletUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 谷币商城订单Controller(销售后台)
+ * 谷养正道商城订单管理 + 销售代下单
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@RestController
+@RequestMapping("/his/guCoinOrder")
+public class FsGuCoinOrderController extends BaseController
+{
+    @Autowired
+    private IFsGuCoinOrderService fsGuCoinOrderService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询谷币订单列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsGuCoinOrderParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getUser().getCompanyId());
+        List<FsGuCoinOrder> list = fsGuCoinOrderService.selectFsGuCoinOrderList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取谷币订单详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:query')")
+    @GetMapping(value = "/{orderId}")
+    public AjaxResult getInfo(@PathVariable("orderId") Long orderId)
+    {
+        return AjaxResult.success(fsGuCoinOrderService.selectFsGuCoinOrderByOrderId(orderId));
+    }
+
+    /**
+     * 发货
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:sendGoods')")
+    @Log(title = "谷币订单发货", businessType = BusinessType.UPDATE)
+    @PutMapping("/sendGoods")
+    public AjaxResult sendGoods(@RequestBody FsGuCoinOrder fsGuCoinOrder)
+    {
+        return toAjax(fsGuCoinOrderService.updateFsGuCoinOrder(fsGuCoinOrder));
+    }
+
+    /**
+     * 修改谷币订单
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:edit')")
+    @Log(title = "谷币订单", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsGuCoinOrder fsGuCoinOrder)
+    {
+        return toAjax(fsGuCoinOrderService.updateFsGuCoinOrder(fsGuCoinOrder));
+    }
+
+    /**
+     * 删除谷币订单
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:remove')")
+    @Log(title = "谷币订单", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{orderIds}")
+    public AjaxResult remove(@PathVariable Long[] orderIds)
+    {
+        return toAjax(fsGuCoinOrderService.deleteFsGuCoinOrderByOrderIds(orderIds));
+    }
+
+    /**
+     * 销售后台手动代下单(多商品合并一单)
+     * 销售人员为会员代下谷币商城订单,扣减会员谷币
+     */
+    @PreAuthorize("@ss.hasPermi('his:guCoinOrder:createByManually')")
+    @Log(title = "销售代下谷币订单", businessType = BusinessType.INSERT)
+    @PostMapping("/createByManually")
+    public R createByManually(@RequestBody @Validated FsGuCoinOrderManuallyParam param)
+    {
+        // 默认绑定当前登录销售
+        if (param.getCompanyUserId() == null) {
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            param.setCompanyUserId(loginUser.getUser().getUserId());
+        }
+        return fsGuCoinOrderService.createOrderByManually(param);
+    }
+}

+ 27 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreStatisticsScrmController.java

@@ -18,6 +18,7 @@ import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.his.vo.AppWatchLogReportVO;
+import com.fs.his.vo.FeiShuWatchLogReportVO;
 import com.fs.his.vo.FsUserReportVO;
 import com.fs.his.vo.WatchLogReportVO;
 import com.fs.hisStore.param.FsStoreStatisticsParam;
@@ -300,6 +301,32 @@ public class FsStoreStatisticsScrmController extends BaseController {
         return util.exportExcelSelectedColumns(list,"看课统计报表",selectedFields);
     }
 
+    /**
+     * 飞书看课统计报表(部门/销售维度)
+     */
+    @GetMapping("/feiShuWatchLogReport")
+    @DataScope(deptAlias = "cu", userAlias = "cu")
+    public TableDataInfo feiShuWatchLogReport(FsCourseWatchLogStatisticsListParam param) {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        return getDataTable(courseWatchLogService.selectFeiShuWatchLogReportVO(param));
+    }
+
+    /**
+     * 导出飞书看课统计报表
+     */
+    @GetMapping("/feiShuWatchLogReportExport")
+    @DataScope(deptAlias = "cu", userAlias = "cu")
+    public AjaxResult feiShuWatchLogReportExport(FsCourseWatchLogStatisticsListParam param) {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<FeiShuWatchLogReportVO> list = courseWatchLogService.selectFeiShuWatchLogReportVO(param);
+        ExcelUtil<FeiShuWatchLogReportVO> util = new ExcelUtil<FeiShuWatchLogReportVO>(FeiShuWatchLogReportVO.class);
+        return util.exportExcel(list, "飞书看课统计报表");
+    }
+
     /**
      * 获取所有有数据的字段名称列表(按注解sort排序,根据type过滤特定字段)
      * @param list 数据列表

+ 101 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsUserGuCoinController.java

@@ -0,0 +1,101 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsUserGuCoinLogs;
+import com.fs.his.enums.GuCoinLogTypeEnum;
+import com.fs.his.param.FsUserAddGuCoinParam;
+import com.fs.his.param.FsUserGuCoinLogsParam;
+import com.fs.his.service.IFsUserGuCoinLogsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 会员谷币Controller(销售后台)
+ * 销售后台录入会员谷币(填写发放备注、消费金额,留存操作日志,支持溯源对账)
+ * 谷币:谷养正道商城专属积分,无有效期
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@RestController
+@RequestMapping("/his/userGuCoin")
+public class FsUserGuCoinController extends BaseController
+{
+    @Autowired
+    private IFsUserGuCoinLogsService fsUserGuCoinLogsService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询会员谷币记录列表(溯源对账)
+     */
+    @PreAuthorize("@ss.hasPermi('his:userGuCoin:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserGuCoinLogsParam param)
+    {
+        startPage();
+        List<FsUserGuCoinLogs> list = fsUserGuCoinLogsService.selectFsUserGuCoinLogsList(param);
+        for (FsUserGuCoinLogs vo : list) {
+            if (vo.getPhone() != null && !vo.getPhone().isEmpty()) {
+                vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            }
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出会员谷币记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:userGuCoin:export')")
+    @Log(title = "会员谷币记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserGuCoinLogsParam param)
+    {
+        List<FsUserGuCoinLogs> list = fsUserGuCoinLogsService.selectFsUserGuCoinLogsList(param);
+        ExcelUtil<FsUserGuCoinLogs> util = new ExcelUtil<FsUserGuCoinLogs>(FsUserGuCoinLogs.class);
+        return util.exportExcel(list, "会员谷币记录数据");
+    }
+
+    /**
+     * 销售后台录入会员谷币
+     * 填写发放备注、消费金额,留存操作日志,支持溯源对账
+     */
+    @PreAuthorize("@ss.hasPermi('his:userGuCoin:add')")
+    @Log(title = "销售后台录入会员谷币", businessType = BusinessType.INSERT)
+    @PostMapping("/addGuCoin")
+    public R addGuCoin(@RequestBody @Validated FsUserAddGuCoinParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyUserId = loginUser.getUser().getUserId();
+        String operatorName = loginUser.getUser().getUserName();
+        return fsUserGuCoinLogsService.addGuCoin(param,
+                GuCoinLogTypeEnum.TYPE_5.getValue(),
+                companyUserId,
+                operatorName,
+                2);
+    }
+
+    /**
+     * 根据id获取会员谷币记录详情
+     */
+    @PreAuthorize("@ss.hasPermi('his:userGuCoin:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsUserGuCoinLogsService.selectFsUserGuCoinLogsById(id));
+    }
+}

+ 20 - 0
fs-service/src/main/java/com/fs/course/mapper/FeiShuCourseWatchLogMapper.java

@@ -2,9 +2,14 @@ package com.fs.course.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FeiShuCourseWatchLog;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.his.vo.FeiShuCourseReportVO;
+import com.fs.his.vo.FeiShuWatchLogReportVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
+import java.util.List;
+
 
 public interface FeiShuCourseWatchLogMapper extends BaseMapper<FeiShuCourseWatchLog> {
 
@@ -18,4 +23,19 @@ public interface FeiShuCourseWatchLogMapper extends BaseMapper<FeiShuCourseWatch
      * 修改看课记录
      */
     void updateByVideoIdAndUserIdAndCompanyUserIdAndPeriodId(FeiShuCourseWatchLog feishuCourseWatchLog);
+
+    /**
+     * 飞书看课统计:仅按公司维度
+     */
+    List<FeiShuCourseReportVO> selectFeiShuWatchStatistics(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 飞书看课统计:销售维度基础数据
+     */
+    List<FeiShuWatchLogReportVO> selectFeiShuSalesBaseData(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 飞书看课统计:部门维度基础数据
+     */
+    List<FeiShuWatchLogReportVO> selectFeiShuCompanyDeptStats(FsCourseWatchLogStatisticsListParam param);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -589,6 +589,12 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "and user_id = #{userId} limit 1 ")
     FsCourseWatchLog getWatchCourseVideoIsOpen(@Param("userId") Long userId,@Param("videoId") Long videoId);
 
+    /**
+     * 统计用户某小节完课(log_type=2)记录数,用于谷币奖励校验
+     */
+    @Select("select count(*) from fs_course_watch_log where user_id = #{userId} and video_id = #{videoId} and log_type = 2")
+    int countFinishedWatchLog(@Param("userId") Long userId, @Param("videoId") Long videoId);
+
     void batchUpdateWatchLogIsOpen(@Param("list") List<FsCourseWatchLog> batchList);
 
     @Select("select count(*) from fs_course_watch_log where user_id =#{userId} and project =#{projectId} ")

+ 3 - 2
fs-service/src/main/java/com/fs/course/param/FsCourseTrafficLogParam.java

@@ -6,6 +6,7 @@ import org.springframework.format.annotation.DateTimeFormat;
 
 import java.time.YearMonth;
 import java.util.Date;
+import java.util.List;
 
 @Data
 public class FsCourseTrafficLogParam {
@@ -46,8 +47,8 @@ public class FsCourseTrafficLogParam {
     private Long companyUserId;
 
     /**
-     * 类型标识 0 小程序 1 app
+     * 类型标识 [0 小程序, 1 app, 2 其他]
      */
-    private String typeFlag;
+    private List<Integer> typeFlag;
 
 }

+ 12 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -156,6 +156,18 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
      */
     List<AppCourseReportVO> selectAppCourseReportVO(FsCourseWatchLogStatisticsListParam param);
 
+    /**
+     * 飞书看课统计报表(仅公司维度,feishu_course_watch_log 表)
+     * @param param
+     * @return
+     */
+    List<FeiShuCourseReportVO> selectFeiShuCourseReportVO(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 飞书看课统计报表(部门/销售维度,feishu_course_watch_log 表)
+     */
+    List<FeiShuWatchLogReportVO> selectFeiShuWatchLogReportVO(FsCourseWatchLogStatisticsListParam param);
+
     /**
      * 会员积分统计报表
      * @param param

+ 25 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -19,8 +19,11 @@ import com.fs.course.service.IFsCourseQuestionBankService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.service.IFsUserGuCoinLogsService;
 import com.fs.system.service.ISysConfigService;
 import lombok.Getter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -39,6 +42,7 @@ import java.util.stream.Collectors;
 @Service
 public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankService
 {
+    private static final Logger logger = LoggerFactory.getLogger(FsCourseQuestionBankServiceImpl.class);
     @Autowired
     private FsCourseQuestionBankMapper fsCourseQuestionBankMapper;
     @Autowired
@@ -63,6 +67,9 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
     @Autowired
     private CompanyUserMapper companyUserMapper;
 
+    @Autowired
+    private IFsUserGuCoinLogsService fsUserGuCoinLogsService;
+
     /**
      * 查询题库
      *
@@ -237,6 +244,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         if (thisRightCount == questions.size()) {
             logs.setIsRight(1);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
+            // 答题全对:触发看课谷币奖励(完整看课+答题全对,每节150谷币,重复只发一次)
+            try {
+                fsUserGuCoinLogsService.rewardCourseWatchGuCoin(param.getUserId(), param.getVideoId(), param.getCourseId());
+            } catch (Exception e) {
+                logger.error("看课谷币奖励发放异常 userId={}, videoId={}", param.getUserId(), param.getVideoId(), e);
+            }
             return R.ok("答题成功");
         } else {
             logs.setIsRight(0);
@@ -342,6 +355,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         if (thisRightCount == param.getQuestions().size()) {
             logs.setIsRight(1);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
+            // 答题全对:触发看课谷币奖励(完整看课+答题全对,每节150谷币,重复只发一次)
+            try {
+                fsUserGuCoinLogsService.rewardCourseWatchGuCoin(param.getUserId(), param.getVideoId(), param.getCourseId());
+            } catch (Exception e) {
+                logger.error("看课谷币奖励发放异常 userId={}, videoId={}", param.getUserId(), param.getVideoId(), e);
+            }
             return R.ok("答题成功");
         } else {
             logs.setIsRight(0);
@@ -461,6 +480,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         if (thisRightCount == param.getQuestions().size()) {
             logs.setIsRight(1);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
+            // 答题全对:触发看课谷币奖励(完整看课+答题全对,每节150谷币,重复只发一次)
+            try {
+                fsUserGuCoinLogsService.rewardCourseWatchGuCoin(param.getUserId(), param.getVideoId(), param.getCourseId());
+            } catch (Exception e) {
+                logger.error("看课谷币奖励发放异常 userId={}, videoId={}", param.getUserId(), param.getVideoId(), e);
+            }
             return R.ok("答题成功");
         } else {
             logs.setIsRight(0);

+ 104 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -123,6 +123,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private ICompanyUserCacheService companyUserCacheService;
 
+    @Autowired
+    private FeiShuCourseWatchLogMapper feiShuCourseWatchLogMapper;
 
     @Autowired
     private IFsUserCourseCacheService fsUserCourseCacheService;
@@ -1360,6 +1362,108 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 redPacketStatistics, param);
     }
 
+    /**
+     * 飞书看课统计报表(仅公司维度,只查 feishu_course_watch_log 表,无答题/红包数据)
+     */
+    @Override
+    public List<FeiShuCourseReportVO> selectFeiShuCourseReportVO(FsCourseWatchLogStatisticsListParam param) {
+        if (param.getSTime() != null && param.getETime() != null) {
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+            param.setStartDate(simpleDateFormat.format(param.getSTime()));
+            param.setEndDate(simpleDateFormat.format(param.getETime()));
+        }
+        // 仅销售公司维度
+        param.setDimension("company");
+        // 查询公司基础信息
+        List<FsCourseReportVO> companyList = fsCourseWatchLogMapper.selectCompanyBaseInfo(param);
+        if (CollectionUtils.isEmpty(companyList)) {
+            return Collections.emptyList();
+        }
+        List<Long> companyIds = companyList.stream()
+                .map(FsCourseReportVO::getCompanyId)
+                .collect(Collectors.toList());
+        param.setCompanyIds(companyIds);
+
+        // 查飞书看课统计数据
+        List<FeiShuCourseReportVO> watchStatistics = feiShuCourseWatchLogMapper.selectFeiShuWatchStatistics(param);
+        Map<String, FeiShuCourseReportVO> watchMap = watchStatistics.stream()
+                .collect(Collectors.toMap(
+                        stats -> String.valueOf(stats.getCompanyId()),
+                        Function.identity(),
+                        (existing, replacement) -> existing
+                ));
+
+        // 组装结果:用 companyList 作为基准,填充看课数据
+        List<FeiShuCourseReportVO> companyListResult = new ArrayList<>();
+        for (FsCourseReportVO company : companyList) {
+            FeiShuCourseReportVO vo = new FeiShuCourseReportVO();
+            vo.setCompanyId(company.getCompanyId());
+            vo.setCompanyName(company.getCompanyName());
+            FeiShuCourseReportVO watch = watchMap.getOrDefault(company.getCompanyId().toString(), new FeiShuCourseReportVO());
+            vo.setAccessCount(watch.getAccessCount());
+            vo.setPendingCount(watch.getPendingCount());
+            vo.setWatchingCount(watch.getWatchingCount());
+            vo.setFinishedCount(watch.getFinishedCount());
+            vo.setInterruptedCount(watch.getInterruptedCount());
+            // 计算看课率、完课率(复用现有计算方法)
+            if (vo.getAccessCount() != null && vo.getAccessCount() > 0) {
+                int totalWatching = (vo.getWatchingCount() != null ? vo.getWatchingCount() : 0)
+                        + (vo.getFinishedCount() != null ? vo.getFinishedCount() : 0);
+                vo.setWatchRate(BigDecimal.valueOf(totalWatching * 100.0 / vo.getAccessCount())
+                        .setScale(2, BigDecimal.ROUND_HALF_UP));
+                vo.setFinishRate(BigDecimal.valueOf(
+                        (vo.getFinishedCount() != null ? vo.getFinishedCount() : 0) * 100.0 / vo.getAccessCount())
+                        .setScale(2, BigDecimal.ROUND_HALF_UP));
+            } else {
+                vo.setWatchRate(BigDecimal.ZERO);
+                vo.setFinishRate(BigDecimal.ZERO);
+            }
+            companyListResult.add(vo);
+        }
+        return companyListResult;
+    }
+
+    /**
+     * 飞书看课统计报表(部门/销售维度,查 feishu_course_watch_log 表)
+     * 无答题/红包统计,无会员数/当前会员上线数
+     */
+    @Override
+    public List<FeiShuWatchLogReportVO> selectFeiShuWatchLogReportVO(FsCourseWatchLogStatisticsListParam param) {
+        if (StringUtils.isNotEmpty(param.getUserPhone())) {
+            param.setUserPhone(PhoneUtil.encryptPhone(param.getUserPhone()));
+        }
+        if (param.getSTime() != null && param.getETime() != null) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            param.setStartDate(sdf.format(param.getSTime()));
+            param.setEndDate(sdf.format(param.getETime()));
+        }
+        String dimension = param.getDimension();
+        List<FeiShuWatchLogReportVO> baseData;
+        if ("company".equals(dimension)) {
+            baseData = feiShuCourseWatchLogMapper.selectFeiShuCompanyDeptStats(param);
+        } else {
+            // 默认 sales 维度
+            param.setDimension("sales");
+            baseData = feiShuCourseWatchLogMapper.selectFeiShuSalesBaseData(param);
+        }
+        if (CollectionUtils.isEmpty(baseData)) {
+            return Collections.emptyList();
+        }
+        // 计算完课率
+        for (FeiShuWatchLogReportVO item : baseData) {
+            int finished = item.getFinishedCount() != null ? item.getFinishedCount() : 0;
+            int unfinished = item.getUnfinishedCount() != null ? item.getUnfinishedCount() : 0;
+            int total = finished + unfinished;
+            if (total > 0) {
+                item.setCompletionRate(BigDecimal.valueOf(finished * 100.0 / total)
+                        .setScale(2, BigDecimal.ROUND_HALF_UP));
+            } else {
+                item.setCompletionRate(BigDecimal.ZERO);
+            }
+        }
+        return baseData;
+    }
+
     @Override
     public List<AppCourseReportVO> selectAppCourseReportVO(FsCourseWatchLogStatisticsListParam param) {
         // 时间转字符串

+ 10 - 0
fs-service/src/main/java/com/fs/erp/service/IErpOrderService.java

@@ -55,5 +55,15 @@ public interface IErpOrderService
      * 获取奖品订单
      */
     default ErpOrderQueryResponse getRewardOrder(ErpOrderQueryRequert request) { return null; }
+
+    /**
+     * 推送谷币订单到聚水潭(无货到付款推两次逻辑,固定shop_code)
+     */
+    default ErpOrderResponse batchAddGuCoinOrder(List<ErpOrder> erpOrders) throws InterruptedException { return null; }
+
+    /**
+     * 查询谷币订单(聚水潭发货状态同步用)
+     */
+    default ErpOrderQueryResponse getGuCoinOrder(ErpOrderQueryRequert param, String type) { return null; }
 }
 

+ 239 - 0
fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java

@@ -26,6 +26,7 @@ import com.fs.erp.service.IErpOrderService;
 import com.fs.his.domain.*;
 import com.fs.his.mapper.*;
 import com.fs.his.service.IFsIntegralOrderService;
+import com.fs.his.service.IFsGuCoinOrderService;
 import com.fs.his.service.IFsStoreOrderItemService;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.his.service.IFsStoreProductService;
@@ -107,6 +108,11 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
     @Autowired
     private IFsIntegralOrderService fsIntegralOrderService;
 
+    @Autowired
+    private FsGuCoinOrderMapper fsGuCoinOrderMapper;
+    @Autowired
+    private IFsGuCoinOrderService fsGuCoinOrderService;
+
     @Autowired
     private FsIntegralGoodsMapper fsIntegralGoodsMapper;
     @Autowired
@@ -1407,6 +1413,239 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
         return erpOrderResponse;
     }
 
+    /**
+     * 推送谷币订单到聚水潭(固定 shop_code,无货到付款推两次逻辑,按 payType 推真实金额)
+     */
+    @Override
+    public ErpOrderResponse batchAddGuCoinOrder(List<ErpOrder> erpOrders) throws InterruptedException {
+        List<ShopOrderDTO> normalOrders = new ArrayList<>();
+        for (ErpOrder order : erpOrders) {
+            FsGuCoinOrder fsGuCoinOrder = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderCode(order.getPlatform_code());
+            log.info("ERP谷币订单号: {}, 订单信息: {}", order.getPlatform_code(), JSON.toJSONString(fsGuCoinOrder));
+            if (fsGuCoinOrder == null) {
+                continue;
+            }
+            ShopOrderDTO shopOrderDTO = new ShopOrderDTO();
+            shopOrderDTO.setShopId(Long.valueOf(order.getShop_code()));
+            shopOrderDTO.setSoId(order.getPlatform_code());
+            shopOrderDTO.setOrderDate(order.getDeal_datetime());
+            shopOrderDTO.setShopStatus(OrderStatusEnum.WAIT_SELLER_SEND_GOODS.name());
+            shopOrderDTO.setShopBuyerId(order.getBuyer_account());
+            shopOrderDTO.setReceiverState(order.getReceiver_province());
+            shopOrderDTO.setReceiverCity(order.getReceiver_city());
+            shopOrderDTO.setReceiverDistrict(order.getReceiver_district());
+            shopOrderDTO.setReceiverAddress(order.getReceiver_address());
+            shopOrderDTO.setReceiverName(order.getReceiver_name());
+            shopOrderDTO.setReceiverPhone(order.getReceiver_mobile());
+            // 实际支付金额(按 payType 推真实金额)
+            BigDecimal payMoney = fsGuCoinOrder.getPayMoney() == null ? BigDecimal.ZERO : fsGuCoinOrder.getPayMoney();
+            shopOrderDTO.setPayAmount(payMoney.doubleValue());
+            shopOrderDTO.setRemark(order.getRemark());
+            shopOrderDTO.setFreight(Double.valueOf("0.00"));
+            shopOrderDTO.setBuyerMessage(order.getBuyer_memo());
+
+            // 订单商品项(解析 itemJson,imgUrl 直接取快照不查表)
+            List<OrderItemDTO> itemDTOList = new ArrayList<>();
+            Object parsedJson = JSON.parse(fsGuCoinOrder.getItemJson());
+            JSONArray jsonArray = (JSONArray) parsedJson;
+            for (int i = 0; i < jsonArray.size(); i++) {
+                OrderItemDTO orderItemDTO = new OrderItemDTO();
+                JSONObject jsonObject = jsonArray.getJSONObject(i);
+                String barCode = jsonObject.getString("barCode");
+                String productName = jsonObject.getString("goodsName");
+                String imgUrl = jsonObject.getString("imgUrl");
+                BigDecimal cash = jsonObject.getBigDecimal("cash");
+                if (cash == null) {
+                    cash = BigDecimal.ZERO;
+                }
+                Integer numVal = jsonObject.getInteger("num");
+                int qty = (numVal != null && numVal > 0) ? numVal : 1;
+                orderItemDTO.setSkuId(barCode);
+                orderItemDTO.setShopSkuId(barCode);
+                orderItemDTO.setName(productName);
+                orderItemDTO.setShopIId(barCode);
+                orderItemDTO.setAmount(cash);
+                orderItemDTO.setPic(imgUrl);
+                orderItemDTO.setPrice(cash);
+                orderItemDTO.setQty(qty);
+                orderItemDTO.setOuterOiId(String.format("%s", fsGuCoinOrder.getOrderCode()));
+                itemDTOList.add(orderItemDTO);
+            }
+            shopOrderDTO.setItems(itemDTOList);
+
+            // 支付信息(payment 标签按 payType)
+            PaymentDTO paymentDTO = new PaymentDTO();
+            paymentDTO.setAmount(payMoney.doubleValue());
+            paymentDTO.setOuterPayId(order.getPlatform_code());
+            paymentDTO.setPayDate(order.getDeal_datetime());
+            Integer payType = fsGuCoinOrder.getPayType();
+            String paymentLabel;
+            if (payType != null && payType == 1) {
+                paymentLabel = "谷币支付";
+            } else if (payType != null && payType == 2) {
+                paymentLabel = "微信支付";
+            } else if (payType != null && payType == 3) {
+                paymentLabel = "谷币+微信支付";
+            } else {
+                paymentLabel = "谷币支付";
+            }
+            paymentDTO.setPayment(paymentLabel);
+            paymentDTO.setBuyerAccount(order.getBuyer_account());
+            paymentDTO.setSellerAccount("平台销售");
+            shopOrderDTO.setPay(paymentDTO);
+            // 无货到付款推两次逻辑
+            normalOrders.add(shopOrderDTO);
+        }
+
+        // 批量推送(每50条一批)
+        int batchSize = 50;
+        ErpOrderResponseDTO lastResponse = null;
+        for (int i = 0; i < normalOrders.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, normalOrders.size());
+            List<ShopOrderDTO> batch = new ArrayList<>(normalOrders.subList(i, endIndex));
+            lastResponse = jstErpHttpService.batchUpload(batch);
+            List<ErpOrderResponseDTO.OrderData> datas = lastResponse.getDatas();
+            for (ErpOrderResponseDTO.OrderData data : datas) {
+                if (data != null && data.getIssuccess()) {
+                    FsGuCoinOrder fsGuCoinOrder1 = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderCode(data.getSoId());
+                    if (fsGuCoinOrder1 != null && StringUtils.isEmpty(fsGuCoinOrder1.getExtendOrderId())) {
+                        // 推送成功后写入待发货 REDIS
+                        redisCache.setCacheObject("delivery" + ":" + data.getOId(), fsGuCoinOrder1.getOrderCode());
+                        // 写入聚水潭订单号
+                        fsGuCoinOrder1.setExtendOrderId(data.getOId().toString());
+                        fsGuCoinOrderService.updateFsGuCoinOrder(fsGuCoinOrder1);
+                    }
+                }
+            }
+            log.info("批量推送谷币订单,数量: {}", batch.size());
+            Thread.sleep(600);
+        }
+        // 返回最后一次推送结果
+        if (lastResponse != null && CollectionUtils.isEmpty(lastResponse.getDatas())) {
+            log.info("推送ERP返回结果: {}", lastResponse);
+            throw new IllegalArgumentException("推送ERP返回数不应该为0");
+        }
+        // 没有订单推送时兜底
+        if (lastResponse == null) {
+            lastResponse = new ErpOrderResponseDTO();
+            ErpOrderResponseDTO.OrderData data = new ErpOrderResponseDTO.OrderData();
+            data.setOId(0);
+            lastResponse.setDatas(Collections.singletonList(data));
+        }
+        ErpOrderResponseDTO.OrderData orderData = lastResponse.getDatas().get(0);
+        ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
+        erpOrderResponse.setSuccess(true);
+        erpOrderResponse.setCode(String.valueOf(orderData.getOId()));
+        return erpOrderResponse;
+    }
+
+    /**
+     * 查询谷币订单(聚水潭发货状态同步用)
+     */
+    @Override
+    public ErpOrderQueryResponse getGuCoinOrder(ErpOrderQueryRequert param, String type) {
+        // 1. 构建查询请求DTO
+        OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();
+        if (StringUtils.isNotEmpty(param.getCode())) {
+            requestDTO.setOIds(Collections.singletonList(Long.valueOf(param.getCode())));
+        } else {
+            requestDTO.setOIds(param.getO_ids());
+            requestDTO.setShopId(Integer.parseInt(param.getShop_id()));
+            requestDTO.setPageIndex(param.getPage_index());
+            requestDTO.setPageSize(param.getPage_size());
+        }
+        // 2. 调用ERP服务查询订单
+        OrderQueryResponseDTO query = jstErpHttpService.query(requestDTO);
+
+        log.info("ERP查询谷币订单返回, orders数量: {}, orders详情: {}",
+                query.getOrders() != null ? query.getOrders().size() : 0,
+                JSON.toJSONString(query.getOrders()));
+
+        // 3. 构建响应对象
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        // 4. 转换订单数据(过滤本地查不到的订单)
+        if (query.getOrders() != null && !query.getOrders().isEmpty()) {
+            List<ErpOrderQuery> erpOrders = query.getOrders().stream()
+                    .map(this::convertToErpGuCoinOrderQuery)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+            response.setOrders(erpOrders);
+            response.setHasNext(query.getHasNext());
+        } else {
+            response.setOrders(Collections.emptyList());
+        }
+        return response;
+    }
+
+    /**
+     * 转换聚水潭订单查询结果为谷币订单查询对象
+     * 修复积分版未 set o_id 的隐患(保证 Task 删 Redis delivery:{o_id} 命中);
+     * 本地查不到订单返回 null(不抛 Asserts,避免单条异常中断整批同步)
+     */
+    private ErpOrderQuery convertToErpGuCoinOrderQuery(OrderQueryResponseDTO.Order order) {
+        FsGuCoinOrder fsGuCoinOrder = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderCode(order.getSoId());
+        if (fsGuCoinOrder == null) {
+            log.warn("谷币订单本地未找到,跳过: {}", order.getSoId());
+            return null;
+        }
+        ErpOrderQuery erpOrder = new ErpOrderQuery();
+        // 修复积分版隐患:显式 set o_id
+        if (order.getOId() != null) {
+            erpOrder.setO_id(order.getOId().intValue());
+        }
+        // 设置基本订单信息
+        erpOrder.setCode(order.getSoId());
+        // 计算订单总数量
+        if (order.getItems() != null && !order.getItems().isEmpty()) {
+            int totalQty = order.getItems().stream()
+                    .mapToInt(OrderQueryResponseDTO.OrderItem::getQty)
+                    .sum();
+            erpOrder.setQty(totalQty);
+        }
+        // 金额相关信息
+        erpOrder.setAmount(order.getAmount() != null ? order.getAmount().doubleValue() : null);
+        erpOrder.setPayment(order.getPaidAmount() != null ? order.getPaidAmount().doubleValue() : null);
+        // 其他订单属性
+        erpOrder.setCod(order.getIsCod());
+        erpOrder.setPlatform_code(order.getOrderFrom());
+        // 尝试解析创建时间
+        try {
+            if (order.getCreated() != null) {
+                SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                erpOrder.setCreatetime(formatter.parse(order.getCreated()));
+            }
+        } catch (Exception e) {
+            // 日期解析异常,忽略
+        }
+        // 店铺信息
+        erpOrder.setShop_name(order.getShopName());
+        erpOrder.setShop_code(String.valueOf(order.getShopId()));
+        // 物流信息
+        erpOrder.setExpress_name(order.getLogisticsCompany());
+        erpOrder.setExpress_code(order.getLId());
+        // 收件人信息
+        erpOrder.setReceiver_name(fsGuCoinOrder.getUserName());
+        erpOrder.setReceiver_phone(fsGuCoinOrder.getUserPhone());
+        erpOrder.setReceiver_mobile(fsGuCoinOrder.getUserPhone());
+        erpOrder.setReceiver_address(fsGuCoinOrder.getUserAddress());
+        // 已发货
+        if (ObjectUtil.equal(order.getStatus(), ErpQueryOrderStatusEnum.SENT.getCode())) {
+            List<ErpDeliverys> deliverysList = new ArrayList<>();
+            ErpDeliverys delivery = new ErpDeliverys();
+            delivery.setMail_no(order.getLId());
+            delivery.setExpress_code(order.getLcId());
+            delivery.setExpress_name(order.getLogisticsCompany());
+            delivery.setDelivery(true);
+            delivery.setCode(order.getSoId());
+            deliverysList.add(delivery);
+            erpOrder.setDeliverys(deliverysList);
+            erpOrder.setDelivery_state(1);
+        } else {
+            erpOrder.setDelivery_state(0);
+        }
+        return erpOrder;
+    }
+
     /**
      * 推送奖品订单
      */

+ 42 - 0
fs-service/src/main/java/com/fs/his/domain/FsGuCoinCart.java

@@ -0,0 +1,42 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 谷币商城购物车对象 fs_gu_coin_cart
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+@TableName("fs_gu_coin_cart")
+@Data
+public class FsGuCoinCart {
+    /** 主键ID */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 用户ID */
+    private Long userId;
+    /** 谷币商品ID */
+    private Long goodsId;
+    /** 所需谷币-单价 */
+    private Long guCoin;
+    /** 所需金额-单价 */
+    private BigDecimal cash;
+    /** 数量 */
+    private Integer cartNum;
+    /** 添加时间 */
+    private LocalDateTime createTime;
+    /** 修改时间 */
+    private LocalDateTime updateTime;
+
+    /** 是否追加数量(非数据库字段,控制 insertOrUpdate 行为) */
+    @TableField(exist = false)
+    private Boolean addToExist;
+}

+ 81 - 0
fs-service/src/main/java/com/fs/his/domain/FsGuCoinGoods.java

@@ -0,0 +1,81 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 谷币商城商品对象 fs_gu_coin_goods
+ * 谷养正道商城专属商品,兑换所需谷币
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinGoods extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 商品id */
+    @TableId(type = IdType.AUTO)
+    private Long goodsId;
+
+    /** 封面图 */
+    @Excel(name = "封面图")
+    private String imgUrl;
+
+    /** 组图 */
+    @Excel(name = "组图")
+    private String images;
+
+    /** 商品名称 */
+    @Excel(name = "商品名称")
+    private String goodsName;
+
+    /** 原价 */
+    @Excel(name = "原价")
+    private BigDecimal otPrice;
+
+    /** 商品分类 */
+    @Excel(name = "商品分类")
+    private Long goodsType;
+
+    /** 状态 1上架 0下架 */
+    @Excel(name = "状态")
+    private Long status;
+
+    /** 所需谷币 */
+    @Excel(name = "所需谷币")
+    private Long guCoin;
+
+    /** 支付金额(谷币+现金混合支付时) */
+    @Excel(name = "支付金额")
+    private BigDecimal cash;
+
+    /** 排序 */
+    @Excel(name = "排序")
+    private Long sort;
+
+    /** 库存 */
+    @Excel(name = "库存")
+    private Long stock;
+
+    /** 详情 */
+    @Excel(name = "详情")
+    private String descs;
+
+    @Excel(name = "产品编码")
+    private String barCode;
+
+    /** 运费模板ID */
+    private Long tempId;
+
+    /** 非数据库字段 购买数量(订单商品快照用) */
+    @TableField(exist = false)
+    private Long num;
+}

+ 138 - 0
fs-service/src/main/java/com/fs/his/domain/FsGuCoinOrder.java

@@ -0,0 +1,138 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 谷币商城订单对象 fs_gu_coin_order
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinOrder
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 订单id */
+    @TableId
+    private Long orderId;
+
+    /** 订单编号 */
+    @Excel(name = "订单编号")
+    private String orderCode;
+
+    /** 会员id */
+    private Long userId;
+
+    /** 会员名称 */
+    @Excel(name = "会员名称")
+    private String userName;
+
+    /** 会员电话 */
+    @Excel(name = "会员电话")
+    private String userPhone;
+
+    /** 会员地址 */
+    @Excel(name = "会员地址")
+    private String userAddress;
+
+    /** 商品信息JSON */
+    @Excel(name = "商品信息")
+    private String itemJson;
+
+    /** 订单总谷币 */
+    private Long totalGuCoin;
+
+    /** 折扣谷币 */
+    private Long discountGuCoin;
+
+    /** 实付谷币 */
+    @Excel(name = "支付谷币")
+    private Long guCoin;
+
+    /** 订单总金额 */
+    private BigDecimal totalMoney;
+
+    /** 折扣金额 */
+    private BigDecimal discountMoney;
+
+    /** 实付金额 */
+    @Excel(name = "支付金额")
+    private BigDecimal payMoney;
+
+    /** 运费 */
+    private BigDecimal deliveryMoney;
+
+    /** 是否支付 0未支付 1已支付 */
+    @Excel(name = "是否支付 0未支付 1已支付")
+    private Integer isPay;
+
+    /** 支付时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "支付时间")
+    private Date payTime;
+
+    /** 支付类型 1谷币 2现金 3谷币+现金 */
+    @Excel(name = "支付类型 1谷币 2现金 3谷币+现金")
+    private Integer payType;
+    /** 1待发货 2待收货 3已完成 4待支付 5取消 */
+    @Excel(name = "状态")
+    private Integer status;
+
+    @Excel(name = "商品编号")
+    private String barCode;
+
+    /** 快递公司编号 */
+    @Excel(name = "快递公司编号")
+    private String deliveryCode;
+
+    /** 快递名称 */
+    @Excel(name = "快递名称")
+    private String deliveryName;
+
+    /** 快递单号 */
+    @Excel(name = "快递单号")
+    private String deliverySn;
+
+    /** 发货时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "发货时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date deliveryTime;
+
+    /** 企业微信ID */
+    @Excel(name = "企业微信ID")
+    private Long qwUserId;
+
+    /** 销售ID */
+    @Excel(name = "销售ID")
+    private Long companyUserId;
+
+    /** 销售公司ID */
+    @Excel(name = "销售公司ID")
+    private Long companyId;
+
+    private String loginAccount;
+
+    private String remark;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(exist = false)
+    private Date updateTime;
+
+    /** 聚水潭订单号(推送聚水潭后返回的o_id) */
+    private String extendOrderId;
+
+    /** 非数据库字段 会员id集合 */
+    @TableField(exist = false)
+    private Long[] userIds;
+}

+ 4 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -51,6 +51,10 @@ public class FsUser extends BaseEntity
     @Excel(name = "用户积分")
     private Long integral;
 
+    /** 谷币余额(谷养正道商城专属积分,无有效期) */
+    @Excel(name = "谷币余额")
+    private Long guCoin;
+
     /** 1为正常,0为禁止 */
     @Excel(name = "1为正常,0为禁止")
     private Integer status;

+ 75 - 0
fs-service/src/main/java/com/fs/his/domain/FsUserGuCoinLogs.java

@@ -0,0 +1,75 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 会员谷币记录对象 fs_user_gu_coin_logs
+ * 记录谷币的每一笔变动,含操作人/消费金额/备注,支持溯源对账
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsUserGuCoinLogs extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键id */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 会员id */
+    @Excel(name = "会员id")
+    private Long userId;
+
+    /** 类型 1看课获得 2商城消费 3订单退款返还 4总后台录入 5销售后台录入 */
+    @Excel(name = "类型 1看课获得 2商城消费 3订单退款返还 4总后台录入 5销售后台录入")
+    private Integer logType;
+
+    /** 谷币 正数增加 负数减少 */
+    @Excel(name = "谷币")
+    private Long guCoin;
+
+    /** 变动后谷币余额 */
+    @Excel(name = "变动后余额")
+    private Long balance;
+
+    /** 关联业务id(课程小节id/订单号/操作人id) */
+    @Excel(name = "关联业务id")
+    private String businessId;
+
+    /** 消费金额(录入谷币时填写,用于对账) */
+    @Excel(name = "消费金额")
+    private BigDecimal consumeAmount;
+
+    /** 发放备注/操作备注 */
+    @Excel(name = "备注")
+    private String remark;
+
+    /** 操作人id */
+    @Excel(name = "操作人id")
+    private Long operatorId;
+
+    /** 操作人姓名 */
+    @Excel(name = "操作人姓名")
+    private String operatorName;
+
+    /** 操作来源 1总后台 2销售后台 3系统自动 */
+    @Excel(name = "操作来源 1总后台 2销售后台 3系统自动")
+    private Integer operatorSource;
+
+    /** 会员昵称(冗余便于查询) */
+    private String nickName;
+
+    /** 会员电话(冗余便于查询) */
+    private String phone;
+
+    /** 状态 1有效 0撤销 */
+    private Integer status;
+}

+ 37 - 0
fs-service/src/main/java/com/fs/his/dto/ComputeGuCoinOrderMoneyDTO.java

@@ -0,0 +1,37 @@
+package com.fs.his.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 谷币商城订单金额计算结果
+ * 与积分商城 ComputeIntegralOrderMoneyDTO 对称,谷币使用 Long 类型
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+@Data
+public class ComputeGuCoinOrderMoneyDTO {
+
+    /** 总谷币 */
+    private Long totalGuCoin = 0L;
+
+    /** 支付谷币 */
+    private Long guCoin = 0L;
+
+    /** 优惠谷币(谷币商城暂不实现优惠,恒为0) */
+    private Long discountGuCoin = 0L;
+
+    /** 总金额 */
+    private BigDecimal totalCash = BigDecimal.ZERO;
+
+    /** 支付金额 */
+    private BigDecimal cash = BigDecimal.ZERO;
+
+    /** 优惠金额(谷币商城暂不实现优惠,恒为0) */
+    private BigDecimal discountCash = BigDecimal.ZERO;
+
+    /** 运费 */
+    private BigDecimal deliveryMoney = BigDecimal.ZERO;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/his/enums/BusinessTypeEnum.java

@@ -8,6 +8,7 @@ import lombok.Getter;
 public enum BusinessTypeEnum {
     INTEGRAL_ORDER("integral", 6, "积分商城订单支付"),
     REWARD_ORDER("reward", 10, "奖励商品订单支付"),
+    GU_COIN_ORDER("gucoin", 11, "谷币商城订单支付"),
     ;
 
     private final String prefix;

+ 34 - 0
fs-service/src/main/java/com/fs/his/enums/GuCoinLogTypeEnum.java

@@ -0,0 +1,34 @@
+package com.fs.his.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+/**
+ * 谷币记录类型枚举
+ * 谷币:谷养正道商城专属积分,无有效期
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Getter
+@AllArgsConstructor
+public enum GuCoinLogTypeEnum {
+    TYPE_1(1, "看课获得"),
+    TYPE_2(2, "商城消费"),
+    TYPE_3(3, "订单退款返还"),
+    TYPE_4(4, "总后台录入"),
+    TYPE_5(5, "销售后台录入"),
+    ;
+
+    private Integer value;
+    private String desc;
+
+    public static GuCoinLogTypeEnum toType(int value) {
+        return Stream.of(GuCoinLogTypeEnum.values())
+                .filter(p -> p.value == value)
+                .findAny()
+                .orElse(null);
+    }
+}

+ 33 - 0
fs-service/src/main/java/com/fs/his/mapper/FsGuCoinCartMapper.java

@@ -0,0 +1,33 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsGuCoinCart;
+import com.fs.his.vo.FsGuCoinCartVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 谷币商城购物车Mapper
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+public interface FsGuCoinCartMapper extends BaseMapper<FsGuCoinCart> {
+
+    /**
+     * 新增或修改购物车(基于 user_id+goods_id 唯一键)
+     */
+    void insertOrUpdate(FsGuCoinCart cart);
+
+    /**
+     * 获取购物车商品数量
+     */
+    Integer getCartCountByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 获取购物车列表(含商品最新价格)
+     */
+    List<FsGuCoinCartVO> getCartsByMap(@Param("params") Map<String, Object> params);
+}

+ 41 - 0
fs-service/src/main/java/com/fs/his/mapper/FsGuCoinGoodsMapper.java

@@ -0,0 +1,41 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.FsGuCoinGoods;
+import com.fs.his.param.FsGuCoinGoodsParam;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
+
+import java.util.List;
+
+/**
+ * 谷币商城商品Mapper接口
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+public interface FsGuCoinGoodsMapper {
+
+    public FsGuCoinGoods selectFsGuCoinGoodsByGoodsId(Long goodsId);
+
+    public List<FsGuCoinGoods> selectFsGuCoinGoodsList(FsGuCoinGoodsParam param);
+
+    public int insertFsGuCoinGoods(FsGuCoinGoods fsGuCoinGoods);
+
+    public int updateFsGuCoinGoods(FsGuCoinGoods fsGuCoinGoods);
+
+    public int deleteFsGuCoinGoodsByGoodsId(Long goodsId);
+
+    public int deleteFsGuCoinGoodsByGoodsIds(Long[] goodsIds);
+
+    /**
+     * 减库存(带库存校验,并发安全)
+     */
+    @Update("update fs_gu_coin_goods set stock = stock - #{num} where goods_id = #{goodsId} and stock >= #{num}")
+    int subStock(@Param("goodsId") Long goodsId, @Param("num") int num);
+
+    /**
+     * 还原库存
+     */
+    @Update("update fs_gu_coin_goods set stock = stock + #{num} where goods_id = #{goodsId}")
+    int addStock(@Param("goodsId") Long goodsId, @Param("num") int num);
+}

+ 48 - 0
fs-service/src/main/java/com/fs/his/mapper/FsGuCoinOrderMapper.java

@@ -0,0 +1,48 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.FsGuCoinOrder;
+import com.fs.his.param.FsGuCoinOrderListUParam;
+import com.fs.his.param.FsGuCoinOrderParam;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+import java.util.List;
+
+/**
+ * 谷币商城订单Mapper接口
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+public interface FsGuCoinOrderMapper {
+
+    public FsGuCoinOrder selectFsGuCoinOrderByOrderId(Long orderId);
+
+    public FsGuCoinOrder selectFsGuCoinOrderByOrderCode(String orderCode);
+
+    public List<FsGuCoinOrder> selectFsGuCoinOrderList(FsGuCoinOrderParam param);
+
+    /** 用户端订单列表查询 */
+    public List<FsGuCoinOrder> selectFsGuCoinOrderListU(FsGuCoinOrderListUParam param);
+
+    public int insertFsGuCoinOrder(FsGuCoinOrder fsGuCoinOrder);
+
+    public int updateFsGuCoinOrder(FsGuCoinOrder fsGuCoinOrder);
+
+    public int deleteFsGuCoinOrderByOrderId(Long orderId);
+
+    public int deleteFsGuCoinOrderByOrderIds(Long[] orderIds);
+
+    /** 取消订单(发货前) */
+    @Update("update fs_gu_coin_order set status = -1 where order_id = #{orderId} and status in (1,4)")
+    int cancelOrder(@Param("orderId") Long orderId);
+
+    /** 查询可推送到聚水潭的谷币订单(待发货且未推送) */
+    @Select("select order_id from fs_gu_coin_order where status = 1 and extend_order_id is null")
+    List<Long> selectFsGuCoinOrderToJst();
+
+    /** 查询待同步发货状态的谷币订单(已推送聚水潭且仍待发货) */
+    @Select("select order_code, extend_order_id from fs_gu_coin_order where extend_order_id is not null and status = 1")
+    List<FsGuCoinOrder> selectJstOmsGuCoinOrderdeliveryOp();
+}

+ 31 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserGuCoinLogsMapper.java

@@ -0,0 +1,31 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.FsUserGuCoinLogs;
+import com.fs.his.param.FsUserGuCoinLogsParam;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 会员谷币记录Mapper接口
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+public interface FsUserGuCoinLogsMapper {
+
+    public FsUserGuCoinLogs selectFsUserGuCoinLogsById(Long id);
+
+    public List<FsUserGuCoinLogs> selectFsUserGuCoinLogsList(FsUserGuCoinLogsParam param);
+
+    public int insertFsUserGuCoinLogs(FsUserGuCoinLogs fsUserGuCoinLogs);
+
+    public int updateFsUserGuCoinLogs(FsUserGuCoinLogs fsUserGuCoinLogs);
+
+    /**
+     * 根据业务id查询记录(用于看课去重判断)
+     */
+    public List<FsUserGuCoinLogs> selectByUserIdAndLogTypeAndBusinessId(@Param("userId") Long userId,
+                                                                        @Param("logType") Integer logType,
+                                                                        @Param("businessId") String businessId);
+}

+ 5 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -613,6 +613,11 @@ public interface FsUserMapper
 
     int addIntegral(@Param("userId") Long userId,@Param("integral") Long integral);
 
+    /**
+     * 原子增减谷币(正数增加,负数减少)
+     */
+    int addGuCoin(@Param("userId") Long userId, @Param("guCoin") Long guCoin);
+
     int disabledUsers(@Param("param") FsUserDisabledUsersParam param);
 
     /**

+ 26 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinCartOrderCreateParam.java

@@ -0,0 +1,26 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 谷币商城购物车下单参数
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+@Data
+public class FsGuCoinCartOrderCreateParam {
+
+    /** 用户id(控制器注入) */
+    private Long userId;
+
+    @NotNull(message = "收货地址不能为空")
+    private Long addressId;
+
+    @NotEmpty(message = "请选择购物车商品")
+    private List<Long> ids;
+}

+ 32 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinCartParam.java

@@ -0,0 +1,32 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 谷币商城购物车添加/修改参数
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+@Data
+public class FsGuCoinCartParam {
+
+    @NotNull(message = "商品ID不能为空")
+    @ApiModelProperty("谷币商品ID")
+    private Long goodsId;
+
+    @NotNull(message = "添加数量不能为空")
+    @Min(value = 1, message = "数量不再合法范围内")
+    @Max(value = 999, message = "数量不再合法范围内")
+    @ApiModelProperty("数量")
+    private Integer cartNum;
+
+    @NotNull(message = "添加类型不能为空")
+    @ApiModelProperty("是否购物车 0否 1是")
+    private Integer isCart;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinGoodsParam.java

@@ -0,0 +1,17 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+/**
+ * 谷币商品查询参数
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinGoodsParam {
+    private Long goodsId;
+    private String goodsName;
+    private Long goodsType;
+    private Long status;
+}

+ 26 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderComputeParam.java

@@ -0,0 +1,26 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 谷币商城订单金额计算参数
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinOrderComputeParam {
+
+    private Long userId;
+
+    /** 谷币商品ID(单商品下单时传,与 cartIds 二选一) */
+    private Long goodsId;
+
+    /** 购物车ID集合(购物车结算时传,与 goodsId 二选一) */
+    private List<Long> cartIds;
+
+    /** 收货地址id(用于运费计算,可空) */
+    private Long userAddressId;
+}

+ 27 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderCreateParam.java

@@ -0,0 +1,27 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 谷币商城订单创建参数
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinOrderCreateParam {
+
+    /** 用户id */
+    private Long userId;
+
+    @NotNull(message = "收货地址不能为空")
+    private Long addressId;
+
+    @NotNull(message = "商品不能为空")
+    private Long goodsId;
+
+    /** 销售id(可空) */
+    private Long companyUserId;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderDoPayParam.java

@@ -0,0 +1,23 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 谷币商城订单支付参数
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinOrderDoPayParam {
+
+    private Long userId;
+
+    @NotNull(message = "订单id不能为空")
+    private Long orderId;
+
+    /** 小程序appid(微信支付用) */
+    private String appId;
+}

+ 22 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderListUParam.java

@@ -0,0 +1,22 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+/**
+ * 谷币商城订单列表查询参数(用户端)
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinOrderListUParam {
+
+    private Long userId;
+
+    /** 订单状态 1待发货 2待收货 3已完成 4待支付 5取消 */
+    private Integer status;
+
+    private Integer pageNum;
+
+    private Integer pageSize;
+}

+ 29 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderManuallyParam.java

@@ -0,0 +1,29 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 谷币商城订单手动创建参数(销售后台代下单)
+ * 销售后台为会员代下谷币商城订单,支持多商品合并一单
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinOrderManuallyParam {
+
+    /** 用户id */
+    private Long userId;
+
+    @NotNull(message = "收货地址不能为空")
+    private Long addressId;
+
+    /** 销售id(可空,默认取当前登录销售) */
+    private Long companyUserId;
+
+    @NotNull(message = "商品ID列表不能为空")
+    private List<Long> goodsIds;
+}

+ 21 - 0
fs-service/src/main/java/com/fs/his/param/FsGuCoinOrderParam.java

@@ -0,0 +1,21 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+/**
+ * 谷币订单查询参数
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsGuCoinOrderParam {
+    private Long orderId;
+    private String orderCode;
+    private Long userId;
+    private String userPhone;
+    private Integer status;
+    private Integer isPay;
+    private Long companyId;
+    private Long companyUserId;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/his/param/FsIntegralOrderCreateParam.java

@@ -4,7 +4,7 @@ import lombok.Data;
 
 
 @Data
-public class FsIntegralOrderCreateParam {
+public class    FsIntegralOrderCreateParam {
 
 
     /** 用户id */

+ 33 - 0
fs-service/src/main/java/com/fs/his/param/FsUserAddGuCoinParam.java

@@ -0,0 +1,33 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+/**
+ * 录入会员谷币参数
+ * 销售后台/总后台给会员录入谷币,填写发放备注、消费金额,留存操作日志支持溯源对账
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsUserAddGuCoinParam {
+
+    @NotNull(message = "会员ID不能为空")
+    private Long userId;
+
+    @NotNull(message = "谷币数量不能为空")
+    @Min(value = 1, message = "录入谷币最小为1")
+    private Long guCoin;
+
+    /** 消费金额(用于对账) */
+    private BigDecimal consumeAmount;
+
+    /** 发放备注 */
+    private String remark;
+
+    private Long projectId;
+}

+ 22 - 0
fs-service/src/main/java/com/fs/his/param/FsUserGuCoinLogsParam.java

@@ -0,0 +1,22 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+/**
+ * 会员谷币记录查询参数
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Data
+public class FsUserGuCoinLogsParam {
+    private Long id;
+    private Long userId;
+    private Integer logType;
+    private String businessId;
+    private Long operatorId;
+    private Integer operatorSource;
+    private Integer status;
+    private String phone;
+    private String nickName;
+}

+ 60 - 0
fs-service/src/main/java/com/fs/his/service/IFsGuCoinCartService.java

@@ -0,0 +1,60 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsGuCoinCart;
+import com.fs.his.vo.FsGuCoinCartVO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 谷币商城购物车Service接口
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+public interface IFsGuCoinCartService extends IService<FsGuCoinCart> {
+
+    /**
+     * 添加或修改购物车
+     *
+     * @param userId  用户ID
+     * @param goodsId 谷币商品ID
+     * @param cartNum 数量
+     * @param isCart  是否购物车 0否 1是(0=商品详情直接添加→追加数量,1=购物车修改数量→覆盖数量)
+     */
+    void addOrUpdateCart(Long userId, Long goodsId, Integer cartNum, Integer isCart);
+
+    /**
+     * 移除购物车
+     *
+     * @param userId 用户ID
+     * @param ids    购物车ID集合
+     */
+    void delCartByIds(Long userId, List<Long> ids);
+
+    /**
+     * 获取购物车商品数量
+     *
+     * @param params 参数
+     * @return 数量
+     */
+    Integer getCartCountByMap(Map<String, Object> params);
+
+    /**
+     * 获取购物车列表
+     *
+     * @param userId 用户ID
+     * @return 列表
+     */
+    List<FsGuCoinCartVO> getCartsByUserId(Long userId);
+
+    /**
+     * 根据购物车ID查询详情
+     *
+     * @param userId 用户ID
+     * @param ids    购物车ID集合
+     * @return 列表
+     */
+    List<FsGuCoinCartVO> getCartByIds(Long userId, List<Long> ids);
+}

+ 25 - 0
fs-service/src/main/java/com/fs/his/service/IFsGuCoinGoodsService.java

@@ -0,0 +1,25 @@
+package com.fs.his.service;
+
+import com.fs.his.domain.FsGuCoinGoods;
+import com.fs.his.param.FsGuCoinGoodsParam;
+
+import java.util.List;
+
+/**
+ * 谷币商城商品Service接口
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+public interface IFsGuCoinGoodsService {
+
+    public FsGuCoinGoods selectFsGuCoinGoodsByGoodsId(Long goodsId);
+
+    public List<FsGuCoinGoods> selectFsGuCoinGoodsList(FsGuCoinGoodsParam param);
+
+    public int insertFsGuCoinGoods(FsGuCoinGoods fsGuCoinGoods);
+
+    public int updateFsGuCoinGoods(FsGuCoinGoods fsGuCoinGoods);
+
+    public int deleteFsGuCoinGoodsByGoodsIds(Long[] goodsIds);
+}

+ 103 - 0
fs-service/src/main/java/com/fs/his/service/IFsGuCoinOrderService.java

@@ -0,0 +1,103 @@
+package com.fs.his.service;
+
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.FsGuCoinOrder;
+import com.fs.his.enums.PaymentMethodEnum;
+import com.fs.his.param.FsGuCoinCartOrderCreateParam;
+import com.fs.his.param.FsGuCoinOrderComputeParam;
+import com.fs.his.param.FsGuCoinOrderCreateParam;
+import com.fs.his.param.FsGuCoinOrderDoPayParam;
+import com.fs.his.param.FsGuCoinOrderListUParam;
+import com.fs.his.param.FsGuCoinOrderManuallyParam;
+import com.fs.his.param.FsGuCoinOrderParam;
+
+import java.text.ParseException;
+import java.util.List;
+
+/**
+ * 谷币商城订单Service接口
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+public interface IFsGuCoinOrderService {
+
+    public FsGuCoinOrder selectFsGuCoinOrderByOrderId(Long orderId);
+
+    public FsGuCoinOrder selectFsGuCoinOrderByOrderCode(String orderCode);
+
+    public List<FsGuCoinOrder> selectFsGuCoinOrderList(FsGuCoinOrderParam param);
+
+    public int insertFsGuCoinOrder(FsGuCoinOrder fsGuCoinOrder);
+
+    public int updateFsGuCoinOrder(FsGuCoinOrder fsGuCoinOrder);
+
+    public int deleteFsGuCoinOrderByOrderIds(Long[] orderIds);
+
+    /**
+     * 用户端订单列表查询
+     */
+    List<FsGuCoinOrder> selectFsGuCoinOrderListU(FsGuCoinOrderListUParam param);
+
+    /**
+     * 计算订单金额(谷币+现金+运费)
+     */
+    R computeOrder(FsGuCoinOrderComputeParam param);
+
+    /**
+     * 创建订单(扣减谷币/库存,纯谷币订单直接待发货,混合支付订单待支付)
+     */
+    R createOrder(FsGuCoinOrderCreateParam param);
+
+    /**
+     * 购物车下单(多商品合并一单)
+     * 扣减谷币/库存,纯谷币订单直接待发货,混合支付订单待支付
+     * 下单成功后清空已购买的购物车项
+     */
+    R createCartOrder(FsGuCoinCartOrderCreateParam param);
+
+    /**
+     * 销售后台手动代下单(多商品合并一单)
+     * 扣减谷币/库存,纯谷币订单直接待发货,混合支付订单待支付
+     */
+    R createOrderByManually(FsGuCoinOrderManuallyParam param);
+
+    /**
+     * 取消订单(还原库存/谷币)
+     */
+    R cannelOrder(Long orderId);
+
+    /**
+     * 预支付(现金/混合支付订单发起支付)
+     */
+    R payment(FsGuCoinOrderDoPayParam param, PaymentMethodEnum paymentMethod);
+
+    /**
+     * 支付回调(支付成功后更新订单状态)
+     */
+    R payConfirm(String orderSn, String payCode, String tradeNo, String payType, int type, String bankTransactionId, String bankSerialNo);
+
+    /**
+     * 取消订单(按订单编号,用于发货前取消/退款返还谷币)
+     */
+    int cancelOrder(String orderCode);
+
+    /**
+     * 完成订单
+     */
+    int finishOrder(String orderCode);
+
+    /**
+     * 推送聚水潭(构建ErpOrder并批量推送,回写extendOrderId)
+     */
+    void createOmsGuCoinOrder(List<Long> orderIds) throws ParseException, InterruptedException;
+
+    /**
+     * 同步聚水潭发货状态后回写订单(含快递鸟订阅)
+     * @param orderCode    本地订单编号
+     * @param deliveryId   快递单号(mail_no)
+     * @param deliverCode  聚水潭快递公司编号(express_code)
+     * @param deliverName  快递公司名称(express_name)
+     */
+    void deliveryGuCoinOrderOrder(String orderCode, String deliveryId, String deliverCode, String deliverName);
+}

+ 74 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserGuCoinLogsService.java

@@ -0,0 +1,74 @@
+package com.fs.his.service;
+
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.FsUserGuCoinLogs;
+import com.fs.his.param.FsUserAddGuCoinParam;
+import com.fs.his.param.FsUserGuCoinLogsParam;
+
+import java.util.List;
+
+/**
+ * 会员谷币记录Service接口
+ * 谷币:谷养正道商城专属积分,无有效期
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+public interface IFsUserGuCoinLogsService {
+
+    public FsUserGuCoinLogs selectFsUserGuCoinLogsById(Long id);
+
+    public List<FsUserGuCoinLogs> selectFsUserGuCoinLogsList(FsUserGuCoinLogsParam param);
+
+    public int insertFsUserGuCoinLogs(FsUserGuCoinLogs fsUserGuCoinLogs);
+
+    /**
+     * 看课获取谷币
+     * 积分逻辑:完整观看1节全部课程内容、完成对应课后答题、所有答题全部正确,
+     * 每节课程可获得150谷币/节;重复观看课程只获得一次积分。
+     * 未完整看课、未答题、答题有误,均不发放本节学习积分。
+     *
+     * @param userId   会员id
+     * @param videoId  课程小节id
+     * @param courseId 课程id
+     * @return 发放结果
+     */
+    public R rewardCourseWatchGuCoin(Long userId, Long videoId, Long courseId);
+
+    /**
+     * 录入会员谷币(总后台/销售后台通用)
+     * 填写发放备注、消费金额,留存操作日志,支持溯源对账
+     *
+     * @param param          录入参数
+     * @param logType        记录类型 4总后台录入 5销售后台录入
+     * @param operatorId     操作人id
+     * @param operatorName   操作人姓名
+     * @param operatorSource 操作来源 1总后台 2销售后台
+     * @return 录入结果
+     */
+    public R addGuCoin(FsUserAddGuCoinParam param, Integer logType, Long operatorId, String operatorName, Integer operatorSource);
+
+    /**
+     * 商城消费扣减谷币(下单时调用)
+     * 原子扣减余额,写入消费日志(logType=2 商城消费),支持溯源对账
+     *
+     * @param userId     会员id
+     * @param guCoinNum  扣减谷币数(正数)
+     * @param businessId 关联业务id(订单id)
+     * @param remark     备注
+     * @return 扣减结果,失败返回错误信息
+     */
+    public R consumeGuCoin(Long userId, Long guCoinNum, String businessId, String remark);
+
+    /**
+     * 订单退款/取消返还谷币
+     * 原子增加余额,写入返还日志(logType=3 订单退款返还),支持溯源对账
+     *
+     * @param userId     会员id
+     * @param guCoinNum  返还谷币数(正数)
+     * @param businessId 关联业务id(订单id)
+     * @param remark     备注
+     * @return 返还结果
+     */
+    public R refundGuCoin(Long userId, Long guCoinNum, String businessId, String remark);
+}

+ 119 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsGuCoinCartServiceImpl.java

@@ -0,0 +1,119 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.exception.CustomException;
+import com.fs.his.domain.FsGuCoinCart;
+import com.fs.his.domain.FsGuCoinGoods;
+import com.fs.his.domain.FsUser;
+import com.fs.his.mapper.FsGuCoinCartMapper;
+import com.fs.his.mapper.FsGuCoinGoodsMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.service.IFsGuCoinCartService;
+import com.fs.his.vo.FsGuCoinCartVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 谷币商城购物车Service实现
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+@Slf4j
+@Service
+public class FsGuCoinCartServiceImpl extends ServiceImpl<FsGuCoinCartMapper, FsGuCoinCart> implements IFsGuCoinCartService {
+
+    @Resource
+    private FsGuCoinGoodsMapper goodsMapper;
+    @Resource
+    private FsUserMapper userMapper;
+
+    /**
+     * 添加或修改购物车
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void addOrUpdateCart(Long userId, Long goodsId, Integer cartNum, Integer isCart) {
+        log.debug("addOrUpdateCart userId: {}, goodsId: {}, cartNum: {}, isCart: {}", userId, goodsId, cartNum, isCart);
+        FsUser fsUser = userMapper.selectFsUserByUserId(userId);
+        if (Objects.isNull(fsUser)) {
+            log.warn("addOrUpdateCart user not find userId: {}", userId);
+            throw new CustomException("用户不存在");
+        }
+
+        FsGuCoinGoods goods = goodsMapper.selectFsGuCoinGoodsByGoodsId(goodsId);
+        if (Objects.isNull(goods)) {
+            log.warn("addOrUpdateCart goods not find goodsId: {}", goodsId);
+            throw new CustomException("商品不存在");
+        }
+        if (goods.getStatus() == null || goods.getStatus() != 1) {
+            throw new CustomException("商品已下架");
+        }
+        if (goods.getStock() == null || goods.getStock() < cartNum) {
+            throw new CustomException("库存不足");
+        }
+
+        FsGuCoinCart cart = new FsGuCoinCart();
+        cart.setUserId(userId);
+        cart.setGoodsId(goodsId);
+        cart.setCartNum(cartNum);
+        cart.setGuCoin(goods.getGuCoin());
+        cart.setCash(goods.getCash());
+        cart.setCreateTime(LocalDateTime.now());
+        cart.setUpdateTime(LocalDateTime.now());
+        // 与积分商城保持一致:isCart=0(非购物车场景,商品详情直接添加)→ 追加数量;isCart=1(购物车场景,修改数量)→ 覆盖数量
+        cart.setAddToExist(isCart == 0);
+        baseMapper.insertOrUpdate(cart);
+    }
+
+    /**
+     * 移除购物车
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void delCartByIds(Long userId, List<Long> ids) {
+        Wrapper<FsGuCoinCart> wrapper = Wrappers.<FsGuCoinCart>lambdaQuery()
+                .eq(FsGuCoinCart::getUserId, userId)
+                .in(FsGuCoinCart::getId, ids);
+        baseMapper.delete(wrapper);
+    }
+
+    /**
+     * 获取购物车商品数量
+     */
+    @Override
+    public Integer getCartCountByMap(Map<String, Object> params) {
+        return baseMapper.getCartCountByMap(params);
+    }
+
+    /**
+     * 获取购物车列表
+     */
+    @Override
+    public List<FsGuCoinCartVO> getCartsByUserId(Long userId) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("userId", userId);
+        return baseMapper.getCartsByMap(params);
+    }
+
+    /**
+     * 根据购物车ID查询详情
+     */
+    @Override
+    public List<FsGuCoinCartVO> getCartByIds(Long userId, List<Long> ids) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("userId", userId);
+        params.put("ids", ids);
+        return baseMapper.getCartsByMap(params);
+    }
+}

+ 51 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsGuCoinGoodsServiceImpl.java

@@ -0,0 +1,51 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsGuCoinGoods;
+import com.fs.his.mapper.FsGuCoinGoodsMapper;
+import com.fs.his.param.FsGuCoinGoodsParam;
+import com.fs.his.service.IFsGuCoinGoodsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 谷币商城商品Service业务层
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Service
+public class FsGuCoinGoodsServiceImpl implements IFsGuCoinGoodsService
+{
+    @Autowired
+    private FsGuCoinGoodsMapper fsGuCoinGoodsMapper;
+
+    @Override
+    public FsGuCoinGoods selectFsGuCoinGoodsByGoodsId(Long goodsId) {
+        return fsGuCoinGoodsMapper.selectFsGuCoinGoodsByGoodsId(goodsId);
+    }
+
+    @Override
+    public List<FsGuCoinGoods> selectFsGuCoinGoodsList(FsGuCoinGoodsParam param) {
+        return fsGuCoinGoodsMapper.selectFsGuCoinGoodsList(param);
+    }
+
+    @Override
+    public int insertFsGuCoinGoods(FsGuCoinGoods fsGuCoinGoods) {
+        fsGuCoinGoods.setCreateTime(DateUtils.getNowDate());
+        return fsGuCoinGoodsMapper.insertFsGuCoinGoods(fsGuCoinGoods);
+    }
+
+    @Override
+    public int updateFsGuCoinGoods(FsGuCoinGoods fsGuCoinGoods) {
+        fsGuCoinGoods.setUpdateTime(DateUtils.getNowDate());
+        return fsGuCoinGoodsMapper.updateFsGuCoinGoods(fsGuCoinGoods);
+    }
+
+    @Override
+    public int deleteFsGuCoinGoodsByGoodsIds(Long[] goodsIds) {
+        return fsGuCoinGoodsMapper.deleteFsGuCoinGoodsByGoodsIds(goodsIds);
+    }
+}

+ 958 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsGuCoinOrderServiceImpl.java

@@ -0,0 +1,958 @@
+package com.fs.his.service.impl;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONUtil;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.core.utils.OrderCodeUtils;
+import com.fs.erp.domain.ErpOrder;
+import com.fs.erp.domain.ErpOrderPayment;
+import com.fs.erp.service.IErpOrderService;
+import com.fs.his.config.FsSysConfig;
+import com.fs.his.domain.FsExpress;
+import com.fs.his.domain.FsGuCoinGoods;
+import com.fs.his.domain.FsGuCoinOrder;
+import com.fs.his.domain.FsStorePayment;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserAddress;
+import com.fs.his.dto.ComputeGuCoinOrderMoneyDTO;
+import com.fs.his.dto.ErpRemarkDTO;
+import com.fs.his.enums.BusinessTypeEnum;
+import com.fs.his.enums.PaymentMethodEnum;
+import com.fs.his.mapper.FsGuCoinGoodsMapper;
+import com.fs.his.mapper.FsGuCoinOrderMapper;
+import com.fs.his.mapper.FsUserAddressMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.param.FsGuCoinCartOrderCreateParam;
+import com.fs.his.param.FsGuCoinOrderComputeParam;
+import com.fs.his.param.FsGuCoinOrderCreateParam;
+import com.fs.his.param.FsGuCoinOrderDoPayParam;
+import com.fs.his.param.FsGuCoinOrderListUParam;
+import com.fs.his.param.FsGuCoinOrderManuallyParam;
+import com.fs.his.param.FsGuCoinOrderParam;
+import com.fs.his.param.PayOrderParam;
+import com.fs.his.service.IFsExpressService;
+import com.fs.his.service.IFsGuCoinCartService;
+import com.fs.his.service.IFsGuCoinOrderService;
+import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.service.IFsUserGuCoinLogsService;
+import com.fs.his.utils.ConfigUtil;
+import com.fs.his.vo.FsGuCoinCartVO;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwUserMapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+import static com.fs.his.utils.PhoneUtil.decryptPhone;
+
+/**
+ * 谷币商城订单Service业务层
+ * 谷币:谷养正道商城专属积分,无有效期
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Service
+public class FsGuCoinOrderServiceImpl implements IFsGuCoinOrderService
+{
+    private static final Logger log = LoggerFactory.getLogger(FsGuCoinOrderServiceImpl.class);
+
+    @Autowired
+    private FsGuCoinOrderMapper fsGuCoinOrderMapper;
+    @Autowired
+    private FsGuCoinGoodsMapper fsGuCoinGoodsMapper;
+    @Autowired
+    private FsUserMapper fsUserMapper;
+    @Autowired
+    private FsUserAddressMapper fsUserAddressMapper;
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+    @Autowired
+    private QwUserMapper qwUserMapper;
+    @Autowired
+    private IFsUserGuCoinLogsService fsUserGuCoinLogsService;
+    @Autowired
+    private IFsStorePaymentService storePaymentService;
+    @Autowired
+    private IFsGuCoinCartService fsGuCoinCartService;
+
+    /** 聚水潭固定店铺编号(与积分订单一致,从配置注入) */
+    @Value("${jst.shop_code:''}")
+    private String shopCode;
+
+    @Autowired
+    private ConfigUtil configUtil;
+
+    /** 谷币订单仅推送聚水潭,直接注入JST实现 */
+    @Autowired
+    @Qualifier("JSTErpOrderServiceImpl")
+    private IErpOrderService jstErpOrderService;
+
+    @Autowired
+    private IFsExpressService expressService;
+
+    @Override
+    public FsGuCoinOrder selectFsGuCoinOrderByOrderId(Long orderId) {
+        return fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderId(orderId);
+    }
+
+    @Override
+    public FsGuCoinOrder selectFsGuCoinOrderByOrderCode(String orderCode) {
+        return fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderCode(orderCode);
+    }
+
+    @Override
+    public List<FsGuCoinOrder> selectFsGuCoinOrderList(FsGuCoinOrderParam param) {
+        return fsGuCoinOrderMapper.selectFsGuCoinOrderList(param);
+    }
+
+    @Override
+    public List<FsGuCoinOrder> selectFsGuCoinOrderListU(FsGuCoinOrderListUParam param) {
+        return fsGuCoinOrderMapper.selectFsGuCoinOrderListU(param);
+    }
+
+    @Override
+    public int insertFsGuCoinOrder(FsGuCoinOrder fsGuCoinOrder) {
+        return fsGuCoinOrderMapper.insertFsGuCoinOrder(fsGuCoinOrder);
+    }
+
+    @Override
+    public int updateFsGuCoinOrder(FsGuCoinOrder fsGuCoinOrder) {
+        return fsGuCoinOrderMapper.updateFsGuCoinOrder(fsGuCoinOrder);
+    }
+
+    @Override
+    public int deleteFsGuCoinOrderByOrderIds(Long[] orderIds) {
+        return fsGuCoinOrderMapper.deleteFsGuCoinOrderByOrderIds(orderIds);
+    }
+
+    /**
+     * 谷币订单支付金额计算
+     * 与积分商城 computeOrder 逻辑对称:判断 goodsId/cartIds 二选一
+     * 取消优惠金额逻辑(不实现 applyCoupon),discountGuCoin/discountCash 恒为 0
+     */
+    @Override
+    public R computeOrder(FsGuCoinOrderComputeParam param) {
+        // 查询用户,判断用户是否存在
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        if (fsUser == null || fsUser.getIsDel() == 1) {
+            return R.error("用户不存在");
+        }
+
+        if (param.getGoodsId() == null && (param.getCartIds() == null || param.getCartIds().isEmpty())) {
+            return R.error("商品不能为空");
+        }
+
+        ComputeGuCoinOrderMoneyDTO moneyDTO = new ComputeGuCoinOrderMoneyDTO();
+        List<FsGuCoinGoods> goodsItem = new ArrayList<>();
+
+        // 判断是购物车还是单商品
+        if (param.getGoodsId() != null) {
+            FsGuCoinGoods goods = fsGuCoinGoodsMapper.selectFsGuCoinGoodsByGoodsId(param.getGoodsId());
+            if (goods == null) {
+                return R.error("谷币商品不存在");
+            }
+            if (goods.getStatus() == null || goods.getStatus() != 1) {
+                return R.error("谷币商品已下架");
+            }
+            goods.setNum(1L);
+            goodsItem.add(goods);
+
+            long goodsGuCoin = goods.getGuCoin() == null ? 0L : goods.getGuCoin();
+            BigDecimal goodsCash = goods.getCash() == null ? BigDecimal.ZERO : goods.getCash();
+            moneyDTO.setTotalGuCoin(goodsGuCoin);
+            moneyDTO.setGuCoin(goodsGuCoin);
+            moneyDTO.setTotalCash(goodsCash);
+            moneyDTO.setCash(goodsCash);
+            // 谷币商城暂不实现优惠金额逻辑,discountGuCoin/discountCash 保持默认 0
+        } else {
+            // 购物车结算
+            List<FsGuCoinCartVO> carts = fsGuCoinCartService.getCartByIds(fsUser.getUserId(), param.getCartIds());
+            if (carts.isEmpty()) {
+                return R.error("购物车商品不存在");
+            }
+            for (FsGuCoinCartVO cart : carts) {
+                FsGuCoinGoods goods = fsGuCoinGoodsMapper.selectFsGuCoinGoodsByGoodsId(cart.getGoodsId());
+                if (Objects.isNull(goods)) {
+                    throw new CustomException("商品不存在");
+                }
+                int num = cart.getCartNum() == null ? 1 : cart.getCartNum();
+                long goodsGuCoin = goods.getGuCoin() == null ? 0L : goods.getGuCoin();
+                BigDecimal goodsCash = goods.getCash() == null ? BigDecimal.ZERO : goods.getCash();
+
+                moneyDTO.setTotalGuCoin(moneyDTO.getTotalGuCoin() + goodsGuCoin * num);
+                moneyDTO.setTotalCash(moneyDTO.getTotalCash().add(goodsCash.multiply(BigDecimal.valueOf(num))));
+
+                goods.setNum((long) num);
+                goodsItem.add(goods);
+            }
+            moneyDTO.setGuCoin(moneyDTO.getTotalGuCoin());
+            moneyDTO.setCash(moneyDTO.getTotalCash());
+        }
+
+        // 运费计算(谷币商城暂不实现运费模板,deliveryMoney 保持默认 0;如传了地址仅做存在性校验)
+        if (param.getUserAddressId() != null) {
+            FsUserAddress fsUserAddress = fsUserAddressMapper.selectFsUserAddressByAddressId(param.getUserAddressId());
+            if (fsUserAddress == null || fsUserAddress.getIsDel() == 1
+                    || !fsUser.getUserId().equals(fsUserAddress.getUserId())) {
+                return R.error("收货地址不存在");
+            }
+        }
+
+        // 返回扣减后的支付金额
+        return R.ok().put("data", moneyDTO);
+    }
+
+    /**
+     * 创建订单
+     * 纯谷币商品(cash=0):扣减谷币,订单直接待发货(status=1)
+     * 混合支付商品(cash>0):扣减谷币,订单待支付(status=4),需支付现金后回调改状态
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R createOrder(FsGuCoinOrderCreateParam param) {
+        FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+        FsUserAddress address = fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
+        if (address == null) {
+            return R.error("收货地址不存在");
+        }
+        FsGuCoinGoods goods = fsGuCoinGoodsMapper.selectFsGuCoinGoodsByGoodsId(param.getGoodsId());
+        if (goods == null) {
+            return R.error("商品不存在");
+        }
+        if (goods.getStatus() == null || goods.getStatus() != 1) {
+            return R.error("商品已下架");
+        }
+        if (goods.getStock() == null || goods.getStock() <= 0) {
+            return R.error("库存不足");
+        }
+
+        long goodsGuCoin = goods.getGuCoin() == null ? 0L : goods.getGuCoin();
+        BigDecimal goodsCash = goods.getCash() == null ? BigDecimal.ZERO : goods.getCash();
+        long userGuCoin = user.getGuCoin() == null ? 0L : user.getGuCoin();
+
+        // 谷币余额校验
+        if (goodsGuCoin > 0 && userGuCoin < goodsGuCoin) {
+            return R.error("谷币余额不足");
+        }
+
+        // 减库存(并发安全,带库存校验)
+        if (fsGuCoinGoodsMapper.subStock(goods.getGoodsId(), 1) <= 0) {
+            throw new CustomException("库存不足");
+        }
+
+        // 生成订单编号
+        String orderCode;
+        try {
+            orderCode = OrderCodeUtils.getOrderSn();
+        } catch (Exception e) {
+            orderCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+        }
+        if (StringUtils.isEmpty(orderCode)) {
+            // 减库存回滚由事务处理
+            throw new CustomException("订单生成失败,请重试");
+        }
+
+        // 构建商品快照(num=1)
+        goods.setNum(1L);
+        List<FsGuCoinGoods> goodsItem = new ArrayList<>();
+        goodsItem.add(goods);
+
+        FsGuCoinOrder order = new FsGuCoinOrder();
+        order.setOrderCode(orderCode);
+        order.setUserId(user.getUserId());
+        order.setUserName(address.getRealName());
+        order.setUserPhone(address.getPhone());
+        order.setUserAddress(org.apache.commons.lang3.StringUtils.defaultString(address.getProvince())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getCity())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getDistrict())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getDetail()));
+        order.setItemJson(JSONUtil.toJsonStr(goodsItem));
+        order.setTotalGuCoin(goodsGuCoin);
+        order.setDiscountGuCoin(0L);
+        order.setGuCoin(goodsGuCoin);
+        order.setTotalMoney(goodsCash);
+        order.setDiscountMoney(BigDecimal.ZERO);
+        order.setPayMoney(goodsCash);
+        order.setDeliveryMoney(BigDecimal.ZERO);
+        order.setBarCode(goods.getBarCode());
+        order.setCreateTime(new Date());
+
+        // 判断支付类型
+        if (goodsCash.compareTo(BigDecimal.ZERO) > 0) {
+            // 现金+谷币
+            order.setPayType(goodsGuCoin > 0 ? 3 : 2);
+            order.setStatus(4); // 待支付
+            order.setIsPay(0);
+        } else {
+            // 纯谷币
+            order.setPayType(1);
+            order.setStatus(1); // 待发货
+            order.setIsPay(1);
+            order.setPayTime(new Date());
+        }
+
+        // 关联销售信息
+        Long companyUserId = param.getCompanyUserId();
+        if (companyUserId != null) {
+            order.setCompanyUserId(companyUserId);
+            CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(companyUserId);
+            if (ObjectUtil.isNotEmpty(companyUser)) {
+                order.setCompanyId(companyUser.getCompanyId());
+            }
+        }
+        // 关联企微信息
+        if (ObjectUtil.isNotNull(user.getQwUserId())) {
+            QwUser qwUser = qwUserMapper.selectOne(new LambdaQueryWrapper<QwUser>()
+                    .select(QwUser::getId, QwUser::getCompanyId, QwUser::getCompanyUserId)
+                    .eq(QwUser::getId, user.getQwUserId()));
+            if (ObjectUtil.isNotNull(qwUser)) {
+                order.setQwUserId(qwUser.getId());
+                if (order.getCompanyId() == null && qwUser.getCompanyId() != null) {
+                    order.setCompanyId(qwUser.getCompanyId());
+                }
+                if (order.getCompanyUserId() == null && qwUser.getCompanyUserId() != null) {
+                    order.setCompanyUserId(qwUser.getCompanyUserId());
+                }
+            }
+        }
+
+        // 插入订单
+        if (fsGuCoinOrderMapper.insertFsGuCoinOrder(order) <= 0) {
+            throw new CustomException("订单创建失败");
+        }
+
+        // 扣减谷币(logType=2 商城消费,businessId=订单id)
+        if (goodsGuCoin > 0) {
+            R consumeRes = fsUserGuCoinLogsService.consumeGuCoin(user.getUserId(), goodsGuCoin,
+                    order.getOrderId().toString(), "谷币商城下单消费:" + orderCode);
+            if (consumeRes == null || !"200".equals(String.valueOf(consumeRes.get("code")))) {
+                // 谷币扣减失败,回滚事务(库存也会回滚)
+                String msg = consumeRes == null ? "谷币扣减失败" : String.valueOf(consumeRes.get("msg"));
+                throw new CustomException(msg);
+            }
+        }
+
+        log.info("谷币商城订单创建成功 orderCode={}, userId={}, guCoin={}, cash={}", orderCode, user.getUserId(), goodsGuCoin, goodsCash);
+        // 返回结构与积分商城 createOrder 对齐:返回整个 order + isPay
+        return R.ok("下单成功").put("order", order).put("isPay", order.getIsPay());
+    }
+
+    /**
+     * 购物车下单(多商品合并一单)
+     * 纯谷币订单直接待发货(status=1),混合支付订单待支付(status=4)
+     * 下单成功后清空已购买的购物车项
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R createCartOrder(FsGuCoinCartOrderCreateParam param) {
+        FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+        FsUserAddress address = fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
+        if (address == null) {
+            return R.error("收货地址不存在");
+        }
+        if (param.getIds() == null || param.getIds().isEmpty()) {
+            return R.error("请选择购物车商品");
+        }
+
+        // 加载购物车项(按用户ID隔离)
+        List<FsGuCoinCartVO> carts = fsGuCoinCartService.getCartByIds(param.getUserId(), param.getIds());
+        if (carts.isEmpty()) {
+            return R.error("购物车商品不存在");
+        }
+
+        // 加载并校验所有商品,聚合金额
+        List<FsGuCoinGoods> goodsList = new ArrayList<>();
+        List<Long> cartIds = new ArrayList<>();
+        long totalGuCoin = 0L;
+        BigDecimal totalCash = BigDecimal.ZERO;
+        for (FsGuCoinCartVO cart : carts) {
+            FsGuCoinGoods goods = fsGuCoinGoodsMapper.selectFsGuCoinGoodsByGoodsId(cart.getGoodsId());
+            if (goods == null) {
+                return R.error("商品不存在: " + cart.getGoodsId());
+            }
+            if (goods.getStatus() == null || goods.getStatus() != 1) {
+                return R.error("商品已下架: " + goods.getGoodsName());
+            }
+            int num = cart.getCartNum() == null ? 1 : cart.getCartNum();
+            if (goods.getStock() == null || goods.getStock() < num) {
+                return R.error("商品库存不足: " + goods.getGoodsName());
+            }
+            // 减库存(并发安全,带库存校验)
+            if (fsGuCoinGoodsMapper.subStock(goods.getGoodsId(), num) <= 0) {
+                throw new CustomException("商品库存不足: " + goods.getGoodsName());
+            }
+
+            long goodsGuCoin = goods.getGuCoin() == null ? 0L : goods.getGuCoin();
+            BigDecimal goodsCash = goods.getCash() == null ? BigDecimal.ZERO : goods.getCash();
+            totalGuCoin += goodsGuCoin * num;
+            totalCash = totalCash.add(goodsCash.multiply(BigDecimal.valueOf(num)));
+
+            goods.setNum((long) num);
+            goodsList.add(goods);
+            cartIds.add(cart.getId());
+        }
+
+        long userGuCoin = user.getGuCoin() == null ? 0L : user.getGuCoin();
+        // 谷币余额校验
+        if (totalGuCoin > 0 && userGuCoin < totalGuCoin) {
+            return R.error("谷币余额不足");
+        }
+
+        // 生成订单编号
+        String orderCode;
+        try {
+            orderCode = OrderCodeUtils.getOrderSn();
+        } catch (Exception e) {
+            orderCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+        }
+        if (StringUtils.isEmpty(orderCode)) {
+            throw new CustomException("订单生成失败,请重试");
+        }
+
+        FsGuCoinOrder order = new FsGuCoinOrder();
+        order.setOrderCode(orderCode);
+        order.setUserId(user.getUserId());
+        order.setUserName(address.getRealName());
+        order.setUserPhone(address.getPhone());
+        order.setUserAddress(org.apache.commons.lang3.StringUtils.defaultString(address.getProvince())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getCity())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getDistrict())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getDetail()));
+        order.setItemJson(JSONUtil.toJsonStr(goodsList));
+        order.setTotalGuCoin(totalGuCoin);
+        order.setDiscountGuCoin(0L);
+        order.setGuCoin(totalGuCoin);
+        order.setTotalMoney(totalCash);
+        order.setDiscountMoney(BigDecimal.ZERO);
+        order.setPayMoney(totalCash);
+        order.setDeliveryMoney(BigDecimal.ZERO);
+        if (!goodsList.isEmpty()) {
+            order.setBarCode(goodsList.get(0).getBarCode());
+        }
+        order.setCreateTime(new Date());
+
+        // 用户自助下单:纯谷币=待发货,混合支付=待支付(与 createOrder 一致)
+        if (totalCash.compareTo(BigDecimal.ZERO) > 0) {
+            order.setPayType(totalGuCoin > 0 ? 3 : 2);
+            order.setStatus(4); // 待支付
+            order.setIsPay(0);
+        } else {
+            order.setPayType(1);
+            order.setStatus(1); // 待发货
+            order.setIsPay(1);
+            order.setPayTime(new Date());
+        }
+
+        // 关联企微信息(购物车下单无销售关联)
+        if (ObjectUtil.isNotNull(user.getQwUserId())) {
+            QwUser qwUser = qwUserMapper.selectOne(new LambdaQueryWrapper<QwUser>()
+                    .select(QwUser::getId, QwUser::getCompanyId, QwUser::getCompanyUserId)
+                    .eq(QwUser::getId, user.getQwUserId()));
+            if (ObjectUtil.isNotNull(qwUser)) {
+                order.setQwUserId(qwUser.getId());
+                if (qwUser.getCompanyId() != null) {
+                    order.setCompanyId(qwUser.getCompanyId());
+                }
+                if (qwUser.getCompanyUserId() != null) {
+                    order.setCompanyUserId(qwUser.getCompanyUserId());
+                }
+            }
+        }
+
+        // 插入订单
+        if (fsGuCoinOrderMapper.insertFsGuCoinOrder(order) <= 0) {
+            throw new CustomException("订单创建失败");
+        }
+
+        // 扣减谷币(logType=2 商城消费,businessId=订单id)
+        if (totalGuCoin > 0) {
+            R consumeRes = fsUserGuCoinLogsService.consumeGuCoin(user.getUserId(), totalGuCoin,
+                    order.getOrderId().toString(), "谷币商城购物车下单消费:" + orderCode);
+            if (consumeRes == null || !"200".equals(String.valueOf(consumeRes.get("code")))) {
+                String msg = consumeRes == null ? "谷币扣减失败" : String.valueOf(consumeRes.get("msg"));
+                throw new CustomException(msg);
+            }
+        }
+
+        // 清空已购买的购物车项
+        fsGuCoinCartService.delCartByIds(param.getUserId(), cartIds);
+
+        log.info("谷币商城购物车下单成功 orderCode={}, userId={}, guCoin={}, cash={}", orderCode, user.getUserId(), totalGuCoin, totalCash);
+        // 返回结构与积分商城 createOrder 对齐:返回整个 order + isPay
+        return R.ok("下单成功").put("order", order).put("isPay", order.getIsPay());
+    }
+
+    /**
+     * 销售后台手动代下单(多商品合并一单)
+     * 代下单统一为待发货(status=1),销售线下收款,扣减会员谷币
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R createOrderByManually(FsGuCoinOrderManuallyParam param) {
+        FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+        FsUserAddress address = fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
+        if (address == null) {
+            return R.error("收货地址不存在");
+        }
+        if (param.getGoodsIds() == null || param.getGoodsIds().isEmpty()) {
+            return R.error("请选择至少一个商品");
+        }
+
+        // 加载并校验所有商品
+        List<FsGuCoinGoods> goodsList = new ArrayList<>();
+        long totalGuCoin = 0L;
+        BigDecimal totalCash = BigDecimal.ZERO;
+        for (Long goodsId : param.getGoodsIds()) {
+            FsGuCoinGoods goods = fsGuCoinGoodsMapper.selectFsGuCoinGoodsByGoodsId(goodsId);
+            if (goods == null) {
+                return R.error("商品不存在: " + goodsId);
+            }
+            if (goods.getStatus() == null || goods.getStatus() != 1) {
+                return R.error("商品已下架: " + goods.getGoodsName());
+            }
+            if (goods.getStock() == null || goods.getStock() <= 0) {
+                return R.error("商品库存不足: " + goods.getGoodsName());
+            }
+            goods.setNum(1L);
+            goodsList.add(goods);
+            totalGuCoin += goods.getGuCoin() == null ? 0L : goods.getGuCoin();
+            totalCash = totalCash.add(goods.getCash() == null ? BigDecimal.ZERO : goods.getCash());
+        }
+
+        long userGuCoin = user.getGuCoin() == null ? 0L : user.getGuCoin();
+        // 谷币余额校验
+        if (totalGuCoin > 0 && userGuCoin < totalGuCoin) {
+            return R.error("谷币余额不足");
+        }
+
+        // 减库存(并发安全,带库存校验)
+        for (FsGuCoinGoods goods : goodsList) {
+            if (fsGuCoinGoodsMapper.subStock(goods.getGoodsId(), 1) <= 0) {
+                throw new CustomException("商品库存不足: " + goods.getGoodsName());
+            }
+        }
+
+        // 生成订单编号
+        String orderCode;
+        try {
+            orderCode = OrderCodeUtils.getOrderSn();
+        } catch (Exception e) {
+            orderCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+        }
+        if (StringUtils.isEmpty(orderCode)) {
+            throw new CustomException("订单生成失败,请重试");
+        }
+
+        FsGuCoinOrder order = new FsGuCoinOrder();
+        order.setOrderCode(orderCode);
+        order.setUserId(user.getUserId());
+        order.setUserName(address.getRealName());
+        order.setUserPhone(address.getPhone());
+        order.setUserAddress(org.apache.commons.lang3.StringUtils.defaultString(address.getProvince())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getCity())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getDistrict())
+                + org.apache.commons.lang3.StringUtils.defaultString(address.getDetail()));
+        order.setItemJson(JSONUtil.toJsonStr(goodsList));
+        order.setTotalGuCoin(totalGuCoin);
+        order.setDiscountGuCoin(0L);
+        order.setGuCoin(totalGuCoin);
+        order.setTotalMoney(totalCash);
+        order.setDiscountMoney(BigDecimal.ZERO);
+        order.setPayMoney(totalCash);
+        order.setDeliveryMoney(BigDecimal.ZERO);
+        if (!goodsList.isEmpty()) {
+            order.setBarCode(goodsList.get(0).getBarCode());
+        }
+        order.setCreateTime(new Date());
+
+        // 代下单统一为待发货(销售线下收款,无需在线支付)
+        // 支付类型仅作展示记录:纯谷币=1,纯现金=2,谷币+现金=3
+        if (totalCash.compareTo(BigDecimal.ZERO) > 0) {
+            order.setPayType(totalGuCoin > 0 ? 3 : 2);
+        } else {
+            order.setPayType(1);
+        }
+        order.setStatus(1); // 待发货
+        order.setIsPay(1);
+        order.setPayTime(new Date());
+
+        // 关联销售信息
+        Long companyUserId = param.getCompanyUserId();
+        if (companyUserId != null) {
+            order.setCompanyUserId(companyUserId);
+            CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(companyUserId);
+            if (ObjectUtil.isNotEmpty(companyUser)) {
+                order.setCompanyId(companyUser.getCompanyId());
+            }
+        }
+        // 关联企微信息
+        if (ObjectUtil.isNotNull(user.getQwUserId())) {
+            QwUser qwUser = qwUserMapper.selectOne(new LambdaQueryWrapper<QwUser>()
+                    .select(QwUser::getId, QwUser::getCompanyId, QwUser::getCompanyUserId)
+                    .eq(QwUser::getId, user.getQwUserId()));
+            if (ObjectUtil.isNotNull(qwUser)) {
+                order.setQwUserId(qwUser.getId());
+                if (order.getCompanyId() == null && qwUser.getCompanyId() != null) {
+                    order.setCompanyId(qwUser.getCompanyId());
+                }
+                if (order.getCompanyUserId() == null && qwUser.getCompanyUserId() != null) {
+                    order.setCompanyUserId(qwUser.getCompanyUserId());
+                }
+            }
+        }
+
+        // 插入订单
+        if (fsGuCoinOrderMapper.insertFsGuCoinOrder(order) <= 0) {
+            throw new CustomException("订单创建失败");
+        }
+
+        // 扣减谷币(logType=2 商城消费,businessId=订单id)
+        if (totalGuCoin > 0) {
+            R consumeRes = fsUserGuCoinLogsService.consumeGuCoin(user.getUserId(), totalGuCoin,
+                    order.getOrderId().toString(), "谷币商城代下单消费:" + orderCode);
+            if (consumeRes == null || !"200".equals(String.valueOf(consumeRes.get("code")))) {
+                String msg = consumeRes == null ? "谷币扣减失败" : String.valueOf(consumeRes.get("msg"));
+                throw new CustomException(msg);
+            }
+        }
+
+        log.info("谷币商城代下单创建成功 orderCode={}, userId={}, guCoin={}, cash={}", orderCode, user.getUserId(), totalGuCoin, totalCash);
+        return R.ok("下单成功").put("orderId", order.getOrderId()).put("orderCode", orderCode)
+                .put("status", order.getStatus()).put("payType", order.getPayType());
+    }
+
+    /**
+     * 取消订单(还原库存+还原谷币)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R cannelOrder(Long orderId) {
+        FsGuCoinOrder order = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderId(orderId);
+        if (order == null) {
+            return R.error("订单不存在");
+        }
+        // 待支付(4)或待发货(1)可取消
+        if (!Objects.equals(order.getStatus(), 4) && !Objects.equals(order.getStatus(), 1)) {
+            return R.error("当前订单状态不可取消");
+        }
+
+        // 更新订单状态为取消
+        order.setStatus(5);
+        fsGuCoinOrderMapper.updateFsGuCoinOrder(order);
+
+        // 还原库存
+        if (StringUtils.isNotEmpty(order.getItemJson())) {
+            JSONArray jsonArray = JSONUtil.parseArray(order.getItemJson());
+            List<FsGuCoinGoods> goodsItem = JSONUtil.toList(jsonArray, FsGuCoinGoods.class);
+            for (FsGuCoinGoods g : goodsItem) {
+                int num = g.getNum() == null ? 1 : g.getNum().intValue();
+                fsGuCoinGoodsMapper.addStock(g.getGoodsId(), num);
+            }
+        }
+
+        // 还原谷币(logType=3 订单退款返还)
+        long orderGuCoin = order.getGuCoin() == null ? 0L : order.getGuCoin();
+        if (orderGuCoin > 0) {
+            fsUserGuCoinLogsService.refundGuCoin(order.getUserId(), orderGuCoin,
+                    order.getOrderId().toString(), "取消订单返还谷币:" + order.getOrderCode());
+        }
+        return R.ok("订单已取消");
+    }
+
+    /**
+     * 预支付(现金/混合支付订单发起支付)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R payment(FsGuCoinOrderDoPayParam param, PaymentMethodEnum paymentMethod) {
+        FsGuCoinOrder order = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderId(param.getOrderId());
+        if (order == null || !Objects.equals(order.getStatus(), 4)
+                || order.getPayMoney() == null || order.getPayMoney().compareTo(BigDecimal.ZERO) <= 0) {
+            return R.error("非法操作");
+        }
+        PayOrderParam payOrderParam = new PayOrderParam();
+        payOrderParam.setOrderId(order.getOrderId());
+        payOrderParam.setOrderCode(order.getOrderCode());
+        payOrderParam.setAmount(order.getPayMoney());
+        payOrderParam.setUserId(order.getUserId());
+        payOrderParam.setCompanyId(order.getCompanyId());
+        payOrderParam.setCompanyUserId(order.getCompanyUserId());
+        payOrderParam.setPaymentMethod(paymentMethod);
+        payOrderParam.setBusinessType(BusinessTypeEnum.GU_COIN_ORDER);
+        payOrderParam.setAppId(param.getAppId());
+        return storePaymentService.processPayment(payOrderParam);
+    }
+
+    /**
+     * 支付回调(支付成功后更新订单状态为待发货)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R payConfirm(String orderSn, String payCode, String tradeNo, String payType, int type,
+                        String bankTransactionId, String bankSerialNo) {
+        log.info("谷币商城订单支付回调 orderSn: {}, payCode: {}, tradeNo: {}, type: {}", orderSn, payCode, tradeNo, type);
+        FsGuCoinOrder order = null;
+        if (type == 1) {
+            FsStorePayment storePayment = storePaymentService.selectFsStorePaymentByPaymentCode(payCode);
+            if (Objects.isNull(storePayment)) {
+                log.info("支付单号不存在 payCode: {}", payCode);
+                return R.error("支付单号不存在");
+            }
+            if (storePayment.getStatus() == 0) {
+                FsStorePayment paymentMap = new FsStorePayment();
+                paymentMap.setPaymentId(storePayment.getPaymentId());
+                paymentMap.setStatus(1);
+                paymentMap.setPayTime(new Date());
+                paymentMap.setTradeNo(tradeNo);
+                paymentMap.setBankSerialNo(bankSerialNo);
+                paymentMap.setBankTransactionId(bankTransactionId);
+                storePaymentService.updateFsStorePayment(paymentMap);
+                order = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderId(Long.parseLong(storePayment.getBusinessId()));
+            }
+        } else if (type == 2) {
+            order = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderCode(orderSn);
+        }
+
+        if (Objects.isNull(order) || order.getStatus() != 4) {
+            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+            return R.error("非法操作");
+        }
+
+        order.setIsPay(1);
+        order.setStatus(1); // 待发货
+        order.setPayTime(new Date());
+        fsGuCoinOrderMapper.updateFsGuCoinOrder(order);
+        log.info("谷币商城订单支付成功 orderCode={}, userId={}", order.getOrderCode(), order.getUserId());
+        return R.ok();
+    }
+
+    /**
+     * 取消订单(按订单编号,用于发货前取消/退款返还谷币)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int cancelOrder(String orderCode) {
+        FsGuCoinOrder order = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderCode(orderCode);
+        if (order == null) {
+            throw new CustomException("订单不存在");
+        }
+        if (order.getStatus() != 1 && order.getStatus() != 4) {
+            throw new CustomException("订单已发货或已完成,不可取消");
+        }
+        int i = fsGuCoinOrderMapper.cancelOrder(order.getOrderId());
+        if (i > 0) {
+            // 还原库存
+            if (StringUtils.isNotEmpty(order.getItemJson())) {
+                JSONArray jsonArray = JSONUtil.parseArray(order.getItemJson());
+                List<FsGuCoinGoods> goodsItem = JSONUtil.toList(jsonArray, FsGuCoinGoods.class);
+                for (FsGuCoinGoods g : goodsItem) {
+                    int num = g.getNum() == null ? 1 : g.getNum().intValue();
+                    fsGuCoinGoodsMapper.addStock(g.getGoodsId(), num);
+                }
+            }
+            // 还原谷币
+            long orderGuCoin = order.getGuCoin() == null ? 0L : order.getGuCoin();
+            if (orderGuCoin > 0) {
+                fsUserGuCoinLogsService.refundGuCoin(order.getUserId(), orderGuCoin,
+                        order.getOrderId().toString(), "取消订单返还谷币:" + orderCode);
+            }
+        }
+        return i;
+    }
+
+    /**
+     * 完成订单
+     */
+    @Override
+    public int finishOrder(String orderCode) {
+        FsGuCoinOrder order = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderCode(orderCode);
+        if (order == null) {
+            throw new CustomException("订单不存在");
+        }
+        order.setStatus(3); // 已完成
+        return fsGuCoinOrderMapper.updateFsGuCoinOrder(order);
+    }
+
+    /**
+     * 推送聚水潭(构建ErpOrder并批量推送,回写extendOrderId)
+     * 与积分订单一致:固定 shop_code、无货到付款推两次逻辑。
+     * 区别:按 payType 推真实支付金额。
+     */
+    @Override
+    public void createOmsGuCoinOrder(List<Long> orderIds) throws ParseException, InterruptedException {
+        if (orderIds == null || orderIds.isEmpty()) {
+            return;
+        }
+        // 1. 校验 ERP 开启且为聚水潭(5)
+        FsSysConfig sysConfig = configUtil.getSysConfig();
+        Integer erpOpen = sysConfig.getErpOpen();
+        if (erpOpen == null || erpOpen == 0) {
+            log.info("ERP未开启,跳过谷币订单推送聚水潭");
+            return;
+        }
+        Integer erpType = sysConfig.getErpType();
+        if (erpType == null || erpType != 5) {
+            log.info("谷币订单仅支持聚水潭推送,当前ERP类型: {},跳过", erpType);
+            return;
+        }
+
+        List<ErpOrder> erpOrders = new ArrayList<>();
+        for (Long orderId : orderIds) {
+            FsGuCoinOrder fsGuCoinOrder = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderId(orderId);
+            if (fsGuCoinOrder == null) {
+                continue;
+            }
+            // 仅推送待发货(status=1)且未推送(extendOrderId为空)的订单
+            if (StringUtils.isNotEmpty(fsGuCoinOrder.getExtendOrderId())) {
+                continue;
+            }
+            if (fsGuCoinOrder.getStatus() == null || fsGuCoinOrder.getStatus() != 1) {
+                continue;
+            }
+
+            ErpOrder erpOrder = new ErpOrder();
+            erpOrder.setVip_code(fsGuCoinOrder.getUserId().toString());
+            erpOrder.setPlatform_code(fsGuCoinOrder.getOrderCode());
+            erpOrder.setShop_code(shopCode);
+            erpOrder.setSeller_memo(fsGuCoinOrder.getRemark());
+            erpOrder.setRemark(fsGuCoinOrder.getRemark());
+            erpOrder.setOrder_type_code("order");
+
+            // 支付信息:按 payType 推真实金额
+            List<ErpOrderPayment> payments = new ArrayList<>();
+            ErpOrderPayment payment = new ErpOrderPayment();
+            payment.setPay_type_code("weixin");
+            BigDecimal payMoney = fsGuCoinOrder.getPayMoney() == null ? BigDecimal.ZERO : fsGuCoinOrder.getPayMoney();
+            payment.setPayment(payMoney.doubleValue());
+            erpOrder.setBuyer_account(fsGuCoinOrder.getUserName());
+            Date payTime = fsGuCoinOrder.getPayTime() != null ? fsGuCoinOrder.getPayTime() : fsGuCoinOrder.getCreateTime();
+            if (payTime != null) {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                Date date = sdf.parse(sdf.format(payTime));
+                payment.setPaytime(new Timestamp(date.getTime()));
+            }
+            payments.add(payment);
+            erpOrder.setPayments(payments);
+
+            // 备注追加金额明细(OMS展示用,与积分订单一致)
+            BigDecimal totalMoney = fsGuCoinOrder.getTotalMoney() == null ? BigDecimal.ZERO : fsGuCoinOrder.getTotalMoney();
+            BigDecimal deliveryMoney = fsGuCoinOrder.getDeliveryMoney() == null ? BigDecimal.ZERO : fsGuCoinOrder.getDeliveryMoney();
+            BigDecimal discountMoney = fsGuCoinOrder.getDiscountMoney() == null ? BigDecimal.ZERO : fsGuCoinOrder.getDiscountMoney();
+            ErpRemarkDTO remarkDTO = new ErpRemarkDTO();
+            remarkDTO.setOrderId(fsGuCoinOrder.getOrderCode());
+            remarkDTO.setTotalPrice(totalMoney);
+            remarkDTO.setPayPrice(payMoney);
+            remarkDTO.setPayMoney(payMoney);
+            remarkDTO.setCouponMoney(discountMoney);
+            remarkDTO.setDeliveryMoney(deliveryMoney);
+            remarkDTO.setYdMoney(BigDecimal.ZERO);
+            if (payTime != null) {
+                SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                remarkDTO.setPayTime(sdf2.format(payTime));
+            }
+            String remarkJson = JSONUtil.toJsonStr(remarkDTO);
+            erpOrder.setSeller_memo(org.apache.commons.lang3.StringUtils.defaultString(erpOrder.getSeller_memo()) + remarkJson);
+            erpOrder.setRemark(org.apache.commons.lang3.StringUtils.defaultString(erpOrder.getRemark()) + remarkJson);
+
+            SimpleDateFormat sdfDeal = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            erpOrder.setDeal_datetime(sdfDeal.format(new Date()));
+
+            // 收货人信息
+            if (StringUtils.isNotEmpty(fsGuCoinOrder.getUserName())) {
+                erpOrder.setReceiver_name(fsGuCoinOrder.getUserName().replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9]", ""));
+            }
+            String userPhone = fsGuCoinOrder.getUserPhone();
+            if (StringUtils.isNotEmpty(userPhone)) {
+                // 手机号长度>11视为加密存储,需解密
+                if (userPhone.length() > 11) {
+                    try {
+                        erpOrder.setReceiver_mobile(decryptPhone(userPhone));
+                    } catch (Exception e) {
+                        erpOrder.setReceiver_mobile(userPhone);
+                    }
+                } else {
+                    erpOrder.setReceiver_mobile(userPhone);
+                }
+            }
+            if (StringUtils.isNotEmpty(fsGuCoinOrder.getUserAddress())) {
+                erpOrder.setReceiver_address(fsGuCoinOrder.getUserAddress());
+            }
+
+            erpOrders.add(erpOrder);
+        }
+
+        if (erpOrders.isEmpty()) {
+            log.info("无待推送的谷币订单");
+            return;
+        }
+        log.info("谷币订单推送聚水潭,待推送数量: {}", erpOrders.size());
+        // 推送聚水潭(回写 extendOrderId 在 JST 实现内完成)
+        jstErpOrderService.batchAddGuCoinOrder(erpOrders);
+    }
+
+    /**
+     * 同步聚水潭发货状态后回写订单(含快递鸟订阅)
+     * 仿 FsIntegralOrderServiceImpl.deliveryIntegralOrderOrder
+     */
+    @Override
+    public void deliveryGuCoinOrderOrder(String orderCode, String deliveryId, String deliverCode, String deliverName) {
+        FsGuCoinOrder fsGuCoinOrder = fsGuCoinOrderMapper.selectFsGuCoinOrderByOrderCode(orderCode);
+        if (fsGuCoinOrder == null || fsGuCoinOrder.getStatus() == null || fsGuCoinOrder.getStatus() != 1) {
+            return;
+        }
+        // 快递公司编号映射:聚水潭 omsCode -> 系统内部 code
+        fsGuCoinOrder.setDeliveryName(deliverName);
+        FsExpress express = expressService.selectFsExpressByOmsCode(deliverCode);
+        if (express != null) {
+            fsGuCoinOrder.setDeliveryCode(express.getCode());
+        } else {
+            fsGuCoinOrder.setDeliveryCode(deliverCode);
+        }
+        fsGuCoinOrder.setStatus(2); // 待收货
+        fsGuCoinOrder.setDeliverySn(deliveryId);
+        fsGuCoinOrder.setDeliveryTime(DateUtils.getNowDate());
+        fsGuCoinOrderMapper.updateFsGuCoinOrder(fsGuCoinOrder);
+
+        // 订阅快递鸟物流(参数顺序与积分订单一致:orderCode, deliveryCode, deliverySn, userPhone)
+        try {
+            expressService.subscribeEspress(fsGuCoinOrder.getOrderCode(),
+                    fsGuCoinOrder.getDeliveryCode(),
+                    fsGuCoinOrder.getDeliverySn(),
+                    fsGuCoinOrder.getUserPhone());
+        } catch (Exception e) {
+            log.error("谷币订单订阅快递鸟失败 orderCode: {}", orderCode, e);
+        }
+    }
+}

+ 264 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserGuCoinLogsServiceImpl.java

@@ -0,0 +1,264 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsCourseAnswerLogs;
+import com.fs.course.mapper.FsCourseAnswerLogsMapper;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserGuCoinLogs;
+import com.fs.his.enums.GuCoinLogTypeEnum;
+import com.fs.his.mapper.FsUserGuCoinLogsMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.param.FsUserAddGuCoinParam;
+import com.fs.his.param.FsUserGuCoinLogsParam;
+import com.fs.his.service.IFsUserGuCoinLogsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 会员谷币记录Service业务层
+ * 谷币:谷养正道商城专属积分,无有效期
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Service
+public class FsUserGuCoinLogsServiceImpl implements IFsUserGuCoinLogsService
+{
+    private static final Logger log = LoggerFactory.getLogger(FsUserGuCoinLogsServiceImpl.class);
+
+    /** 看课奖励每节谷币数 */
+    private static final long COURSE_REWARD_GU_COIN = 150L;
+
+    @Autowired
+    private FsUserGuCoinLogsMapper fsUserGuCoinLogsMapper;
+    @Autowired
+    private FsUserMapper fsUserMapper;
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    @Autowired
+    private FsCourseAnswerLogsMapper fsCourseAnswerLogsMapper;
+
+    @Override
+    public FsUserGuCoinLogs selectFsUserGuCoinLogsById(Long id) {
+        return fsUserGuCoinLogsMapper.selectFsUserGuCoinLogsById(id);
+    }
+
+    @Override
+    public List<FsUserGuCoinLogs> selectFsUserGuCoinLogsList(FsUserGuCoinLogsParam param) {
+        return fsUserGuCoinLogsMapper.selectFsUserGuCoinLogsList(param);
+    }
+
+    @Override
+    public int insertFsUserGuCoinLogs(FsUserGuCoinLogs fsUserGuCoinLogs) {
+        fsUserGuCoinLogs.setCreateTime(DateUtils.getNowDate());
+        return fsUserGuCoinLogsMapper.insertFsUserGuCoinLogs(fsUserGuCoinLogs);
+    }
+
+    /**
+     * 看课获取谷币
+     * 积分逻辑:完整观看1节全部课程内容、完成对应课后答题、所有答题全部正确,
+     * 每节课程可获得150谷币/节;重复观看课程只获得一次积分。
+     * 未完整看课、未答题、答题有误,均不发放本节学习积分。
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
+    public R rewardCourseWatchGuCoin(Long userId, Long videoId, Long courseId) {
+        try {
+            if (userId == null || videoId == null) {
+                return R.error("参数缺失");
+            }
+            // 1. 去重:同一小节只发放一次学习积分(重复观看只获得一次积分)
+            List<FsUserGuCoinLogs> existLogs = fsUserGuCoinLogsMapper.selectByUserIdAndLogTypeAndBusinessId(
+                    userId, GuCoinLogTypeEnum.TYPE_1.getValue(), videoId.toString());
+            if (existLogs != null && !existLogs.isEmpty()) {
+                log.info("看课谷币奖励-用户{}小节{}已发放过,跳过", userId, videoId);
+                return R.ok("本节课程已发放过学习积分");
+            }
+            // 2. 校验:完整观看(完课 log_type=2)
+            int finishedCount = fsCourseWatchLogMapper.countFinishedWatchLog(userId, videoId);
+            if (finishedCount <= 0) {
+                log.info("看课谷币奖励-用户{}小节{}未完整看课,不发放", userId, videoId);
+                return R.error("未完整看课,不发放本节学习积分");
+            }
+            // 3. 校验:完成课后答题且全部正确(is_right=1)
+            FsCourseAnswerLogs rightLog = fsCourseAnswerLogsMapper.selectRightLogByCourseVideoIsOpen(videoId, userId);
+            if (rightLog == null) {
+                log.info("看课谷币奖励-用户{}小节{}未答题或答题有误,不发放", userId, videoId);
+                return R.error("未答题或答题有误,不发放本节学习积分");
+            }
+            // 4. 全部条件满足,发放150谷币
+            return doAddGuCoin(userId, COURSE_REWARD_GU_COIN, GuCoinLogTypeEnum.TYPE_1,
+                    videoId.toString(), null, "看课奖励:" + COURSE_REWARD_GU_COIN + "谷币",
+                    null, null, 3);
+        } catch (Exception e) {
+            log.error("看课谷币奖励失败 userId={}, videoId={}", userId, videoId, e);
+            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+            return R.error("学习积分发放失败");
+        }
+    }
+
+    /**
+     * 录入会员谷币(总后台/销售后台通用)
+     * 填写发放备注、消费金额,留存操作日志,支持溯源对账
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R addGuCoin(FsUserAddGuCoinParam param, Integer logType, Long operatorId, String operatorName, Integer operatorSource) {
+        try {
+            if (param == null || param.getUserId() == null || param.getGuCoin() == null) {
+                return R.error("参数缺失");
+            }
+            if (param.getGuCoin() <= 0) {
+                return R.error("录入谷币必须大于0");
+            }
+            GuCoinLogTypeEnum logsType = GuCoinLogTypeEnum.toType(logType);
+            if (logsType == null) {
+                return R.error("谷币记录类型错误");
+            }
+            return doAddGuCoin(param.getUserId(), param.getGuCoin(), logsType,
+                    operatorId != null ? operatorId.toString() : null,
+                    param.getConsumeAmount(), param.getRemark(),
+                    operatorId, operatorName, operatorSource);
+        } catch (Exception e) {
+            log.error("录入会员谷币失败 param={}", param, e);
+            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+            return R.error("录入谷币失败");
+        }
+    }
+
+    /**
+     * 谷币增加通用方法:原子更新余额 + 写入操作日志(溯源对账)
+     */
+    private R doAddGuCoin(Long userId, Long guCoinNum, GuCoinLogTypeEnum logsType,
+                          String businessId, java.math.BigDecimal consumeAmount, String remark,
+                          Long operatorId, String operatorName, Integer operatorSource) {
+        FsUser user = fsUserMapper.selectFsUserByUserId(userId);
+        if (user == null) {
+            return R.error("会员信息不存在");
+        }
+        // 原子增加谷币余额
+        fsUserMapper.addGuCoin(userId, guCoinNum);
+        // 查询变动后余额
+        FsUser newUser = fsUserMapper.selectFsUserByUserId(userId);
+        long newBalance = newUser.getGuCoin() == null ? 0L : newUser.getGuCoin();
+        // 写入操作日志(留存操作日志,支持溯源对账)
+        FsUserGuCoinLogs logs = new FsUserGuCoinLogs();
+        logs.setUserId(userId);
+        logs.setLogType(logsType.getValue());
+        logs.setGuCoin(guCoinNum);
+        logs.setBalance(newBalance);
+        logs.setBusinessId(businessId);
+        logs.setConsumeAmount(consumeAmount);
+        logs.setRemark(remark);
+        logs.setOperatorId(operatorId);
+        logs.setOperatorName(operatorName);
+        logs.setOperatorSource(operatorSource);
+        logs.setNickName(user.getNickName());
+        logs.setPhone(user.getPhone());
+        logs.setStatus(1);
+        logs.setCreateTime(new Date());
+        fsUserGuCoinLogsMapper.insertFsUserGuCoinLogs(logs);
+        log.info("谷币变动 userId={}, type={}, num={}, balance={}, operator={}", userId, logsType.getDesc(), guCoinNum, newBalance, operatorId);
+        return R.ok(logsType.getDesc() + ":" + guCoinNum + "谷币").put("balance", newBalance);
+    }
+
+    /**
+     * 商城消费扣减谷币(下单时调用)
+     * 原子扣减余额(负数),写入消费日志(logType=2 商城消费),支持溯源对账
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R consumeGuCoin(Long userId, Long guCoinNum, String businessId, String remark) {
+        try {
+            if (userId == null || guCoinNum == null || guCoinNum <= 0) {
+                return R.error("参数缺失");
+            }
+            FsUser user = fsUserMapper.selectFsUserByUserId(userId);
+            if (user == null) {
+                return R.error("会员信息不存在");
+            }
+            long currentBalance = user.getGuCoin() == null ? 0L : user.getGuCoin();
+            if (currentBalance < guCoinNum) {
+                return R.error("谷币余额不足");
+            }
+            // 原子扣减(负数)
+            fsUserMapper.addGuCoin(userId, -guCoinNum);
+            // 查询变动后余额
+            FsUser newUser = fsUserMapper.selectFsUserByUserId(userId);
+            long newBalance = newUser.getGuCoin() == null ? 0L : newUser.getGuCoin();
+            // 写入消费日志
+            FsUserGuCoinLogs logs = new FsUserGuCoinLogs();
+            logs.setUserId(userId);
+            logs.setLogType(GuCoinLogTypeEnum.TYPE_2.getValue());
+            logs.setGuCoin(-guCoinNum);
+            logs.setBalance(newBalance);
+            logs.setBusinessId(businessId);
+            logs.setRemark(remark);
+            logs.setOperatorSource(3);
+            logs.setNickName(user.getNickName());
+            logs.setPhone(user.getPhone());
+            logs.setStatus(1);
+            logs.setCreateTime(new Date());
+            fsUserGuCoinLogsMapper.insertFsUserGuCoinLogs(logs);
+            log.info("谷币消费 userId={}, num={}, businessId={}, balance={}", userId, guCoinNum, businessId, newBalance);
+            return R.ok("消费" + guCoinNum + "谷币").put("balance", newBalance);
+        } catch (Exception e) {
+            log.error("谷币消费失败 userId={}, guCoinNum={}, businessId={}", userId, guCoinNum, businessId, e);
+            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+            return R.error("谷币消费失败");
+        }
+    }
+
+    /**
+     * 订单退款/取消返还谷币
+     * 原子增加余额,写入返还日志(logType=3 订单退款返还),支持溯源对账
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R refundGuCoin(Long userId, Long guCoinNum, String businessId, String remark) {
+        try {
+            if (userId == null || guCoinNum == null || guCoinNum <= 0) {
+                return R.error("参数缺失");
+            }
+            FsUser user = fsUserMapper.selectFsUserByUserId(userId);
+            if (user == null) {
+                return R.error("会员信息不存在");
+            }
+            // 原子增加(正数)
+            fsUserMapper.addGuCoin(userId, guCoinNum);
+            // 查询变动后余额
+            FsUser newUser = fsUserMapper.selectFsUserByUserId(userId);
+            long newBalance = newUser.getGuCoin() == null ? 0L : newUser.getGuCoin();
+            // 写入返还日志
+            FsUserGuCoinLogs logs = new FsUserGuCoinLogs();
+            logs.setUserId(userId);
+            logs.setLogType(GuCoinLogTypeEnum.TYPE_3.getValue());
+            logs.setGuCoin(guCoinNum);
+            logs.setBalance(newBalance);
+            logs.setBusinessId(businessId);
+            logs.setRemark(remark);
+            logs.setOperatorSource(3);
+            logs.setNickName(user.getNickName());
+            logs.setPhone(user.getPhone());
+            logs.setStatus(1);
+            logs.setCreateTime(new Date());
+            fsUserGuCoinLogsMapper.insertFsUserGuCoinLogs(logs);
+            log.info("谷币返还 userId={}, num={}, businessId={}, balance={}", userId, guCoinNum, businessId, newBalance);
+            return R.ok("返还" + guCoinNum + "谷币").put("balance", newBalance);
+        } catch (Exception e) {
+            log.error("谷币返还失败 userId={}, guCoinNum={}, businessId={}", userId, guCoinNum, businessId, e);
+            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+            return R.error("谷币返还失败");
+        }
+    }
+}

+ 52 - 0
fs-service/src/main/java/com/fs/his/vo/FeiShuCourseReportVO.java

@@ -0,0 +1,52 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 飞书看课统计报表 VO
+ * 仅查询 feishu_course_watch_log 表,无答题/红包数据
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+@Data
+public class FeiShuCourseReportVO {
+
+    /** 公司id */
+    private Long companyId;
+
+    /** 公司名称 */
+    @Excel(name = "销售公司")
+    private String companyName;
+
+    /** 进线数 */
+    @Excel(name = "进线数")
+    private Integer accessCount;
+
+    /** 待看课数 */
+    @Excel(name = "待看课数")
+    private Integer pendingCount;
+
+    /** 看课中数 */
+    @Excel(name = "看课中数")
+    private Integer watchingCount;
+
+    /** 完课数 */
+    @Excel(name = "完课数")
+    private Integer finishedCount;
+
+    /** 看课中断数 */
+    @Excel(name = "看课中断数")
+    private Integer interruptedCount;
+
+    /** 看课率 */
+    @Excel(name = "看课率")
+    private BigDecimal watchRate;
+
+    /** 完课率 */
+    @Excel(name = "完课率")
+    private BigDecimal finishRate;
+}

+ 84 - 0
fs-service/src/main/java/com/fs/his/vo/FeiShuWatchLogReportVO.java

@@ -0,0 +1,84 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 飞书看课统计报表 VO
+ * 查询 feishu_course_watch_log 表,无会员数/当前会员上线数/答题/红包字段
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+@Data
+public class FeiShuWatchLogReportVO {
+
+    @Excel(name = "会员id")
+    private Long userId;
+
+    @Excel(name = "会员昵称")
+    private String nickName;
+
+    @Excel(name = "销售数")
+    private Integer salesCount;
+
+    @Excel(name = "所属销售")
+    private String salesName;
+
+    @Excel(name = "销售部门")
+    private String salesDept;
+
+    @Excel(name = "所属销售公司")
+    private String salesCompany;
+
+    @Excel(name = "训练营")
+    private String trainingCampName;
+
+    @Excel(name = "营期")
+    private String periodName;
+
+    @Excel(name = "小节名称")
+    private String videoTitle;
+
+    @Excel(name = "看课状态")
+    private String watchStatus;
+
+    @Excel(name = "观看时长")
+    private String duration;
+
+    @Excel(name = "完课数")
+    private Integer finishedCount;
+
+    @Excel(name = "未完课")
+    private Integer unfinishedCount;
+
+    @Excel(name = "完课率")
+    private BigDecimal completionRate;
+
+    @Excel(name = "看课时间", dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date courseTime;
+
+    @Excel(name = "完课时间", dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date finishTime;
+
+    @Excel(name = "未看数")
+    private Integer notWatchedCount;
+
+    @Excel(name = "历史疗法订单数")
+    private Integer historyOrderCount;
+
+    // ---- 非导出字段 ----
+
+    private Long companyUserId;
+    private Long totalLogCount;
+    private Long periodId;
+    private Long videoId;
+    private Long logId;
+    private Long deptId;
+}

+ 53 - 0
fs-service/src/main/java/com/fs/his/vo/FsGuCoinCartVO.java

@@ -0,0 +1,53 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 谷币商城购物车列表VO
+ *
+ * @author fs
+ * @date 2026-07-20
+ */
+@Data
+public class FsGuCoinCartVO {
+
+    @ApiModelProperty("主键ID")
+    private Long id;
+
+    @ApiModelProperty("谷币商品ID")
+    private Long goodsId;
+
+    @ApiModelProperty("商品名称")
+    private String goodsName;
+
+    @ApiModelProperty("商品主图")
+    private String imgUrl;
+
+    @ApiModelProperty("添加购物车时所需谷币")
+    private Long guCoin;
+
+    @ApiModelProperty("添加购物车时所需金额")
+    private BigDecimal cash;
+
+    @ApiModelProperty("最新所需谷币")
+    private Long newGuCoin;
+
+    @ApiModelProperty("最新所需金额")
+    private BigDecimal newCash;
+
+    @ApiModelProperty("数量")
+    private Integer cartNum;
+
+    @ApiModelProperty("创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("修改时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+}

+ 135 - 0
fs-service/src/main/resources/mapper/course/FeiShuCourseWatchLogMapper.xml

@@ -25,4 +25,139 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         AND company_user_id = #{companyUserId}
         AND period_id = #{periodId}
     </update>
+
+    <!-- 飞书看课统计:仅按公司维度,查询 feishu_course_watch_log 表 -->
+    <select id="selectFeiShuWatchStatistics" resultType="com.fs.his.vo.FeiShuCourseReportVO">
+        SELECT
+            company_id AS companyId,
+            COUNT(DISTINCT CASE WHEN log_type = 3 THEN log_id END) AS pendingCount,
+            COUNT(DISTINCT CASE WHEN log_type != 3 THEN log_id END) AS watchingCount,
+            COUNT(DISTINCT CASE WHEN log_type = 2 THEN log_id END) AS finishedCount,
+            COUNT(DISTINCT CASE WHEN log_type = 4 THEN log_id END) AS interruptedCount,
+            COUNT(DISTINCT log_id) AS accessCount
+        FROM feishu_course_watch_log
+        <where>
+            <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+                AND create_time &gt;= #{startDate} AND create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+            </if>
+            <if test="companyIds != null and companyIds.size() > 0">
+                AND company_id IN
+                <foreach collection="companyIds" item="companyId" open="(" separator="," close=")">
+                    #{companyId}
+                </foreach>
+            </if>
+        </where>
+        GROUP BY company_id
+    </select>
+
+    <!-- 飞书看课统计:销售维度(对应 fs_course_watch_log 的 selectSalesBaseData) -->
+    <select id="selectFeiShuSalesBaseData" resultType="com.fs.his.vo.FeiShuWatchLogReportVO">
+        SELECT
+            log.user_id userId,
+            log.company_user_id companyUserId,
+            log.log_id logId,
+            log.create_time courseTime,
+            cu.nick_name as salesName,
+            d.dept_name AS salesDept,
+            c.company_name AS salesCompany,
+            COUNT(DISTINCT log.log_id) AS totalLogCount,
+            COUNT(DISTINCT CASE WHEN log.log_type = 2 THEN log.log_id END) AS finishedCount,
+            COUNT(DISTINCT CASE WHEN log.log_type IN (1, 3, 4) THEN log.log_id END) AS unfinishedCount,
+            COUNT(DISTINCT log.log_id) - COUNT(DISTINCT log.user_id) AS notWatchedCount
+        FROM feishu_course_watch_log log
+        <if test="orderSTime != null and orderETime != null">
+            LEFT JOIN fs_package_order po ON po.user_id = log.user_id
+        </if>
+        LEFT JOIN company_user cu ON log.company_user_id = cu.user_id
+        LEFT JOIN fs_user u on log.user_id = u.user_id
+        LEFT JOIN company_dept d ON cu.dept_id = d.dept_id
+        LEFT JOIN company c ON d.company_id = c.company_id
+        <where>
+            <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+                AND log.create_time &gt;= #{startDate} AND log.create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+            </if>
+            <if test="orderSTime != null and orderETime != null">
+                AND DATE(po.create_time) BETWEEN #{orderSTime} AND #{orderETime}
+            </if>
+            <if test="companyId != null and companyId != ''">
+                AND log.company_id = #{companyId}
+            </if>
+            <if test="deptId != null and deptId != ''">
+                AND d.dept_id = #{deptId}
+            </if>
+            <if test="trainingCampId != null and trainingCampId != ''">
+                AND log.period_id IN (SELECT period_id FROM fs_user_course_period WHERE training_camp_id = #{trainingCampId})
+            </if>
+            <if test="periodId != null and periodId != ''">
+                AND log.period_id = #{periodId}
+            </if>
+            <if test="userId != null and userId != ''">
+                AND log.user_id = #{userId}
+            </if>
+            <if test="userPhone != null and userPhone != ''">
+                AND u.phone LIKE CONCAT('%', #{userPhone}, '%')
+            </if>
+            <if test="project != null and project != ''">
+                AND log.project = #{project}
+            </if>
+            <if test="nickName != null and nickName != ''">
+                AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
+            </if>
+            <if test="params != null and params.dataScope != null and params.dataScope != ''">
+                ${params.dataScope}
+            </if>
+        </where>
+        GROUP BY cu.user_id
+    </select>
+
+    <!-- 飞书看课统计:部门维度(对应 fs_course_watch_log 的 selectCompanyDeptStatsMerged) -->
+    <select id="selectFeiShuCompanyDeptStats" resultType="com.fs.his.vo.FeiShuWatchLogReportVO">
+        SELECT
+            d.dept_id AS deptId,
+            d.dept_name AS salesDept,
+            c.company_name AS salesCompany,
+            COUNT(DISTINCT cu.user_id) AS salesCount,
+            COUNT(DISTINCT log.log_id) AS totalLogCount,
+            COUNT(DISTINCT CASE WHEN log.log_type = 2 THEN log.log_id END) AS finishedCount,
+            COUNT(DISTINCT CASE WHEN log.log_type IN (1, 3, 4) THEN log.log_id END) AS unfinishedCount,
+            COUNT(DISTINCT log.user_id) - COUNT(DISTINCT CASE WHEN log.log_type = 2 THEN log.user_id END) AS notWatchedCount
+        FROM feishu_course_watch_log log
+        LEFT JOIN company_user cu ON log.company_user_id = cu.user_id
+        LEFT JOIN fs_user u ON log.user_id = u.user_id
+        LEFT JOIN company_dept d ON cu.dept_id = d.dept_id
+        LEFT JOIN company c ON d.company_id = c.company_id
+        <where>
+            <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+                AND log.create_time &gt;= #{startDate} AND log.create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+            </if>
+            <if test="companyId != null and companyId != ''">
+                AND c.company_id = #{companyId}
+            </if>
+            <if test="deptId != null and deptId != ''">
+                AND d.dept_id = #{deptId}
+            </if>
+            <if test="trainingCampId != null and trainingCampId != ''">
+                AND log.period_id IN (SELECT period_id FROM fs_user_course_period WHERE training_camp_id = #{trainingCampId})
+            </if>
+            <if test="periodId != null and periodId != ''">
+                AND log.period_id = #{periodId}
+            </if>
+            <if test="userId != null and userId != ''">
+                AND log.user_id = #{userId}
+            </if>
+            <if test="userPhone != null and userPhone != ''">
+                AND u.phone LIKE CONCAT('%', #{userPhone}, '%')
+            </if>
+            <if test="project != null and project != ''">
+                AND log.project = #{project}
+            </if>
+            <if test="nickName != null and nickName != ''">
+                AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
+            </if>
+            <if test="params != null and params.dataScope != null and params.dataScope != ''">
+                ${params.dataScope}
+            </if>
+        </where>
+        GROUP BY d.dept_id
+    </select>
 </mapper>

+ 5 - 2
fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml

@@ -289,8 +289,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test='companyId !=null'>
                 and log.company_id = #{companyId}
             </if>
-            <if test="typeFlag != null">
-                and log.type_flag = #{typeFlag}
+            <if test="typeFlag != null and typeFlag.size() > 0">
+                and log.type_flag in
+                <foreach collection="typeFlag" item="flag" open="(" close=")" separator=",">
+                    #{flag}
+                </foreach>
             </if>
             <if test="courseId != null">
                 and log.course_id = ${courseId}

+ 81 - 0
fs-service/src/main/resources/mapper/his/FsGuCoinCartMapper.xml

@@ -0,0 +1,81 @@
+<?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.his.mapper.FsGuCoinCartMapper">
+
+    <insert id="insertOrUpdate">
+        insert into fs_gu_coin_cart (
+            user_id,
+            goods_id,
+            gu_coin,
+            cash,
+            cart_num,
+            create_time,
+            update_time
+        ) values (
+            #{userId},
+            #{goodsId},
+            #{guCoin},
+            #{cash},
+            #{cartNum},
+            #{createTime},
+            #{updateTime}
+        )
+        on duplicate key update
+            gu_coin = values(gu_coin),
+            cash = values(cash),
+            update_time = values(update_time),
+            cart_num =
+            <choose>
+                <when test="addToExist == true">
+                    cart_num + values(cart_num)
+                </when>
+                <otherwise>
+                    values(cart_num)
+                </otherwise>
+            </choose>
+    </insert>
+
+    <select id="getCartCountByMap" resultType="java.lang.Integer">
+        select
+            ifnull(sum(gc.cart_num), 0)
+        from fs_gu_coin_cart gc
+        <where>
+            <if test="params.userId != null">
+                gc.user_id = #{params.userId}
+            </if>
+            <if test="params.goodsId != null">
+                and gc.goods_id = #{params.goodsId}
+            </if>
+        </where>
+    </select>
+
+    <select id="getCartsByMap" resultType="com.fs.his.vo.FsGuCoinCartVO">
+        select
+            gc.id,
+            gc.goods_id,
+            gg.goods_name,
+            gg.img_url,
+            gc.gu_coin,
+            gc.cash,
+            gg.gu_coin newGuCoin,
+            gg.cash newCash,
+            gc.cart_num,
+            gc.create_time,
+            gc.update_time
+        from fs_gu_coin_cart gc
+        inner join fs_gu_coin_goods gg on gg.goods_id = gc.goods_id
+        <where>
+            <if test="params.userId != null">
+                gc.user_id = #{params.userId}
+            </if>
+            <if test="params.ids != null and params.ids.size() > 0">
+                and gc.id in
+                <foreach collection="params.ids" item="id" open="(" close=")" separator=",">
+                    #{id}
+                </foreach>
+            </if>
+        </where>
+    </select>
+</mapper>

+ 118 - 0
fs-service/src/main/resources/mapper/his/FsGuCoinGoodsMapper.xml

@@ -0,0 +1,118 @@
+<?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.his.mapper.FsGuCoinGoodsMapper">
+
+    <resultMap type="FsGuCoinGoods" id="FsGuCoinGoodsResult">
+        <result property="goodsId"    column="goods_id"    />
+        <result property="imgUrl"     column="img_url"     />
+        <result property="images"     column="images"      />
+        <result property="goodsName"  column="goods_name"  />
+        <result property="otPrice"    column="ot_price"    />
+        <result property="goodsType"  column="goods_type"  />
+        <result property="status"     column="status"      />
+        <result property="guCoin"     column="gu_coin"     />
+        <result property="cash"       column="cash"        />
+        <result property="sort"       column="sort"        />
+        <result property="stock"      column="stock"       />
+        <result property="descs"      column="descs"       />
+        <result property="barCode"    column="bar_code"    />
+        <result property="tempId"     column="temp_id"     />
+        <result property="createBy"   column="create_by"   />
+        <result property="createTime" column="create_time" />
+        <result property="updateBy"   column="update_by"   />
+        <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="selectFsGuCoinGoodsVo">
+        select * from fs_gu_coin_goods
+    </sql>
+
+    <select id="selectFsGuCoinGoodsByGoodsId" parameterType="Long" resultMap="FsGuCoinGoodsResult">
+        <include refid="selectFsGuCoinGoodsVo"/>
+        where goods_id = #{goodsId}
+    </select>
+
+    <select id="selectFsGuCoinGoodsList" parameterType="com.fs.his.param.FsGuCoinGoodsParam" resultMap="FsGuCoinGoodsResult">
+        <include refid="selectFsGuCoinGoodsVo"/>
+        <where>
+            <if test="goodsName != null and goodsName != ''"> and goods_name like concat('%', #{goodsName}, '%')</if>
+            <if test="goodsType != null"> and goods_type = #{goodsType}</if>
+            <if test="status != null"> and status = #{status}</if>
+            <if test="goodsId != null"> and goods_id = #{goodsId}</if>
+        </where>
+        order by sort asc, goods_id desc
+    </select>
+
+    <insert id="insertFsGuCoinGoods" parameterType="FsGuCoinGoods" useGeneratedKeys="true" keyProperty="goodsId">
+        insert into fs_gu_coin_goods
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="imgUrl != null and imgUrl != ''">img_url,</if>
+            <if test="images != null">images,</if>
+            <if test="goodsName != null and goodsName != ''">goods_name,</if>
+            <if test="otPrice != null">ot_price,</if>
+            <if test="goodsType != null">goods_type,</if>
+            <if test="status != null">status,</if>
+            <if test="guCoin != null">gu_coin,</if>
+            <if test="cash != null">cash,</if>
+            <if test="sort != null">sort,</if>
+            <if test="stock != null">stock,</if>
+            <if test="descs != null">descs,</if>
+            <if test="barCode != null">bar_code,</if>
+            <if test="tempId != null">temp_id,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="imgUrl != null and imgUrl != ''">#{imgUrl},</if>
+            <if test="images != null">#{images},</if>
+            <if test="goodsName != null and goodsName != ''">#{goodsName},</if>
+            <if test="otPrice != null">#{otPrice},</if>
+            <if test="goodsType != null">#{goodsType},</if>
+            <if test="status != null">#{status},</if>
+            <if test="guCoin != null">#{guCoin},</if>
+            <if test="cash != null">#{cash},</if>
+            <if test="sort != null">#{sort},</if>
+            <if test="stock != null">#{stock},</if>
+            <if test="descs != null">#{descs},</if>
+            <if test="barCode != null">#{barCode},</if>
+            <if test="tempId != null">#{tempId},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateFsGuCoinGoods" parameterType="FsGuCoinGoods">
+        update fs_gu_coin_goods
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="imgUrl != null and imgUrl != ''">img_url = #{imgUrl},</if>
+            <if test="images != null">images = #{images},</if>
+            <if test="goodsName != null and goodsName != ''">goods_name = #{goodsName},</if>
+            <if test="otPrice != null">ot_price = #{otPrice},</if>
+            <if test="goodsType != null">goods_type = #{goodsType},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="guCoin != null">gu_coin = #{guCoin},</if>
+            <if test="cash != null">cash = #{cash},</if>
+            <if test="sort != null">sort = #{sort},</if>
+            <if test="stock != null">stock = #{stock},</if>
+            <if test="descs != null">descs = #{descs},</if>
+            <if test="barCode != null">bar_code = #{barCode},</if>
+            <if test="tempId != null">temp_id = #{tempId},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where goods_id = #{goodsId}
+    </update>
+
+    <delete id="deleteFsGuCoinGoodsByGoodsId" parameterType="Long">
+        delete from fs_gu_coin_goods where goods_id = #{goodsId}
+    </delete>
+
+    <delete id="deleteFsGuCoinGoodsByGoodsIds" parameterType="String">
+        delete from fs_gu_coin_goods where goods_id in
+        <foreach item="goodsId" collection="array" open="(" separator="," close=")">
+            #{goodsId}
+        </foreach>
+    </delete>
+</mapper>

+ 173 - 0
fs-service/src/main/resources/mapper/his/FsGuCoinOrderMapper.xml

@@ -0,0 +1,173 @@
+<?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.his.mapper.FsGuCoinOrderMapper">
+
+    <resultMap type="FsGuCoinOrder" id="FsGuCoinOrderResult">
+        <result property="orderId"         column="order_id"          />
+        <result property="orderCode"       column="order_code"        />
+        <result property="userId"          column="user_id"           />
+        <result property="userName"        column="user_name"         />
+        <result property="userPhone"       column="user_phone"        />
+        <result property="userAddress"     column="user_address"      />
+        <result property="itemJson"        column="item_json"         />
+        <result property="totalGuCoin"     column="total_gu_coin"     />
+        <result property="discountGuCoin"  column="discount_gu_coin"  />
+        <result property="guCoin"          column="gu_coin"           />
+        <result property="totalMoney"      column="total_money"       />
+        <result property="discountMoney"   column="discount_money"    />
+        <result property="payMoney"        column="pay_money"         />
+        <result property="deliveryMoney"   column="delivery_money"    />
+        <result property="isPay"           column="is_pay"            />
+        <result property="payTime"         column="pay_time"          />
+        <result property="payType"         column="pay_type"          />
+        <result property="status"          column="status"            />
+        <result property="barCode"         column="bar_code"          />
+        <result property="deliveryCode"    column="delivery_code"     />
+        <result property="deliveryName"    column="delivery_name"     />
+        <result property="deliverySn"      column="delivery_sn"       />
+        <result property="deliveryTime"    column="delivery_time"     />
+        <result property="qwUserId"        column="qw_user_id"        />
+        <result property="companyUserId"   column="company_user_id"   />
+        <result property="companyId"       column="company_id"        />
+        <result property="loginAccount"    column="login_account"     />
+        <result property="remark"          column="remark"            />
+        <result property="createTime"      column="create_time"       />
+        <result property="extendOrderId"   column="extend_order_id"   />
+    </resultMap>
+
+    <sql id="selectFsGuCoinOrderVo">
+        select * from fs_gu_coin_order
+    </sql>
+
+    <select id="selectFsGuCoinOrderByOrderId" parameterType="Long" resultMap="FsGuCoinOrderResult">
+        <include refid="selectFsGuCoinOrderVo"/>
+        where order_id = #{orderId}
+    </select>
+
+    <select id="selectFsGuCoinOrderByOrderCode" parameterType="String" resultMap="FsGuCoinOrderResult">
+        <include refid="selectFsGuCoinOrderVo"/>
+        where order_code = #{orderCode}
+    </select>
+
+    <select id="selectFsGuCoinOrderList" parameterType="com.fs.his.param.FsGuCoinOrderParam" resultMap="FsGuCoinOrderResult">
+        <include refid="selectFsGuCoinOrderVo"/>
+        <where>
+            <if test="orderCode != null and orderCode != ''"> and order_code = #{orderCode}</if>
+            <if test="userId != null"> and user_id = #{userId}</if>
+            <if test="userPhone != null and userPhone != ''"> and user_phone = #{userPhone}</if>
+            <if test="status != null"> and status = #{status}</if>
+            <if test="isPay != null"> and is_pay = #{isPay}</if>
+            <if test="companyId != null"> and company_id = #{companyId}</if>
+            <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectFsGuCoinOrderListU" parameterType="com.fs.his.param.FsGuCoinOrderListUParam" resultMap="FsGuCoinOrderResult">
+        <include refid="selectFsGuCoinOrderVo"/>
+        <where>
+            <if test="userId != null"> and user_id = #{userId}</if>
+            <if test="status != null and status!=0"> and status = #{status}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <insert id="insertFsGuCoinOrder" parameterType="FsGuCoinOrder" useGeneratedKeys="true" keyProperty="orderId">
+        insert into fs_gu_coin_order
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="orderCode != null and orderCode != ''">order_code,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="userName != null">user_name,</if>
+            <if test="userPhone != null">user_phone,</if>
+            <if test="userAddress != null">user_address,</if>
+            <if test="itemJson != null">item_json,</if>
+            <if test="totalGuCoin != null">total_gu_coin,</if>
+            <if test="discountGuCoin != null">discount_gu_coin,</if>
+            <if test="guCoin != null">gu_coin,</if>
+            <if test="totalMoney != null">total_money,</if>
+            <if test="discountMoney != null">discount_money,</if>
+            <if test="payMoney != null">pay_money,</if>
+            <if test="deliveryMoney != null">delivery_money,</if>
+            <if test="isPay != null">is_pay,</if>
+            <if test="payTime != null">pay_time,</if>
+            <if test="payType != null">pay_type,</if>
+            <if test="status != null">status,</if>
+            <if test="barCode != null">bar_code,</if>
+            <if test="deliveryCode != null">delivery_code,</if>
+            <if test="deliveryName != null">delivery_name,</if>
+            <if test="deliverySn != null">delivery_sn,</if>
+            <if test="deliveryTime != null">delivery_time,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="loginAccount != null">login_account,</if>
+            <if test="remark != null">remark,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="extendOrderId != null">extend_order_id,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="orderCode != null and orderCode != ''">#{orderCode},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="userName != null">#{userName},</if>
+            <if test="userPhone != null">#{userPhone},</if>
+            <if test="userAddress != null">#{userAddress},</if>
+            <if test="itemJson != null">#{itemJson},</if>
+            <if test="totalGuCoin != null">#{totalGuCoin},</if>
+            <if test="discountGuCoin != null">#{discountGuCoin},</if>
+            <if test="guCoin != null">#{guCoin},</if>
+            <if test="totalMoney != null">#{totalMoney},</if>
+            <if test="discountMoney != null">#{discountMoney},</if>
+            <if test="payMoney != null">#{payMoney},</if>
+            <if test="deliveryMoney != null">#{deliveryMoney},</if>
+            <if test="isPay != null">#{isPay},</if>
+            <if test="payTime != null">#{payTime},</if>
+            <if test="payType != null">#{payType},</if>
+            <if test="status != null">#{status},</if>
+            <if test="barCode != null">#{barCode},</if>
+            <if test="deliveryCode != null">#{deliveryCode},</if>
+            <if test="deliveryName != null">#{deliveryName},</if>
+            <if test="deliverySn != null">#{deliverySn},</if>
+            <if test="deliveryTime != null">#{deliveryTime},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="loginAccount != null">#{loginAccount},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="extendOrderId != null">#{extendOrderId},</if>
+        </trim>
+    </insert>
+
+    <update id="updateFsGuCoinOrder" parameterType="FsGuCoinOrder">
+        update fs_gu_coin_order
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userName != null">user_name = #{userName},</if>
+            <if test="userPhone != null">user_phone = #{userPhone},</if>
+            <if test="userAddress != null">user_address = #{userAddress},</if>
+            <if test="isPay != null">is_pay = #{isPay},</if>
+            <if test="payTime != null">pay_time = #{payTime},</if>
+            <if test="payType != null">pay_type = #{payType},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="deliveryCode != null">delivery_code = #{deliveryCode},</if>
+            <if test="deliveryName != null">delivery_name = #{deliveryName},</if>
+            <if test="deliverySn != null">delivery_sn = #{deliverySn},</if>
+            <if test="deliveryTime != null">delivery_time = #{deliveryTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="extendOrderId != null">extend_order_id = #{extendOrderId},</if>
+        </trim>
+        where order_id = #{orderId}
+    </update>
+
+    <delete id="deleteFsGuCoinOrderByOrderId" parameterType="Long">
+        delete from fs_gu_coin_order where order_id = #{orderId}
+    </delete>
+
+    <delete id="deleteFsGuCoinOrderByOrderIds" parameterType="String">
+        delete from fs_gu_coin_order where order_id in
+        <foreach item="orderId" collection="array" open="(" separator="," close=")">
+            #{orderId}
+        </foreach>
+    </delete>
+</mapper>

+ 99 - 0
fs-service/src/main/resources/mapper/his/FsUserGuCoinLogsMapper.xml

@@ -0,0 +1,99 @@
+<?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.his.mapper.FsUserGuCoinLogsMapper">
+
+    <resultMap type="FsUserGuCoinLogs" id="FsUserGuCoinLogsResult">
+        <result property="id"              column="id"               />
+        <result property="userId"          column="user_id"          />
+        <result property="logType"         column="log_type"         />
+        <result property="guCoin"          column="gu_coin"          />
+        <result property="balance"         column="balance"          />
+        <result property="businessId"      column="business_id"      />
+        <result property="consumeAmount"   column="consume_amount"   />
+        <result property="remark"          column="remark"           />
+        <result property="operatorId"      column="operator_id"      />
+        <result property="operatorName"    column="operator_name"    />
+        <result property="operatorSource"  column="operator_source"  />
+        <result property="nickName"        column="nick_name"        />
+        <result property="phone"           column="phone"            />
+        <result property="status"          column="status"           />
+        <result property="createTime"      column="create_time"      />
+    </resultMap>
+
+    <sql id="selectFsUserGuCoinLogsVo">
+        select * from fs_user_gu_coin_logs
+    </sql>
+
+    <select id="selectFsUserGuCoinLogsById" parameterType="Long" resultMap="FsUserGuCoinLogsResult">
+        <include refid="selectFsUserGuCoinLogsVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectFsUserGuCoinLogsList" parameterType="com.fs.his.param.FsUserGuCoinLogsParam" resultMap="FsUserGuCoinLogsResult">
+        <include refid="selectFsUserGuCoinLogsVo"/>
+        <where>
+            <if test="userId != null"> and user_id = #{userId}</if>
+            <if test="logType != null"> and log_type = #{logType}</if>
+            <if test="businessId != null and businessId != ''"> and business_id = #{businessId}</if>
+            <if test="operatorId != null"> and operator_id = #{operatorId}</if>
+            <if test="operatorSource != null"> and operator_source = #{operatorSource}</if>
+            <if test="status != null"> and status = #{status}</if>
+            <if test="phone != null and phone != ''"> and phone = #{phone}</if>
+            <if test="nickName != null and nickName != ''"> and nick_name like concat('%', #{nickName}, '%')</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectByUserIdAndLogTypeAndBusinessId" resultMap="FsUserGuCoinLogsResult">
+        select * from fs_user_gu_coin_logs
+        where user_id = #{userId} and log_type = #{logType} and business_id = #{businessId}
+          and status = 1
+    </select>
+
+    <insert id="insertFsUserGuCoinLogs" parameterType="FsUserGuCoinLogs" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_user_gu_coin_logs
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="logType != null">log_type,</if>
+            <if test="guCoin != null">gu_coin,</if>
+            <if test="balance != null">balance,</if>
+            <if test="businessId != null">business_id,</if>
+            <if test="consumeAmount != null">consume_amount,</if>
+            <if test="remark != null">remark,</if>
+            <if test="operatorId != null">operator_id,</if>
+            <if test="operatorName != null">operator_name,</if>
+            <if test="operatorSource != null">operator_source,</if>
+            <if test="nickName != null">nick_name,</if>
+            <if test="phone != null">phone,</if>
+            <if test="status != null">status,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="logType != null">#{logType},</if>
+            <if test="guCoin != null">#{guCoin},</if>
+            <if test="balance != null">#{balance},</if>
+            <if test="businessId != null">#{businessId},</if>
+            <if test="consumeAmount != null">#{consumeAmount},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="operatorId != null">#{operatorId},</if>
+            <if test="operatorName != null">#{operatorName},</if>
+            <if test="operatorSource != null">#{operatorSource},</if>
+            <if test="nickName != null">#{nickName},</if>
+            <if test="phone != null">#{phone},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createTime != null">#{createTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateFsUserGuCoinLogs" parameterType="FsUserGuCoinLogs">
+        update fs_user_gu_coin_logs
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="status != null">status = #{status},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+</mapper>

+ 7 - 0
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -780,6 +780,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         WHERE user_id = #{userId}
     </update>
 
+    <update id="addGuCoin">
+        UPDATE fs_user
+        SET gu_coin = gu_coin + #{guCoin},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+    </update>
+
     <update id="updateFsUser" parameterType="FsUser">
         update fs_user
         <trim prefix="SET" suffixOverrides=",">

+ 335 - 0
fs-user-app/src/main/java/com/fs/app/controller/GuCoinController.java

@@ -0,0 +1,335 @@
+package com.fs.app.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.fs.app.annotation.Login;
+import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.domain.FsGuCoinGoods;
+import com.fs.his.domain.FsGuCoinOrder;
+import com.fs.his.domain.FsUserGuCoinLogs;
+import com.fs.his.dto.ExpressInfoDTO;
+import com.fs.his.enums.GuCoinLogTypeEnum;
+import com.fs.his.enums.PaymentMethodEnum;
+import com.fs.his.enums.ShipperCodeEnum;
+import com.fs.his.param.FsGuCoinCartOrderCreateParam;
+import com.fs.his.param.FsGuCoinCartParam;
+import com.fs.his.param.FsGuCoinGoodsParam;
+import com.fs.his.param.FsGuCoinOrderComputeParam;
+import com.fs.his.param.FsGuCoinOrderCreateParam;
+import com.fs.his.param.FsGuCoinOrderDoPayParam;
+import com.fs.his.param.FsGuCoinOrderListUParam;
+import com.fs.his.param.FsUserGuCoinLogsParam;
+import com.fs.his.service.IFsExpressService;
+import com.fs.his.service.IFsGuCoinCartService;
+import com.fs.his.service.IFsGuCoinGoodsService;
+import com.fs.his.service.IFsGuCoinOrderService;
+import com.fs.his.service.IFsUserGuCoinLogsService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static com.fs.his.utils.PhoneUtil.decryptPhone;
+
+/**
+ * 谷币商城接口(用户端)
+ * 谷币:谷养正道商城专属积分,无有效期
+ *
+ * @author fs
+ * @date 2026-07-17
+ */
+@Slf4j
+@Api("谷币商城接口")
+@RestController
+@RequestMapping(value = "/app/guCoin")
+public class GuCoinController extends AppBaseController {
+
+    @Autowired
+    private IFsGuCoinGoodsService guCoinGoodsService;
+    @Autowired
+    private IFsGuCoinOrderService guCoinOrderService;
+    @Autowired
+    private IFsUserGuCoinLogsService userGuCoinLogsService;
+    @Autowired
+    private IFsExpressService expressService;
+    @Autowired
+    private IFsGuCoinCartService guCoinCartService;
+
+    /**
+     * 获取谷币商城商品列表
+     */
+    @ApiOperation("获取谷币商城商品列表")
+    @GetMapping("/getGoodsList")
+    public R getGoodsList(FsGuCoinGoodsParam param,
+                          @RequestParam(defaultValue = "1") Integer pageNum,
+                          @RequestParam(defaultValue = "10") Integer pageSize) {
+        // 仅展示上架商品
+        param.setStatus(1L);
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsGuCoinGoods> list = guCoinGoodsService.selectFsGuCoinGoodsList(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
+    /**
+     * 获取谷币商城商品详情
+     */
+    @Login
+    @ApiOperation("获取谷币商城商品详情")
+    @GetMapping("/getGoodsById")
+    public R getGoodsById(@RequestParam("goodsId") Long goodsId) {
+        FsGuCoinGoods goods = guCoinGoodsService.selectFsGuCoinGoodsByGoodsId(goodsId);
+        if (goods == null) {
+            return R.error("商品不存在");
+        }
+        return R.ok().put("data", goods);
+    }
+
+    /**
+     * 计算订单金额(谷币+现金+运费)
+     */
+    @Login
+    @ApiOperation("计算订单")
+    @PostMapping("/compute")
+    public R compute(@Validated @RequestBody FsGuCoinOrderComputeParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return guCoinOrderService.computeOrder(param);
+    }
+
+    /**
+     * 创建订单
+     */
+    @Login
+    @ApiOperation("创建订单")
+    @PostMapping("/createOrder")
+    @RepeatSubmit
+    public R createOrder(@Validated @RequestBody FsGuCoinOrderCreateParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return guCoinOrderService.createOrder(param);
+    }
+
+    /**
+     * 取消订单
+     */
+    @Login
+    @ApiOperation("取消订单")
+    @PostMapping("/cannelOrder")
+    @RepeatSubmit
+    public R cannelOrder(@RequestBody FsGuCoinOrderDoPayParam param) {
+        return guCoinOrderService.cannelOrder(param.getOrderId());
+    }
+
+    /**
+     * 微信小程序支付
+     */
+    @Login
+    @RepeatSubmit
+    @ApiOperation("微信小程序支付")
+    @PostMapping("/payment")
+    public R payment(@Validated @RequestBody FsGuCoinOrderDoPayParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return guCoinOrderService.payment(param, PaymentMethodEnum.MINIAPP_WECHAT);
+    }
+
+    /**
+     * 支付宝支付
+     */
+    @Login
+    @RepeatSubmit
+    @ApiOperation("支付宝支付")
+    @PostMapping("/aliPayment")
+    public R aliPayment(@Validated @RequestBody FsGuCoinOrderDoPayParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return guCoinOrderService.payment(param, PaymentMethodEnum.ALIPAY);
+    }
+
+    /**
+     * H5微信支付
+     */
+    @Login
+    @RepeatSubmit
+    @ApiOperation("H5微信支付")
+    @PostMapping("/H5WxPayment")
+    public R h5WxPayment(@Validated @RequestBody FsGuCoinOrderDoPayParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return guCoinOrderService.payment(param, PaymentMethodEnum.H5_WECHAT);
+    }
+
+    /**
+     * H5支付宝支付
+     */
+    @Login
+    @RepeatSubmit
+    @ApiOperation("H5支付宝支付")
+    @PostMapping("/H5AliPayment")
+    public R h5AliPayment(@Validated @RequestBody FsGuCoinOrderDoPayParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return guCoinOrderService.payment(param, PaymentMethodEnum.H5_ALIPAY);
+    }
+
+    /**
+     * 获取谷币订单列表
+     */
+    @Login
+    @ApiOperation("获取谷币订单列表")
+    @GetMapping("/getOrderList")
+    public R getOrderList(FsGuCoinOrderListUParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        PageHelper.startPage(param.getPageNum() == null ? 1 : param.getPageNum(),
+                param.getPageSize() == null ? 10 : param.getPageSize());
+        List<FsGuCoinOrder> list = guCoinOrderService.selectFsGuCoinOrderListU(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
+    /**
+     * 获取谷币订单详情
+     */
+    @Login
+    @ApiOperation("获取谷币订单详情")
+    @GetMapping("/getOrderById")
+    public R getOrderById(@RequestParam("orderId") Long orderId) {
+        FsGuCoinOrder order = guCoinOrderService.selectFsGuCoinOrderByOrderId(orderId);
+        if (Objects.isNull(order)) {
+            return R.error("订单不存在");
+        }
+        return R.ok().put("data", order);
+    }
+
+    /**
+     * 获取会员谷币记录列表
+     */
+    @Login
+    @ApiOperation("获取会员谷币记录列表")
+    @GetMapping("/getGuCoinLogsList")
+    public R getGuCoinLogsList(FsUserGuCoinLogsParam param,
+                               @RequestParam(defaultValue = "1") Integer pageNum,
+                               @RequestParam(defaultValue = "10") Integer pageSize) {
+        param.setUserId(Long.parseLong(getUserId()));
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsUserGuCoinLogs> list = userGuCoinLogsService.selectFsUserGuCoinLogsList(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
+    /**
+     * 添加或修改购物车
+     */
+    @Login
+    @ApiOperation("添加或修改购物车")
+    @PostMapping("/addOrUpdateCart")
+    public R addOrUpdateCart(@Validated @RequestBody FsGuCoinCartParam param) {
+        guCoinCartService.addOrUpdateCart(Long.parseLong(getUserId()), param.getGoodsId(), param.getCartNum(), param.getIsCart());
+        return R.ok();
+    }
+
+    /**
+     * 删除购物车
+     */
+    @Login
+    @ApiOperation("删除购物车")
+    @PostMapping("/delCart")
+    public R delCart(@RequestBody List<Long> ids) {
+        if (Objects.isNull(ids) || ids.isEmpty()) {
+            return R.error("ID不能为空");
+        }
+        guCoinCartService.delCartByIds(Long.parseLong(getUserId()), ids);
+        return R.ok();
+    }
+
+    /**
+     * 获取购物车商品数量
+     */
+    @Login
+    @ApiOperation("获取购物车商品数量")
+    @GetMapping("/getCartCount")
+    public R getCartCount(@RequestParam(required = false) Long goodsId) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("userId", Long.parseLong(getUserId()));
+        params.put("goodsId", goodsId);
+        return R.ok().put("data", guCoinCartService.getCartCountByMap(params));
+    }
+
+    /**
+     * 获取购物车列表
+     */
+    @Login
+    @ApiOperation("获取购物车列表")
+    @GetMapping("/getCarts")
+    public R getCarts() {
+        return R.ok().put("carts", guCoinCartService.getCartsByUserId(Long.parseLong(getUserId())));
+    }
+
+    /**
+     * 根据ID集合查询购物车
+     */
+    @Login
+    @ApiOperation("根据ID集合查询购物车")
+    @PostMapping("/getCartByIds")
+    public R getCartByIds(@RequestBody List<Long> ids) {
+        return R.ok().put("data", guCoinCartService.getCartByIds(Long.parseLong(getUserId()), ids));
+    }
+
+    /**
+     * 创建购物车订单
+     */
+    @Login
+    @ApiOperation("创建购物车订单")
+    @PostMapping("/createCartOrder")
+    @RepeatSubmit
+    public R createCartOrder(@Validated @RequestBody FsGuCoinCartOrderCreateParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return guCoinOrderService.createCartOrder(param);
+    }
+
+    /**
+     * 物流信息
+     */
+    @Login
+    @ApiOperation("物流信息")
+    @GetMapping(value = "/getExpress/{orderId}")
+    public R getExpress(@PathVariable Long orderId) {
+        FsGuCoinOrder order = guCoinOrderService.selectFsGuCoinOrderByOrderId(orderId);
+        if (Objects.isNull(order)) {
+            return R.error("订单不存在");
+        }
+        ExpressInfoDTO expressInfoDTO = null;
+        if (StringUtils.isNotEmpty(order.getDeliverySn())) {
+            String lastFourNumber = "";
+            if (ShipperCodeEnum.SF.getValue().equals(order.getDeliveryCode())) {
+                String phone = order.getUserPhone();
+                if (phone != null && phone.length() > 11) {
+                    phone = decryptPhone(phone);
+                }
+                lastFourNumber = StrUtil.sub(phone, phone.length(), -4);
+            }
+            expressInfoDTO = expressService.getExpressInfo(order.getOrderCode(), order.getDeliveryCode(), order.getDeliverySn(), lastFourNumber);
+        }
+        return R.ok().put("data", expressInfoDTO).put("deliveryName", order.getDeliveryName());
+    }
+
+    /**
+     * 谷币记录类型字典
+     * 将 fs_user_gu_coin_logs.log_type 数值映射为中文说明
+     */
+    @ApiOperation("谷币记录类型字典")
+    @GetMapping("/getLogTypeDict")
+    public R getLogTypeDict() {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (GuCoinLogTypeEnum e : GuCoinLogTypeEnum.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("value", e.getValue());
+            item.put("label", e.getDesc());
+            list.add(item);
+        }
+        return R.ok().put("data", list);
+    }
+}

+ 5 - 0
fs-user-app/src/main/java/com/fs/app/controller/HuifuPayController.java

@@ -39,6 +39,8 @@ public class HuifuPayController {
     private IFsStorePaymentService storePaymentService;
     @Autowired
     private IFsRewardGoodsOrderService rewardGoodsOrderService;
+    @Autowired
+    private IFsGuCoinOrderService guCoinOrderService;
 
     org.slf4j.Logger logger= LoggerFactory.getLogger(getClass());
     @Autowired
@@ -118,6 +120,9 @@ public class HuifuPayController {
                 case "reward":
                     rewardGoodsOrderService.payConfirm("",orderId[1],huiFuResult.getHf_seq_id(),"",1,huiFuResult.getOut_trans_id(),huiFuResult.getParty_order_id());
                     break;
+                case "gucoin":
+                    guCoinOrderService.payConfirm("",orderId[1],huiFuResult.getHf_seq_id(),"",1,huiFuResult.getOut_trans_id(),huiFuResult.getParty_order_id());
+                    break;
             }
         }
 

+ 1 - 1
fs-watch/src/main/java/com/fs/FsWatchApplication.java

@@ -15,7 +15,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 @EnableTransactionManagement
 @EnableAsync
 @EnableSwaggerBootstrapUI
-//@EnableScheduling // 启用定时任务
+@EnableScheduling // 启用定时任务
 public class FsWatchApplication
 {
     public static void main(String[] args)