Преглед на файлове

订单发货权限,订单审核 公开课流量统计

wangxy преди 2 седмици
родител
ревизия
0f974f13c4
променени са 50 файла, в които са добавени 2218 реда и са изтрити 78 реда
  1. 7 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java
  2. 37 0
      fs-admin/src/main/java/com/fs/his/controller/AppOperationReportController.java
  3. 68 0
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderFinanceAuditController.java
  4. 71 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  5. 128 0
      fs-admin/src/main/java/com/fs/web/controller/tool/TestController.java
  6. 46 10
      fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java
  7. 29 0
      fs-service/src/main/java/com/fs/course/domain/FsPublicCourseTrafficLog.java
  8. 42 0
      fs-service/src/main/java/com/fs/course/mapper/FsPublicCourseTrafficLogMapper.java
  9. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  10. 3 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  11. 9 1
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  12. 80 7
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  13. 30 0
      fs-service/src/main/java/com/fs/his/domain/FsAppActiveUserDaily.java
  14. 14 14
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  15. 6 0
      fs-service/src/main/java/com/fs/his/domain/FsStore.java
  16. 74 0
      fs-service/src/main/java/com/fs/his/domain/FsStoreOrderFinanceAudit.java
  17. 12 0
      fs-service/src/main/java/com/fs/his/domain/FsStoreProduct.java
  18. 40 0
      fs-service/src/main/java/com/fs/his/enums/ChainBrandEnum.java
  19. 33 0
      fs-service/src/main/java/com/fs/his/enums/ProductSourceTypeEnum.java
  20. 101 0
      fs-service/src/main/java/com/fs/his/mapper/AppOperationReportMapper.java
  21. 34 0
      fs-service/src/main/java/com/fs/his/mapper/FsAppActiveUserDailyMapper.java
  22. 26 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderFinanceAuditMapper.java
  23. 7 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreProductMapper.java
  24. 19 0
      fs-service/src/main/java/com/fs/his/param/AppOperationReportParam.java
  25. 31 0
      fs-service/src/main/java/com/fs/his/param/FsOrderFinanceAuditApplyParam.java
  26. 20 0
      fs-service/src/main/java/com/fs/his/param/FsOrderFinanceAuditParam.java
  27. 8 0
      fs-service/src/main/java/com/fs/his/param/FsStoreProductAddEditParam.java
  28. 17 0
      fs-service/src/main/java/com/fs/his/service/IAppOperationReportService.java
  29. 30 0
      fs-service/src/main/java/com/fs/his/service/IFsAppActiveUserDailyService.java
  30. 0 7
      fs-service/src/main/java/com/fs/his/service/IFsCouponService.java
  31. 27 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderFinanceAuditService.java
  32. 14 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  33. 183 0
      fs-service/src/main/java/com/fs/his/service/impl/AppOperationReportServiceImpl.java
  34. 103 0
      fs-service/src/main/java/com/fs/his/service/impl/FsAppActiveUserDailyServiceImpl.java
  35. 3 10
      fs-service/src/main/java/com/fs/his/service/impl/FsCouponServiceImpl.java
  36. 8 4
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  37. 125 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderFinanceAuditServiceImpl.java
  38. 160 12
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  39. 65 0
      fs-service/src/main/java/com/fs/his/vo/AppOperationReportVO.java
  40. 10 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderListVO.java
  41. 3 0
      fs-service/src/main/resources/application-druid-hdt.yml
  42. 1 2
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  43. 102 0
      fs-service/src/main/resources/mapper/course/FsPublicCourseTrafficLogMapper.xml
  44. 193 0
      fs-service/src/main/resources/mapper/his/AppOperationReportMapper.xml
  45. 34 0
      fs-service/src/main/resources/mapper/his/FsAppActiveUserDailyMapper.xml
  46. 2 9
      fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml
  47. 5 1
      fs-service/src/main/resources/mapper/his/FsStoreMapper.xml
  48. 132 0
      fs-service/src/main/resources/mapper/his/FsStoreOrderFinanceAuditMapper.xml
  49. 11 1
      fs-service/src/main/resources/mapper/his/FsStoreProductMapper.xml
  50. 12 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

+ 7 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java

@@ -158,6 +158,13 @@ public class FsCourseRedPacketLogController extends BaseController
         return R.ok().put("list", optionsVOS);
     }
 
+    @GetMapping("/publicCourseList")
+    public R publicCourseList()
+    {
+        List<OptionsVO> optionsVOS = fsUserCourseMapper.selectFsUserCoursePublicList();
+        return R.ok().put("list", optionsVOS);
+    }
+
     @GetMapping(value = "/videoList/{id}")
     public R videoList(@PathVariable("id") Long id)
     {

+ 37 - 0
fs-admin/src/main/java/com/fs/his/controller/AppOperationReportController.java

@@ -0,0 +1,37 @@
+package com.fs.his.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.his.param.AppOperationReportParam;
+import com.fs.his.service.IAppOperationReportService;
+import com.fs.his.vo.AppOperationReportVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * App运营报表Controller
+ */
+@Api("App运营报表")
+@RestController
+@RequestMapping("/his/appOperationReport")
+public class AppOperationReportController extends BaseController {
+
+    @Autowired
+    private IAppOperationReportService appOperationReportService;
+
+    /**
+     * 查询月度运营报表
+     * @param param 查询参数(year、month、retentionDays)
+     * @return 月度运营报表数据
+     */
+    @ApiOperation("月报表查询")
+    @PreAuthorize("@ss.hasPermi('his:appOperationReport:list')")
+    @GetMapping("/monthReport")
+    public AjaxResult monthReport(AppOperationReportParam param) {
+        AppOperationReportVO vo = appOperationReportService.getMonthReport(param);
+        return AjaxResult.success(vo);
+    }
+}

+ 68 - 0
fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderFinanceAuditController.java

@@ -0,0 +1,68 @@
+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.FsStoreOrderFinanceAudit;
+import com.fs.his.param.FsOrderFinanceAuditApplyParam;
+import com.fs.his.param.FsOrderFinanceAuditParam;
+import com.fs.his.service.IFsStoreOrderFinanceAuditService;
+import io.swagger.annotations.ApiOperation;
+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;
+
+@RestController
+@RequestMapping("/his/orderFinanceAudit")
+public class FsStoreOrderFinanceAuditController extends BaseController {
+
+    @Autowired
+    private IFsStoreOrderFinanceAuditService fsStoreOrderFinanceAuditService;
+
+    @PreAuthorize("@ss.hasPermi('his:orderFinanceAudit:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit) {
+        startPage();
+        List<FsStoreOrderFinanceAudit> list = fsStoreOrderFinanceAuditService.selectFsStoreOrderFinanceAuditList(fsStoreOrderFinanceAudit);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:orderFinanceAudit:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return AjaxResult.success(fsStoreOrderFinanceAuditService.selectFsStoreOrderFinanceAuditById(id));
+    }
+
+    @ApiOperation("申请财务审核")
+    @PreAuthorize("@ss.hasPermi('his:orderFinanceAudit:apply')")
+    @Log(title = "申请财务审核", businessType = BusinessType.INSERT)
+    @PostMapping("/apply")
+    public AjaxResult apply(@Validated @RequestBody FsOrderFinanceAuditApplyParam param) {
+        Long userId = SecurityUtils.getUserId();
+        String userName = SecurityUtils.getUsername();
+        return toAjax(fsStoreOrderFinanceAuditService.applyFinanceAudit(param, userId, userName));
+    }
+
+    @ApiOperation("财务审核")
+    @PreAuthorize("@ss.hasPermi('his:orderFinanceAudit:audit')")
+    @Log(title = "财务审核", businessType = BusinessType.UPDATE)
+    @PostMapping("/audit")
+    public AjaxResult audit(@Validated @RequestBody FsOrderFinanceAuditParam param) {
+        Long userId = SecurityUtils.getUserId();
+        String userName = SecurityUtils.getUsername();
+        return toAjax(fsStoreOrderFinanceAuditService.auditFinanceAudit(param, userId, userName));
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:orderFinanceAudit:remove')")
+    @Log(title = "删除财务审核记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(fsStoreOrderFinanceAuditService.deleteFsStoreOrderFinanceAuditByIds(ids));
+    }
+}

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

@@ -192,6 +192,9 @@ public class Task {
     @Autowired
     public RedisTemplate redisTemplate;
 
+    @Autowired
+    private com.fs.his.service.IFsAppActiveUserDailyService fsAppActiveUserDailyService;
+
     @Autowired
     private ICompanyUserService userService;
 
@@ -1860,4 +1863,72 @@ public class Task {
         fsExternalOrderService.pushToJstBatch(orderIds);
     }
 
+    /**
+     * 初始化产品来源类型和连锁品牌
+     * 根据店铺名称判断:包含红德堂、优身、乘济、诚质的为大包品,否则为自库品
+     */
+    public void initProductSourceType() {
+        log.info("开始初始化产品来源类型和连锁品牌");
+        try {
+            List<FsStoreProduct> products = fsStoreProductMapper.selectFsStoreProductList(new FsStoreProduct());
+            int bigPackageCount = 0;
+            int selfStoreCount = 0;
+            
+            for (FsStoreProduct product : products) {
+                if (product.getStoreId() == null) {
+                    continue;
+                }
+                
+                FsStore store = fsStoreMapper.selectFsStoreByStoreId(product.getStoreId());
+                if (store == null || store.getStoreName() == null) {
+                    continue;
+                }
+                
+                String storeName = store.getStoreName();
+                String chainBrand = null;
+                Integer productSourceType = 2;
+                
+                if (storeName.contains("红德堂")) {
+                    chainBrand =store.getChainBrands();
+                    productSourceType = 1;
+                    bigPackageCount++;
+                } else if (storeName.contains("优身")) {
+                    chainBrand =store.getChainBrands();
+                    productSourceType = 1;
+                    bigPackageCount++;
+                } else if (storeName.contains("乘济")) {
+                    chainBrand =store.getChainBrands();
+                    productSourceType = 1;
+                    bigPackageCount++;
+                } else if (storeName.contains("诚质")) {
+                    chainBrand =store.getChainBrands();
+                    productSourceType = 1;
+                    bigPackageCount++;
+                } else {
+                    selfStoreCount++;
+                }
+                
+                fsStoreProductMapper.updateProductSourceType(product.getProductId(), productSourceType, chainBrand);
+            }
+            
+            log.info("产品来源类型初始化完成,大包品: {},自库品: {}", bigPackageCount, selfStoreCount);
+        } catch (Exception e) {
+            log.error("初始化产品来源类型失败: {}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 同步Redis活跃用户到数据库
+     * 每小时执行一次
+     */
+    public void syncAppActiveUserFromRedis() {
+        log.info("开始同步Redis活跃用户到数据库");
+        try {
+            fsAppActiveUserDailyService.syncActiveUserFromRedis();
+            log.info("同步Redis活跃用户到数据库完成");
+        } catch (Exception e) {
+            log.error("同步Redis活跃用户到数据库失败: {}", e.getMessage(), e);
+        }
+    }
+
 }

+ 128 - 0
fs-admin/src/main/java/com/fs/web/controller/tool/TestController.java

@@ -4,11 +4,19 @@ import java.text.ParseException;
 import java.util.*;
 
 import com.fs.common.core.domain.R;
+import com.fs.his.domain.FsStore;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.domain.FsStoreOrderItem;
+import com.fs.his.domain.FsStoreProduct;
+import com.fs.his.enums.ProductSourceTypeEnum;
+import com.fs.his.mapper.FsStoreOrderItemMapper;
 import com.fs.his.mapper.FsStoreOrderMapper;
+import com.fs.his.mapper.FsStoreProductMapper;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.his.service.IFsStoreProductService;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.task.Task;
+import com.fs.his.mapper.FsStoreMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.DeleteMapping;
@@ -59,6 +67,15 @@ public class TestController extends BaseController {
     @Autowired
     private Task task;
 
+    @Autowired
+    private FsStoreMapper fsStoreMapper;
+
+    @Autowired
+    private FsStoreProductMapper fsStoreProductMapper;
+
+    @Autowired
+    private FsStoreOrderItemMapper fsStoreOrderItemMapper;
+
     @ApiOperation("获取用户列表")
     @GetMapping("/list")
     public AjaxResult userList() {
@@ -97,6 +114,117 @@ public class TestController extends BaseController {
         return R.ok().put("updateCount", count);
     }
 
+    /**
+     * 测试订单发货权限校验
+     */
+    @GetMapping("/checkDeliveryPermission/{orderId}")
+    public R checkDeliveryPermission(@PathVariable Long orderId) {
+        FsStoreOrder order = fsStoreOrderMapper.selectFsStoreOrderByOrderId(orderId);
+        if (order == null) {
+            return R.error("订单不存在");
+        }
+        
+        if (order.getStoreId() == null) {
+            return R.error("订单店铺ID为空");
+        }
+        
+        FsStore store = fsStoreMapper.selectFsStoreByStoreId(order.getStoreId());
+        if (store == null) {
+            return R.error("店铺不存在");
+        }
+        
+        FsStoreOrderItem queryItem = new FsStoreOrderItem();
+        queryItem.setOrderId(orderId);
+        List<FsStoreOrderItem> orderItems = fsStoreOrderItemMapper.selectFsStoreOrderItemList(queryItem);
+        
+        String result = checkDeliveryPermission(store, orderItems);
+        
+        Map<String, Object> data = new HashMap<>();
+        data.put("orderCode", order.getOrderCode());
+        data.put("storeId", store.getStoreId());
+        data.put("storeName", store.getStoreName());
+        data.put("chainBrands", store.getChainBrands());
+        data.put("checkResult", result);
+        data.put("canDelivery", result == null);
+        
+        if (orderItems != null && !orderItems.isEmpty()) {
+            List<Map<String, Object>> productInfos = new ArrayList<>();
+            for (FsStoreOrderItem item : orderItems) {
+                FsStoreProduct product = fsStoreProductMapper.selectFsStoreProductByProductId(item.getProductId());
+                if (product != null) {
+                    Map<String, Object> productInfo = new HashMap<>();
+                    productInfo.put("productId", product.getProductId());
+                    productInfo.put("productName", product.getProductName());
+                    productInfo.put("productSourceType", product.getProductSourceType());
+                    productInfo.put("productSourceTypeName", getProductSourceTypeName(product.getProductSourceType()));
+                    productInfo.put("chainBrand", product.getChainBrand());
+                    productInfo.put("productStoreId", product.getStoreId());
+                    productInfos.add(productInfo);
+                }
+            }
+            data.put("products", productInfos);
+        }
+        
+        return R.ok().put("data", data);
+    }
+    
+    private String checkDeliveryPermission(FsStore store, List<FsStoreOrderItem> orderItems) {
+        if (orderItems == null || orderItems.isEmpty()) {
+            return null;
+        }
+        
+        String storeChainBrands = store.getChainBrands();
+        Set<String> storeAllowedBrands = new HashSet<>();
+        if (storeChainBrands != null && !storeChainBrands.trim().isEmpty()) {
+            String[] brands = storeChainBrands.split(",");
+            for (String brand : brands) {
+                storeAllowedBrands.add(brand.trim());
+            }
+        }
+        
+        for (FsStoreOrderItem item : orderItems) {
+            FsStoreProduct product = fsStoreProductMapper.selectFsStoreProductByProductId(item.getProductId());
+            if (product == null) {
+                continue;
+            }
+            
+            Integer productSourceType = product.getProductSourceType();
+            if (productSourceType == null) {
+                productSourceType = ProductSourceTypeEnum.SELF_STORE.getCode();
+            }
+            
+            if (ProductSourceTypeEnum.BIG_PACKAGE.getCode().equals(productSourceType)) {
+                String productChainBrand = product.getChainBrand();
+                if (productChainBrand == null || productChainBrand.trim().isEmpty()) {
+                    return "商品【" + product.getProductName() + "】为大包品但未配置连锁品牌";
+                }
+                if (!storeAllowedBrands.contains(productChainBrand)) {
+                    return "店铺无权发货大包品【" + product.getProductName() + "】,连锁品牌:" + productChainBrand;
+                }
+            } else if (ProductSourceTypeEnum.SELF_STORE.getCode().equals(productSourceType)) {
+                if (!store.getStoreId().equals(product.getStoreId())) {
+                    return "商品【" + product.getProductName() + "】为自库品,不属于当前店铺";
+                }
+            }
+        }
+        
+        return null;
+    }
+    
+    private String getProductSourceTypeName(Integer productSourceType) {
+        if (productSourceType == null) {
+            return "自库品(默认)";
+        }
+        if (productSourceType == 1) {
+            return "大包品";
+        } else if (productSourceType == 2) {
+            return "自库品";
+        } else if (productSourceType == 3) {
+            return "免费品";
+        }
+        return "未知";
+    }
+
     @ApiOperation("获取用户详细")
     @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path")
     @GetMapping("/{userId}")

+ 46 - 10
fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java

@@ -24,6 +24,7 @@ import com.fs.his.enums.ShipperCodeEnum;
 import com.fs.his.param.*;
 import com.fs.his.service.IFsExportTaskService;
 import com.fs.his.service.IFsExpressService;
+import com.fs.his.service.IFsStoreOrderFinanceAuditService;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.utils.PhoneUtil;
@@ -435,23 +436,58 @@ public class FsStoreOrderController extends BaseController
     }
 
     /**
-     * 订单审核
+     * 订单审核申请
      */
-    @Log(title = "订单财务审核", businessType = BusinessType.INSERT)
+    @Log(title = "订单财务审核申请", businessType = BusinessType.INSERT)
     @PreAuthorize("@ss.hasPermi('his:storeOrder:approveOrder')")
     @PostMapping("/approveOrder")
-    public AjaxResult approveOrder(@RequestBody List<Long> orderIds)
+    public AjaxResult approveOrder(@RequestBody FsOrderFinanceAuditApplyParam param)
     {
-        return toAjax(fsStoreOrderService.approveOrder(orderIds));
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        Long userId = loginUser.getUser().getUserId();
+        String userName = loginUser.getUser().getUserName();
+        return toAjax(fsStoreOrderService.applyFinanceAudit(
+                param.getOrderIds(), 
+                param.getAuditType(),
+                param.getNewTotalPrice(),
+                param.getNewPayPrice(),
+                param.getPriceChangeReason(),
+                param.getVoucherImages(),
+                param.getVoucherRemark(),
+                userId, 
+                userName));
     }
+
+    @Autowired
+    private IFsStoreOrderFinanceAuditService fsStoreOrderFinanceAuditService;
+
     /**
-     * 改价
+     * 财务审核列表
      */
-    @PreAuthorize("@ss.hasPermi('his:storeOrder:price')")
-    @PutMapping("/updateMoney")
-    public AjaxResult updateMoney(@RequestBody FsStoreOrder fsStoreOrder)
-    {
-        return toAjax(fsStoreOrderService.updateMoney(fsStoreOrder));
+    @PreAuthorize("@ss.hasPermi('his:storeOrder:financeAuditList')")
+    @GetMapping("/financeAuditList")
+    public TableDataInfo financeAuditList(FsStoreOrderFinanceAudit query) {
+        startPage();
+        List<FsStoreOrderFinanceAudit> list = fsStoreOrderFinanceAuditService.selectFsStoreOrderFinanceAuditList(query);
+        return getDataTable(list);
+    }
+
+    /**
+     * 财务审核
+     */
+    @Log(title = "财务审核", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('his:storeOrder:financeAudit')")
+    @PostMapping("/financeAudit")
+    public AjaxResult financeAudit(@RequestBody FsOrderFinanceAuditParam param) {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        Long userId = loginUser.getUser().getUserId();
+        String userName = loginUser.getUser().getUserName();
+        return toAjax(fsStoreOrderService.auditFinanceAudit(
+                param.getId(),
+                param.getAuditStatus(),
+                param.getAuditRemark(),
+                userId,
+                userName));
     }
 
     @GetMapping("/getCustomerOrderList")

+ 29 - 0
fs-service/src/main/java/com/fs/course/domain/FsPublicCourseTrafficLog.java

@@ -0,0 +1,29 @@
+package com.fs.course.domain;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 公开课流量记录对象 fs_public_course_traffic_log
+ */
+@Data
+public class FsPublicCourseTrafficLog extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long logId;
+
+    private String uuId;
+
+    private Long userId;
+
+    private Long courseId;
+
+    private Long videoId;
+
+    private Long internetTraffic;
+
+    private Integer status;
+
+    private  Long projectId;
+}

+ 42 - 0
fs-service/src/main/java/com/fs/course/mapper/FsPublicCourseTrafficLogMapper.java

@@ -0,0 +1,42 @@
+package com.fs.course.mapper;
+
+import com.fs.course.domain.FsPublicCourseTrafficLog;
+import com.fs.course.param.FsCourseTrafficLogParam;
+import com.fs.course.vo.FsCourseTrafficLogListVO;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 公开课流量记录Mapper接口
+ */
+@Repository
+public interface FsPublicCourseTrafficLogMapper {
+
+    FsPublicCourseTrafficLog selectFsPublicCourseTrafficLogByLogId(Long logId);
+
+    int insertFsPublicCourseTrafficLog(FsPublicCourseTrafficLog fsPublicCourseTrafficLog);
+
+    int updateFsPublicCourseTrafficLog(FsPublicCourseTrafficLog fsPublicCourseTrafficLog);
+
+    @Select("select * from fs_public_course_traffic_log where uu_id = #{uuId}")
+    FsPublicCourseTrafficLog selectFsPublicCourseTrafficLogByUuId(@Param("uuId") String uuId);
+
+    void insertOrUpdateTrafficLog(FsPublicCourseTrafficLog trafficLog);
+
+    @Select("SELECT IFNULL(SUM(internet_traffic), 0) FROM fs_public_course_traffic_log " +
+            "WHERE DATE(create_time) = DATE(CURDATE()) AND company_id = #{companyId}")
+    Long getTodayTrafficLogCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT IFNULL(SUM(internet_traffic), 0) FROM fs_public_course_traffic_log " +
+            "WHERE DATE(create_time) = DATE(CURDATE() - INTERVAL 1 DAY) AND company_id = #{companyId}")
+    Long getYesterdayTrafficLogCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT IFNULL(SUM(internet_traffic), 0) FROM fs_public_course_traffic_log " +
+            "WHERE YEAR(create_time) = YEAR(CURDATE()) AND MONTH(create_time) = MONTH(CURDATE()) AND company_id = #{companyId}")
+    Long getMonthTrafficLogCompanyId(@Param("companyId") Long companyId);
+
+    List<FsCourseTrafficLogListVO> selectTrafficNew(FsCourseTrafficLogParam param);
+}

+ 3 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java

@@ -236,6 +236,9 @@ public interface FsUserCourseMapper
     @Select("select course_id dict_value, course_name dict_label,img_url dict_imgUrl  from fs_user_course where is_del = 0 and is_private = 1 ")
     List<OptionsVO> selectFsUserCourseAllList();
 
+    @Select("select course_id dict_value, course_name dict_label,img_url dict_imgUrl  from fs_user_course where is_del = 0 and is_private = 0 ")
+    List<OptionsVO> selectFsUserCoursePublicList();
+
     @Select("select course_id dict_value, course_name dict_label,img_url dict_imgUrl  from fs_user_course where is_del = 0 and is_private = 1" +
             " and find_in_set(#{companyId},company_ids) ORDER BY sort ASC, course_id DESC ")
     List<OptionsVO> selectFsUserCourseByCompany(@Param("companyId") Long companyId);

+ 3 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -110,6 +110,9 @@ public interface IFsUserCourseVideoService
 
     R getInternetTraffic(FsUserCourseVideoFinishUParam param);
 
+    //公开课流量统计
+    R getPublicCourseInternetTraffic(FsUserCourseVideoFinishUParam param);
+
     R getIntegralByH5Video(FsUserCourseVideoFinishUParam param);
 //
     R sendReward(FsCourseSendRewardUParam param);

+ 9 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

@@ -27,6 +27,7 @@ import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsCourseTrafficLogMapper;
 import com.fs.course.domain.FsCourseTrafficLog;
 import com.fs.course.service.IFsCourseTrafficLogService;
+import com.fs.course.mapper.FsPublicCourseTrafficLogMapper;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -42,6 +43,8 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
     @Autowired
     private FsCourseTrafficLogMapper fsCourseTrafficLogMapper;
     @Autowired
+    private FsPublicCourseTrafficLogMapper fsPublicCourseTrafficLogMapper;
+    @Autowired
     private ISysConfigService iSysConfigService;
     @Autowired
     private ICompanyCacheService companyCacheService;
@@ -188,7 +191,12 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
         if (ObjectUtils.isNotEmpty(param.getTabType())&&param.getTabType().equals("common")){
             param.setCommon(param.getTabType());
         }
-        List<FsCourseTrafficLogListVO> fsCourseTrafficLogListVOS = fsCourseTrafficLogMapper.selectTrafficNew(param);
+        List<FsCourseTrafficLogListVO> fsCourseTrafficLogListVOS;
+        if (ObjectUtils.isNotEmpty(param.getCommon())) {
+            fsCourseTrafficLogListVOS = fsPublicCourseTrafficLogMapper.selectTrafficNew(param);
+        } else {
+            fsCourseTrafficLogListVOS = fsCourseTrafficLogMapper.selectTrafficNew(param);
+        }
         for (FsCourseTrafficLogListVO log : fsCourseTrafficLogListVOS) {
             if (ObjectUtils.isNotNull(log.getProject())) {
                 String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(log.getProject()));

+ 80 - 7
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -180,6 +180,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService {
     @Autowired
     private FsCourseTrafficLogMapper fsCourseTrafficLogMapper;
     @Autowired
+    private FsPublicCourseTrafficLogMapper fsPublicCourseTrafficLogMapper;
+    @Autowired
     private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
     @Autowired
     private FsUserMapper fsUserMapper;
@@ -210,6 +212,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService {
     @Autowired
     private FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper;
 
+    @Autowired
+    private com.fs.his.service.IFsAppActiveUserDailyService fsAppActiveUserDailyService;
+
 
     @Autowired
     private IQwExternalContactService qwExternalContactService;
@@ -1083,6 +1088,70 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService {
         return R.ok();
     }
 
+    @Override
+    public R getPublicCourseInternetTraffic(FsUserCourseVideoFinishUParam param) {
+        try {
+            if (param.getBufferRate() == null) {
+                logger.error("【公开课缓冲值空】参数: {}", param);
+                return R.error("缓冲值空");
+            }
+            FsPublicCourseTrafficLog trafficLog = new FsPublicCourseTrafficLog();
+            trafficLog.setCreateTime(new Date());
+            BeanUtils.copyProperties(param, trafficLog);
+
+            FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
+            if (video == null) {
+                return R.error("视频不存在");
+            }
+//            Company company = companyMapper.selectCompanyById(param.getCompanyId());
+//            if (company == null) {
+//                return R.error("公司不存在");
+//            }
+            FsUserCourse fsUserCourse = fsUserCourseMapper.selectFsUserCourseByCourseId(param.getCourseId());
+            if (fsUserCourse != null) {
+                trafficLog.setProjectId(fsUserCourse.getProject());
+            }
+            BigDecimal result = param.getBufferRate().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
+            BigDecimal longAsBigDecimal = BigDecimal.valueOf(video.getFileSize());
+            long roundedResult = result.multiply(longAsBigDecimal).setScale(0, RoundingMode.HALF_UP).longValue();
+            trafficLog.setInternetTraffic(roundedResult);
+
+            if (StringUtils.isNotEmpty(trafficLog.getUuId())) {
+                fsPublicCourseTrafficLogMapper.insertOrUpdateTrafficLog(trafficLog);
+//                asyncDeductPublicCourseTraffic(company, trafficLog);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            logger.error("【公开课插入或更新流量失败】参数: {}, 错误信息:{}", param, e.getMessage(), e);
+            return R.error();
+        }
+        return R.ok();
+    }
+
+//    public void asyncDeductPublicCourseTraffic(Company company, FsPublicCourseTrafficLog trafficLog) {
+//        try {
+//            FsPublicCourseTrafficLog existingLog = fsPublicCourseTrafficLogMapper.selectFsPublicCourseTrafficLogByUuId(trafficLog.getUuId());
+//            long recordedTraffic;
+//            if (existingLog != null) {
+//                recordedTraffic = trafficLog.getInternetTraffic() - existingLog.getInternetTraffic();
+//            } else {
+//                recordedTraffic = trafficLog.getInternetTraffic();
+//            }
+//            if (recordedTraffic <= 0) {
+//                return;
+//            }
+//            Long remainingTraffic = updateRedisCache(company, recordedTraffic / 1024);
+//
+//            if ("1".equals(configUtil.generateConfigByKey("watch.course.config").getString("doNotPlay")) && remainingTraffic <= 0) {
+//                logger.warn("公开课公司ID: {} 流量不足,当前剩余: {}", company.getCompanyId(), remainingTraffic);
+//                throw new Exception("流量不足");
+//            }
+//        } catch (Exception e) {
+//            logger.error("公开课异步扣除流量失败 - 公司ID: {}, 错误信息: {}",
+//                    company.getCompanyId(), e.getMessage(), e);
+//        }
+//    }
+
     public void asyncDeductTraffic(Company company, FsCourseTrafficLog trafficLog) {
         try {
             //根据uuid查询
@@ -2094,13 +2163,15 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService {
                     fsCourseWatchLog.setWatchType(param.getTypeFlag());
                 }
                 courseWatchLogMapper.insertFsCourseWatchLog(fsCourseWatchLog);
-                String redisKey = "h5wxuser:watch:heartbeat:" + param.getUserId() + ":" + param.getVideoId() + ":" + 0;
-                redisCache.setCacheObject(redisKey, LocalDateTime.now().toString());
-                // 设置 Redis 记录的过期时间(例如 5 分钟)
-                redisCache.expire(redisKey, 300, TimeUnit.SECONDS);
-            }
-            return ResponseResult.ok(fsUser);
+
+            String redisKey = "h5wxuser:watch:heartbeat:" + param.getUserId() + ":" + param.getVideoId() + ":" + 0;
+            redisCache.setCacheObject(redisKey, LocalDateTime.now().toString());
+            redisCache.expire(redisKey, 300, TimeUnit.SECONDS);
+            //记录活跃用户
+            fsAppActiveUserDailyService.recordActiveUserToRedis(param.getUserId());
         }
+        return ResponseResult.ok(fsUser);
+    }
 
         if (!isUserCoursePeriodValid(param)) {
             return ResponseResult.fail(504, "请观看最新的课程项目");
@@ -2240,8 +2311,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService {
 
             String redisKey = "h5wxuser:watch:heartbeat:" + param.getUserId() + ":" + param.getVideoId() + ":" + param.getCompanyUserId();
             redisCache.setCacheObject(redisKey, LocalDateTime.now().toString());
-            // 设置 Redis 记录的过期时间(例如 5 分钟)
             redisCache.expire(redisKey, 300, TimeUnit.SECONDS);
+
+            //记录活跃用户
+            fsAppActiveUserDailyService.recordActiveUserToRedis(param.getUserId());
         }
         //导入im好友
         openIMService.checkAndImportFriendByDianBoNew(param.getCompanyUserId(), param.getUserId().toString(), true);

+ 30 - 0
fs-service/src/main/java/com/fs/his/domain/FsAppActiveUserDaily.java

@@ -0,0 +1,30 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * APP活跃用户日统计表
+ */
+@Data
+@TableName("fs_app_active_user_daily")
+public class FsAppActiveUserDaily {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 统计日期 */
+    @Excel(name = "统计日期")
+    private Date statDate;
+
+    /** 用户ID */
+    private Long userId;
+
+    /** 创建时间 */
+    private Date createTime;
+}

+ 14 - 14
fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java

@@ -158,18 +158,18 @@ public class FsIntegralOrder
     @TableField(exist = false)
     private List<Long> userIds;
 
-    /**
-     * 优惠券id
-     */
-    private Long couponId;
-
-    /**
-     * 订单总金额
-     */
-    private BigDecimal totalPrice;
-
-    /**
-     * 优惠金额
-     */
-    private BigDecimal discountPrice;
+//    /**
+//     * 优惠券id
+//     */
+//    private Long couponId;
+//
+//    /**
+//     * 订单总金额
+//     */
+//    private BigDecimal totalPrice;
+//
+//    /**
+//     * 优惠金额
+//     */
+//    private BigDecimal discountPrice;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/his/domain/FsStore.java

@@ -112,4 +112,10 @@ public class FsStore extends BaseEntity
      */
     private String shopCode;
 
+    /**
+     * 可发货的连锁品牌(逗号分隔,如:红德堂,优身)
+     */
+    @Excel(name = "可发货连锁品牌")
+    private String chainBrands;
+
 }

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

@@ -0,0 +1,74 @@
+package com.fs.his.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 订单财务审核记录对象 fs_store_order_finance_audit
+ */
+@Data
+public class FsStoreOrderFinanceAudit extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    @Excel(name = "订单ID")
+    private Long orderId;
+
+    @Excel(name = "订单编号")
+    private String orderCode;
+
+    @Excel(name = "审核类型", readConverterExp = "1=价格变更审核,2=订单完成审核")
+    private Integer auditType;
+
+    @Excel(name = "原订单总价")
+    private BigDecimal originalTotalPrice;
+
+    @Excel(name = "原实付金额")
+    private BigDecimal originalPayPrice;
+
+    @Excel(name = "变更后总价")
+    private BigDecimal newTotalPrice;
+
+    @Excel(name = "变更后实付金额")
+    private BigDecimal newPayPrice;
+
+    @Excel(name = "价格变更原因")
+    private String priceChangeReason;
+
+    @Excel(name = "审核状态", readConverterExp = "0=待审核,1=审核通过,2=审核拒绝")
+    private Integer auditStatus;
+
+    @Excel(name = "审核人ID")
+    private Long auditUserId;
+
+    @Excel(name = "审核人姓名")
+    private String auditUserName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "审核时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date auditTime;
+
+    @Excel(name = "审核备注")
+    private String auditRemark;
+
+    private String voucherImages;
+
+    @Excel(name = "凭证说明")
+    private String voucherRemark;
+
+    @Excel(name = "申请人ID")
+    private Long applyUserId;
+
+    @Excel(name = "申请人姓名")
+    private String applyUserName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "申请时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date applyTime;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/his/domain/FsStoreProduct.java

@@ -239,4 +239,16 @@ public class FsStoreProduct extends BaseEntity {
     @Excel(name = "运费模板")
     private Long tempId;
     private Integer isDrug;
+
+    /**
+     * 产品来源类型:1=大包品,2=自库品,3=免费品
+     */
+    @Excel(name = "产品来源类型", readConverterExp = "1=大包品,2=自库品,3=免费品")
+    private Integer productSourceType;
+
+    /**
+     * 连锁品牌:红德堂、优身、乘济、诚质
+     */
+    @Excel(name = "连锁品牌")
+    private String chainBrand;
 }

+ 40 - 0
fs-service/src/main/java/com/fs/his/enums/ChainBrandEnum.java

@@ -0,0 +1,40 @@
+package com.fs.his.enums;
+
+public enum ChainBrandEnum {
+    HONG_DE_TANG("红德堂"),
+    YOU_SHEN("优身"),
+    CHENG_JI("乘济"),
+    CHENG_ZHI("诚质");
+
+    private final String name;
+
+    ChainBrandEnum(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public static boolean isValid(String brandName) {
+        if (brandName == null || brandName.trim().isEmpty()) {
+            return false;
+        }
+        for (ChainBrandEnum brand : values()) {
+            if (brand.getName().equals(brandName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static ChainBrandEnum getByName(String name) {
+        if (name == null) return null;
+        for (ChainBrandEnum brand : values()) {
+            if (brand.getName().equals(name)) {
+                return brand;
+            }
+        }
+        return null;
+    }
+}

+ 33 - 0
fs-service/src/main/java/com/fs/his/enums/ProductSourceTypeEnum.java

@@ -0,0 +1,33 @@
+package com.fs.his.enums;
+
+public enum ProductSourceTypeEnum {
+    BIG_PACKAGE(1, "大包品"),
+    SELF_STORE(2, "自库品"),
+    FREE(3, "免费品");
+
+    private final Integer code;
+    private final String desc;
+
+    ProductSourceTypeEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public static ProductSourceTypeEnum getByCode(Integer code) {
+        if (code == null) return null;
+        for (ProductSourceTypeEnum type : values()) {
+            if (type.getCode().equals(code)) {
+                return type;
+            }
+        }
+        return null;
+    }
+}

+ 101 - 0
fs-service/src/main/java/com/fs/his/mapper/AppOperationReportMapper.java

@@ -0,0 +1,101 @@
+package com.fs.his.mapper;
+
+import com.fs.his.param.AppOperationReportParam;
+import com.fs.his.vo.AppOperationReportVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.math.BigDecimal;
+
+/**
+ * App运营报表Mapper接口
+ */
+public interface AppOperationReportMapper {
+
+    /**
+     * 查询新增用户数
+     * @param startDate 开始时间
+     * @param endDate 结束时间
+     * @return 新增用户数
+     */
+    Long selectNewUsers(@Param("startDate") String startDate, @Param("endDate") String endDate);
+
+    /**
+     * 查询累计注册用户数
+     * @return 累计注册用户数
+     */
+    Long selectTotalUsers();
+
+    /**
+     * 查询活跃用户数(有看课记录的app用户)
+     * @param startDate 开始时间
+     * @param endDate 结束时间
+     * @return 活跃用户数
+     */
+    Long selectActiveUsers(@Param("startDate") String startDate, @Param("endDate") String endDate);
+
+    /**
+     * 查询留存用户数
+     * @param newUserStartDate 新增用户开始时间
+     * @param newUserEndDate 新增用户结束时间
+     * @param activeStartDate 活跃用户开始时间
+     * @param activeEndDate 活跃用户结束时间
+     * @return 留存用户数
+     */
+    Long selectRetainedUsers(@Param("newUserStartDate") String newUserStartDate,
+                              @Param("newUserEndDate") String newUserEndDate,
+                              @Param("activeStartDate") String activeStartDate,
+                              @Param("activeEndDate") String activeEndDate);
+
+    /**
+     * 查询总看课时长(秒)
+     * @param startDate 开始时间
+     * @param endDate 结束时间
+     * @return 总看课时长
+     */
+    Long selectTotalWatchDuration(@Param("startDate") String startDate, @Param("endDate") String endDate);
+
+    /**
+     * 查询所有订单总金额(store_order、package_order、inquiry_order、integral_order)
+     * @param endDate 截止时间
+     * @return 订单总金额
+     */
+    BigDecimal selectTotalOrderAmount(@Param("endDate") String endDate);
+
+    /**
+     * 查询红包总金额(app用户)
+     * @param startDate 开始时间
+     * @param endDate 结束时间
+     * @return 红包总金额
+     */
+    BigDecimal selectTotalRedPacketAmount(@Param("startDate") String startDate, @Param("endDate") String endDate);
+
+    /**
+     * 查询上个月活跃用户数
+     * @param lastMonthStart 上个月开始时间
+     * @param lastMonthEnd 上个月结束时间
+     * @return 上个月活跃用户数
+     */
+    Long selectLastMonthActiveUsers(@Param("lastMonthStart") String lastMonthStart,
+                                     @Param("lastMonthEnd") String lastMonthEnd);
+
+    /**
+     * 查询本月未活跃用户数(上月活跃但本月未活跃)
+     * @param lastMonthStart 上个月开始时间
+     * @param lastMonthEnd 上个月结束时间
+     * @param currentMonthStart 本月开始时间
+     * @param currentMonthEnd 本月结束时间
+     * @return 本月未活跃用户数
+     */
+    Long selectCurrentMonthInactiveUsers(@Param("lastMonthStart") String lastMonthStart,
+                                          @Param("lastMonthEnd") String lastMonthEnd,
+                                          @Param("currentMonthStart") String currentMonthStart,
+                                          @Param("currentMonthEnd") String currentMonthEnd);
+
+    /**
+     * 查询月度报表基础信息
+     * @param year 年份
+     * @param month 月份
+     * @return 月度报表基础信息
+     */
+    AppOperationReportVO selectMonthReport(@Param("year") Integer year, @Param("month") Integer month);
+}

+ 34 - 0
fs-service/src/main/java/com/fs/his/mapper/FsAppActiveUserDailyMapper.java

@@ -0,0 +1,34 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsAppActiveUserDaily;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * APP活跃用户日统计Mapper接口
+ */
+public interface FsAppActiveUserDailyMapper extends BaseMapper<FsAppActiveUserDaily> {
+
+    /**
+     * 批量插入活跃用户
+     * @param statDate 统计日期
+     * @param userIds 用户ID列表
+     * @return 插入数量
+     */
+    int batchInsert(@Param("statDate") String statDate, @Param("userIds") java.util.List<Long> userIds);
+
+    /**
+     * 查询指定日期范围的活跃用户数
+     * @param startDate 开始日期
+     * @param endDate 结束日期
+     * @return 活跃用户数
+     */
+    Long selectActiveUserCount(@Param("startDate") String startDate, @Param("endDate") String endDate);
+
+    /**
+     * 查询指定日期的活跃用户ID列表
+     * @param statDate 统计日期
+     * @return 用户ID列表
+     */
+    java.util.List<Long> selectUserIdsByDate(@Param("statDate") String statDate);
+}

+ 26 - 0
fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderFinanceAuditMapper.java

@@ -0,0 +1,26 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.FsStoreOrderFinanceAudit;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 订单财务审核记录Mapper接口
+ */
+public interface FsStoreOrderFinanceAuditMapper {
+
+    FsStoreOrderFinanceAudit selectFsStoreOrderFinanceAuditById(Long id);
+
+    List<FsStoreOrderFinanceAudit> selectFsStoreOrderFinanceAuditList(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit);
+
+    FsStoreOrderFinanceAudit selectByOrderId(@Param("orderId") Long orderId);
+
+    int insertFsStoreOrderFinanceAudit(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit);
+
+    int updateFsStoreOrderFinanceAudit(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit);
+
+    int deleteFsStoreOrderFinanceAuditById(Long id);
+
+    int deleteFsStoreOrderFinanceAuditByIds(Long[] ids);
+}

+ 7 - 0
fs-service/src/main/java/com/fs/his/mapper/FsStoreProductMapper.java

@@ -134,6 +134,13 @@ public interface FsStoreProductMapper {
             " where product_id=#{productId}")
     int incStockDecSales(@Param("num") Long num, @Param("productId") Long productId);
 
+    @Update("update fs_store_product set stock=stock-#{num}, sales=sales+#{num}" +
+            " where product_id=#{productId} and stock >= #{num}")
+    int decStockIncSales(@Param("num") Long num, @Param("productId") Long productId);
+
+    @Update("update fs_store_product set product_source_type = #{productSourceType}, chain_brand = #{chainBrand} where product_id = #{productId}")
+    int updateProductSourceType(@Param("productId") Long productId, @Param("productSourceType") Integer productSourceType, @Param("chainBrand") String chainBrand);
+
 
     @Select({"<script> " +
             "select p.product_id  from fs_store_product p left join fs_store_product_category pc on p.cate_id=pc.cate_id  " +

+ 19 - 0
fs-service/src/main/java/com/fs/his/param/AppOperationReportParam.java

@@ -0,0 +1,19 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+/**
+ * App运营报表查询参数
+ */
+@Data
+public class AppOperationReportParam {
+
+    /** 年份 */
+    private Integer year;
+
+    /** 月份 */
+    private Integer month;
+
+    /** 留存天数(默认7天) */
+    private Integer retentionDays;
+}

+ 31 - 0
fs-service/src/main/java/com/fs/his/param/FsOrderFinanceAuditApplyParam.java

@@ -0,0 +1,31 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 财务审核申请参数
+ */
+@Data
+public class FsOrderFinanceAuditApplyParam {
+
+    @NotEmpty(message = "订单ID不能为空")
+    private List<Long> orderIds;
+
+    @NotNull(message = "审核类型不能为空")
+    private Integer auditType;
+
+    private BigDecimal newTotalPrice;
+
+    private BigDecimal newPayPrice;
+
+    private String priceChangeReason;
+
+    private String voucherImages;
+
+    private String voucherRemark;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/his/param/FsOrderFinanceAuditParam.java

@@ -0,0 +1,20 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 财务审核参数
+ */
+@Data
+public class FsOrderFinanceAuditParam {
+
+    @NotNull(message = "审核记录ID不能为空")
+    private Long id;
+
+    @NotNull(message = "审核状态不能为空")
+    private Integer auditStatus;
+
+    private String auditRemark;
+}

+ 8 - 0
fs-service/src/main/java/com/fs/his/param/FsStoreProductAddEditParam.java

@@ -160,6 +160,14 @@ public class FsStoreProductAddEditParam implements Serializable {
     @Excel(name = "品牌")
     private String brand;
 
+    /** 产品来源类型:1=大包品,2=自库品,3=免费品 */
+    @Excel(name = "产品来源类型", readConverterExp = "1=大包品,2=自库品,3=免费品")
+    private Integer productSourceType;
+
+    /** 连锁品牌:红德堂、优身、乘济、诚质 */
+    @Excel(name = "连锁品牌")
+    private String chainBrand;
+
     //属性项目
     private List<ProductArrtDTO> items;
     //sku结果集

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

@@ -0,0 +1,17 @@
+package com.fs.his.service;
+
+import com.fs.his.param.AppOperationReportParam;
+import com.fs.his.vo.AppOperationReportVO;
+
+/**
+ * App运营报表Service接口
+ */
+public interface IAppOperationReportService {
+
+    /**
+     * 获取月度运营报表
+     * @param param 查询参数(year、month、retentionDays)
+     * @return 月度运营报表数据
+     */
+    AppOperationReportVO getMonthReport(AppOperationReportParam param);
+}

+ 30 - 0
fs-service/src/main/java/com/fs/his/service/IFsAppActiveUserDailyService.java

@@ -0,0 +1,30 @@
+package com.fs.his.service;
+
+import com.fs.his.domain.FsAppActiveUserDaily;
+
+import java.util.List;
+
+/**
+ * APP活跃用户日统计Service接口
+ */
+public interface IFsAppActiveUserDailyService {
+
+    /**
+     * 记录用户活跃到Redis
+     * @param userId 用户ID
+     */
+    void recordActiveUserToRedis(Long userId);
+
+    /**
+     * 同步Redis活跃用户到数据库
+     */
+    void syncActiveUserFromRedis();
+
+    /**
+     * 查询指定日期范围的活跃用户数
+     * @param startDate 开始日期
+     * @param endDate 结束日期
+     * @return 活跃用户数
+     */
+    Long getActiveUserCount(String startDate, String endDate);
+}

+ 0 - 7
fs-service/src/main/java/com/fs/his/service/IFsCouponService.java

@@ -81,13 +81,6 @@ public interface IFsCouponService
      */
     List<FsCoupon> selectFsCouponListByIds(List<Long> ids);
 
-    /**
-     * 查询用户指定类型的优惠券列表
-     * @param userId 用户ID
-     * @param couponType 优惠券类型 3问诊优惠券 5套餐包优惠券 7积分商品免单券
-     * @return 优惠券列表
-     */
-    List<FsCoupon> selectUserCouponByType(Long userId, Integer couponType);
 
     /**
      * 查询用户指定类型的优惠券列表(带商品匹配)

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

@@ -0,0 +1,27 @@
+package com.fs.his.service;
+
+import com.fs.his.domain.FsStoreOrderFinanceAudit;
+import com.fs.his.param.FsOrderFinanceAuditApplyParam;
+import com.fs.his.param.FsOrderFinanceAuditParam;
+
+import java.util.List;
+
+/**
+ * 订单财务审核记录Service接口
+ */
+public interface IFsStoreOrderFinanceAuditService {
+
+    FsStoreOrderFinanceAudit selectFsStoreOrderFinanceAuditById(Long id);
+
+    List<FsStoreOrderFinanceAudit> selectFsStoreOrderFinanceAuditList(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit);
+
+    int insertFsStoreOrderFinanceAudit(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit);
+
+    int updateFsStoreOrderFinanceAudit(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit);
+
+    int deleteFsStoreOrderFinanceAuditByIds(Long[] ids);
+
+    int applyFinanceAudit(FsOrderFinanceAuditApplyParam param, Long applyUserId, String applyUserName);
+
+    int auditFinanceAudit(FsOrderFinanceAuditParam param, Long auditUserId, String auditUserName);
+}

+ 14 - 0
fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java

@@ -299,6 +299,20 @@ public interface IFsStoreOrderService
      */
     int approveOrder(List<Long> orderId);
 
+    /**
+     * 申请财务审核
+     */
+    int applyFinanceAudit(List<Long> orderIds, Integer auditType,
+                          BigDecimal newTotalPrice, BigDecimal newPayPrice,
+                          String priceChangeReason, String voucherImages, String voucherRemark,
+                          Long applyUserId, String applyUserName);
+
+    /**
+     * 财务审核
+     */
+    int auditFinanceAudit(Long auditId, Integer auditStatus, String auditRemark,
+                          Long auditUserId, String auditUserName);
+
     /**
      * 聚水潭订单
      * @param orderIds

+ 183 - 0
fs-service/src/main/java/com/fs/his/service/impl/AppOperationReportServiceImpl.java

@@ -0,0 +1,183 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.core.redis.RedisCache;
+import com.fs.his.mapper.AppOperationReportMapper;
+import com.fs.his.param.AppOperationReportParam;
+import com.fs.his.service.IAppOperationReportService;
+import com.fs.his.vo.AppOperationReportVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class AppOperationReportServiceImpl implements IAppOperationReportService {
+
+    @Autowired
+    private AppOperationReportMapper appOperationReportMapper;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    private static final String CACHE_PREFIX = "app:report:month:";
+
+    /**
+     * 获取月度运营报表
+     * @param param 查询参数(year、month、retentionDays)
+     * @return 月度运营报表数据
+     */
+    @Override
+    public AppOperationReportVO getMonthReport(AppOperationReportParam param) {
+        Integer year = param.getYear();
+        Integer month = param.getMonth();
+
+        if (year == null) {
+            Calendar cal = Calendar.getInstance();
+            year = cal.get(Calendar.YEAR);
+        }
+        if (month == null) {
+            Calendar cal = Calendar.getInstance();
+            month = cal.get(Calendar.MONTH) + 1;
+        }
+
+        String cacheKey = CACHE_PREFIX + year + ":" + month;
+        AppOperationReportVO cachedVo = redisCache.getCacheObject(cacheKey);
+        if (cachedVo != null) {
+            return cachedVo;
+        }
+
+        String monthStart = year + "-" + String.format("%02d", month) + "-01";
+        String monthEnd = year + "-" + String.format("%02d", month) + "-" + getLastDayOfMonth(year, month);
+
+        AppOperationReportVO vo = new AppOperationReportVO();
+        vo.setYear(year);
+        vo.setMonth(month);
+        vo.setPeriod("月度统计");
+
+        Long newUsers = appOperationReportMapper.selectNewUsers(monthStart + " 00:00:00", monthEnd + " 23:59:59");
+        vo.setNewUsers(newUsers);
+
+        Long totalUsers = appOperationReportMapper.selectTotalUsers();
+        vo.setTotalUsers(totalUsers);
+
+        Long activeUsers = appOperationReportMapper.selectActiveUsers(monthStart, monthEnd);
+        vo.setActiveUsers(activeUsers);
+
+        if (newUsers != null && newUsers > 0) {
+            int retentionDays = param.getRetentionDays() != null ? param.getRetentionDays() : 30;
+            Long retained = appOperationReportMapper.selectRetainedUsers(
+                    monthStart + " 00:00:00", monthEnd + " 23:59:59",
+                    getAfterDate(monthStart, retentionDays) + " 00:00:00", getAfterDate(monthEnd, retentionDays) + " 23:59:59");
+            vo.setRetentionRate(calculateRate(retained, newUsers));
+            vo.setRetentionDays(retentionDays);
+        }
+
+        if (activeUsers != null && activeUsers > 0) {
+            Long totalDuration = appOperationReportMapper.selectTotalWatchDuration(monthStart + " 00:00:00", monthEnd + " 23:59:59");
+            BigDecimal avgDuration = new BigDecimal(totalDuration).divide(new BigDecimal(activeUsers), 2, RoundingMode.HALF_UP);
+            vo.setAvgUseDuration(avgDuration.divide(new BigDecimal(60), 2, RoundingMode.HALF_UP));
+        }
+
+        if (totalUsers != null && totalUsers > 0) {
+            BigDecimal totalAmount = appOperationReportMapper.selectTotalOrderAmount(monthEnd + " 23:59:59");
+            vo.setLtv(totalAmount.divide(new BigDecimal(totalUsers), 2, RoundingMode.HALF_UP));
+        }
+
+        if (newUsers != null && newUsers > 0) {
+            BigDecimal redPacketAmount = appOperationReportMapper.selectTotalRedPacketAmount(monthStart + " 00:00:00", monthEnd + " 23:59:59");
+            vo.setCac(redPacketAmount.divide(new BigDecimal(newUsers), 2, RoundingMode.HALF_UP));
+        }
+
+        String[] lastMonthDates = getLastMonthDateRange(year, month);
+        Long lastMonthActive = appOperationReportMapper.selectLastMonthActiveUsers(lastMonthDates[0], lastMonthDates[1]);
+        if (lastMonthActive != null && lastMonthActive > 0) {
+            Long currentMonthInactive = appOperationReportMapper.selectCurrentMonthInactiveUsers(
+                    lastMonthDates[0], lastMonthDates[1], monthStart, monthEnd);
+            vo.setMonthlyChurnRate(calculateRate(currentMonthInactive, lastMonthActive));
+        }
+
+        int cacheMinutes = isCurrentMonth(year, month) ? 5 : 60;
+        redisCache.setCacheObject(cacheKey, vo, cacheMinutes, TimeUnit.MINUTES);
+
+        return vo;
+    }
+
+    /**
+     * 判断是否为当前月份
+     */
+    private boolean isCurrentMonth(int year, int month) {
+        Calendar cal = Calendar.getInstance();
+        return cal.get(Calendar.YEAR) == year && (cal.get(Calendar.MONTH) + 1) == month;
+    }
+
+    /**
+     * 计算比率
+     * @param part 部分值
+     * @param total 总值
+     * @return 比率(百分比)
+     */
+    private BigDecimal calculateRate(Long part, Long total) {
+        if (total == null || total == 0 || part == null) {
+            return BigDecimal.ZERO;
+        }
+        return new BigDecimal(part).multiply(new BigDecimal(100))
+                .divide(new BigDecimal(total), 2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 获取指定日期后的日期
+     * @param date 基准日期
+     * @param days 天数
+     * @return 计算后的日期
+     */
+    private String getAfterDate(String date, int days) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            Calendar cal = Calendar.getInstance();
+            cal.setTime(sdf.parse(date));
+            cal.add(Calendar.DAY_OF_MONTH, days);
+            return sdf.format(cal.getTime());
+        } catch (Exception e) {
+            return date;
+        }
+    }
+
+    /**
+     * 获取指定月份的最后一天
+     * @param year 年份
+     * @param month 月份
+     * @return 该月最后一天的日期(1-31)
+     */
+    private int getLastDayOfMonth(int year, int month) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, year);
+        cal.set(Calendar.MONTH, month);
+        cal.set(Calendar.DAY_OF_MONTH, 0);
+        return cal.get(Calendar.DAY_OF_MONTH);
+    }
+
+    /**
+     * 获取上个月的日期范围
+     * @param year 当前年份
+     * @param month 当前月份
+     * @return 上个月的开始日期和结束日期数组
+     */
+    private String[] getLastMonthDateRange(int year, int month) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, year);
+        cal.set(Calendar.MONTH, month - 2);
+        cal.set(Calendar.DAY_OF_MONTH, 1);
+
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        String startDate = sdf.format(cal.getTime());
+
+        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        String endDate = sdf.format(cal.getTime());
+
+        return new String[]{startDate, endDate};
+    }
+}

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

@@ -0,0 +1,103 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.core.redis.RedisCache;
+import com.fs.his.domain.FsAppActiveUserDaily;
+import com.fs.his.mapper.FsAppActiveUserDailyMapper;
+import com.fs.his.service.IFsAppActiveUserDailyService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * APP活跃用户日统计Service实现
+ */
+@Slf4j
+@Service
+public class FsAppActiveUserDailyServiceImpl implements IFsAppActiveUserDailyService {
+
+    private static final String REDIS_KEY_PREFIX = "app:active:user:";
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private FsAppActiveUserDailyMapper fsAppActiveUserDailyMapper;
+
+    /**
+     * 记录用户活跃到Redis
+     * 使用Set结构存储,自动去重
+     * @param userId 用户ID
+     */
+    @Override
+    public void recordActiveUserToRedis(Long userId) {
+        try {
+            String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
+            String redisKey = REDIS_KEY_PREFIX + today;
+            Set<Long> userIdSet = new HashSet<>();
+            userIdSet.add(userId);
+            redisCache.setCacheSet(redisKey, userIdSet);
+            redisCache.expire(redisKey, 86400 * 2);
+            log.debug("记录活跃用户到Redis: userId={}, date={}", userId, today);
+        } catch (Exception e) {
+            log.error("记录活跃用户到Redis失败: userId={}", userId, e);
+        }
+    }
+
+    /**
+     * 同步Redis活跃用户到数据库
+     * 定时任务调用,每天凌晨执行
+     */
+    @Override
+    public void syncActiveUserFromRedis() {
+        try {
+            Calendar cal = Calendar.getInstance();
+            cal.add(Calendar.DAY_OF_MONTH, -1);
+            String yesterday = new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime());
+            
+            String redisKey = REDIS_KEY_PREFIX + yesterday;
+            Set<Long> userIds = redisCache.getCacheSet(redisKey);
+            
+            if (userIds == null || userIds.isEmpty()) {
+                log.info("昨日无活跃用户数据: date={}", yesterday);
+                return;
+            }
+            
+            List<Long> userIdList = new ArrayList<>(userIds);
+            int batchSize = 1000;
+            int total = userIdList.size();
+            int inserted = 0;
+            
+            for (int i = 0; i < total; i += batchSize) {
+                int end = Math.min(i + batchSize, total);
+                List<Long> batch = userIdList.subList(i, end);
+                inserted += fsAppActiveUserDailyMapper.batchInsert(yesterday, batch);
+            }
+            
+            log.info("同步活跃用户数据完成: date={}, total={}, inserted={}", yesterday, total, inserted);
+            
+            redisCache.deleteObject(redisKey);
+            
+        } catch (Exception e) {
+            log.error("同步Redis活跃用户到数据库失败", e);
+        }
+    }
+
+    /**
+     * 查询指定日期范围的活跃用户数
+     * @param startDate 开始日期
+     * @param endDate 结束日期
+     * @return 活跃用户数
+     */
+    @Override
+    public Long getActiveUserCount(String startDate, String endDate) {
+        return fsAppActiveUserDailyMapper.selectActiveUserCount(startDate, endDate);
+    }
+}

+ 3 - 10
fs-service/src/main/java/com/fs/his/service/impl/FsCouponServiceImpl.java

@@ -158,20 +158,13 @@ public class FsCouponServiceImpl implements IFsCouponService
         return R.ok();
     }
 
-    @Override
-    public List<FsCoupon> selectUserCouponByType(Long userId, Integer couponType) {
-        return fsCouponMapper.selectUserCouponByType(userId, couponType);
-    }
-
     @Override
     public List<FsCoupon> selectUserCouponByTypeWithGoods(Long userId, Integer couponType, Long goodsId) {
         List<FsCoupon> coupons = fsCouponMapper.selectUserCouponByType(userId, couponType);
         if (couponType == 7 && goodsId != null) {
-            // TODO: 积分商品免单券需要判断 fs_coupon 表的 free_goods_id 字段是否和商品ID对应
-            // 目前 FsCoupon 实体类暂无 freeGoodsId 字段,后续需要添加该字段后启用以下逻辑
-            // coupons = coupons.stream()
-            //     .filter(c -> c.getFreeGoodsId() != null && c.getFreeGoodsId().equals(goodsId))
-            //     .collect(Collectors.toList());
+             coupons = coupons.stream()
+                 .filter(c -> c.getFreeGoodsId() != null && c.getFreeGoodsId().equals(goodsId))
+                 .collect(Collectors.toList());
         }
         return coupons;
     }

+ 8 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java

@@ -1283,6 +1283,8 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService {
         FsUserCoupon userCoupon = null;
         FsCoupon coupon = null;
         BigDecimal discountPrice = BigDecimal.ZERO;
+        //优惠积分
+        Long discountIntegral= 0L;
         BigDecimal totalPrice = totalCash;
         if (param.getCouponId() != null) {
             coupon = fsCouponMapper.selectFsCouponByCouponId(param.getCouponId());
@@ -1305,6 +1307,7 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService {
             } else {
                 discountPrice = couponPrice;
             }
+            discountIntegral=totalIntegral;
             totalCash = totalPrice.subtract(discountPrice);
             if (totalCash.compareTo(BigDecimal.ZERO) < 0) {
                 totalCash = BigDecimal.ZERO;
@@ -1348,12 +1351,13 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService {
         order.setCreateTime(new Date());
         order.setCompanyUserId(param.getCompanyUserId());
         order.setCompanyId(param.getCompanyId());
-        order.setTotalPrice(totalPrice);
-        order.setDiscountPrice(discountPrice);
+        order.setTotalMoney(totalPrice);
+        order.setDiscountMoney(discountPrice);
+        order.setDiscountIntegral(String.valueOf(discountIntegral));
+        order.setTotalIntegral(String.valueOf(totalIntegral));
         if (coupon != null) {
-            order.setCouponId(coupon.getCouponId());
+            order.setUserCouponId(userCoupon.getCouponId());
         }
-
         if (fsIntegralOrderMapper.insertFsIntegralOrder(order) > 0) {
             if (order.getPayType() != 2 && totalIntegral > 0) {
                 FsUser userMap = new FsUser();

+ 125 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderFinanceAuditServiceImpl.java

@@ -0,0 +1,125 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.domain.FsStoreOrderFinanceAudit;
+import com.fs.his.mapper.FsStoreOrderFinanceAuditMapper;
+import com.fs.his.mapper.FsStoreOrderMapper;
+import com.fs.his.param.FsOrderFinanceAuditApplyParam;
+import com.fs.his.param.FsOrderFinanceAuditParam;
+import com.fs.his.service.IFsStoreOrderFinanceAuditService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class FsStoreOrderFinanceAuditServiceImpl implements IFsStoreOrderFinanceAuditService {
+
+    @Autowired
+    private FsStoreOrderFinanceAuditMapper fsStoreOrderFinanceAuditMapper;
+
+    @Autowired
+    private FsStoreOrderMapper fsStoreOrderMapper;
+
+    @Override
+    public FsStoreOrderFinanceAudit selectFsStoreOrderFinanceAuditById(Long id) {
+        return fsStoreOrderFinanceAuditMapper.selectFsStoreOrderFinanceAuditById(id);
+    }
+
+    @Override
+    public List<FsStoreOrderFinanceAudit> selectFsStoreOrderFinanceAuditList(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit) {
+        return fsStoreOrderFinanceAuditMapper.selectFsStoreOrderFinanceAuditList(fsStoreOrderFinanceAudit);
+    }
+
+    @Override
+    public int insertFsStoreOrderFinanceAudit(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit) {
+        return fsStoreOrderFinanceAuditMapper.insertFsStoreOrderFinanceAudit(fsStoreOrderFinanceAudit);
+    }
+
+    @Override
+    public int updateFsStoreOrderFinanceAudit(FsStoreOrderFinanceAudit fsStoreOrderFinanceAudit) {
+        return fsStoreOrderFinanceAuditMapper.updateFsStoreOrderFinanceAudit(fsStoreOrderFinanceAudit);
+    }
+
+    @Override
+    public int deleteFsStoreOrderFinanceAuditByIds(Long[] ids) {
+        return fsStoreOrderFinanceAuditMapper.deleteFsStoreOrderFinanceAuditByIds(ids);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int applyFinanceAudit(FsOrderFinanceAuditApplyParam param, Long applyUserId, String applyUserName) {
+        int count = 0;
+        for (Long orderId : param.getOrderIds()) {
+            FsStoreOrder order = fsStoreOrderMapper.selectFsStoreOrderByOrderId(orderId);
+            if (order == null) {
+                throw new CustomException("订单不存在:" + orderId);
+            }
+
+            FsStoreOrderFinanceAudit existAudit = fsStoreOrderFinanceAuditMapper.selectByOrderId(orderId);
+            if (existAudit != null && existAudit.getAuditStatus() == 0) {
+                throw new CustomException("订单已存在待审核记录:" + order.getOrderCode());
+            }
+
+            FsStoreOrderFinanceAudit audit = new FsStoreOrderFinanceAudit();
+            audit.setOrderId(orderId);
+            audit.setOrderCode(order.getOrderCode());
+            audit.setAuditType(param.getAuditType());
+            audit.setOriginalTotalPrice(order.getTotalPrice());
+            audit.setOriginalPayPrice(order.getPayPrice());
+            audit.setNewTotalPrice(param.getNewTotalPrice());
+            audit.setNewPayPrice(param.getNewPayPrice());
+            audit.setPriceChangeReason(param.getPriceChangeReason());
+            audit.setVoucherImages(param.getVoucherImages());
+            audit.setVoucherRemark(param.getVoucherRemark());
+            audit.setAuditStatus(0);
+            audit.setApplyUserId(applyUserId);
+            audit.setApplyUserName(applyUserName);
+            audit.setApplyTime(new Date());
+
+            count += fsStoreOrderFinanceAuditMapper.insertFsStoreOrderFinanceAudit(audit);
+        }
+        return count;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int auditFinanceAudit(FsOrderFinanceAuditParam param, Long auditUserId, String auditUserName) {
+        FsStoreOrderFinanceAudit audit = fsStoreOrderFinanceAuditMapper.selectFsStoreOrderFinanceAuditById(param.getId());
+        if (audit == null) {
+            throw new CustomException("审核记录不存在");
+        }
+
+        if (audit.getAuditStatus() != 0) {
+            throw new CustomException("该记录已审核,不能重复审核");
+        }
+
+        audit.setAuditStatus(param.getAuditStatus());
+        audit.setAuditUserId(auditUserId);
+        audit.setAuditUserName(auditUserName);
+        audit.setAuditTime(new Date());
+        audit.setAuditRemark(param.getAuditRemark());
+
+        int result = fsStoreOrderFinanceAuditMapper.updateFsStoreOrderFinanceAudit(audit);
+
+        if (param.getAuditStatus() == 1) {
+            FsStoreOrder order = new FsStoreOrder();
+            order.setOrderId(audit.getOrderId());
+
+            if (audit.getAuditType() == 1) {
+                order.setStatus(4);
+            } else if (audit.getAuditType() == 2) {
+                order.setTotalPrice(audit.getNewTotalPrice());
+                order.setPayPrice(audit.getNewPayPrice());
+            }
+
+            fsStoreOrderMapper.updateFsStoreOrder(order);
+        }
+
+        return result;
+    }
+}

+ 160 - 12
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -600,7 +600,14 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
 
     @Override
     public List<FsStoreOrderListVO> selectFsStoreOrderListVO(FsStoreOrderParam param) {
-        return fsStoreOrderMapper.selectFsStoreOrderListVO(param);
+        List<FsStoreOrderListVO> list = fsStoreOrderMapper.selectFsStoreOrderListVO(param);
+
+        for (FsStoreOrderListVO vo : list) {
+            FsStoreOrderFinanceAudit audit = fsStoreOrderFinanceAuditMapper.selectByOrderId(vo.getOrderId());
+            vo.setIsApplyAudit(audit != null);
+        }
+
+        return list;
     }
 
     @Override
@@ -1965,6 +1972,23 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
                 companyService.subtractCompanyMoney(order);
             }
 
+            FsStoreOrderItem queryItem = new FsStoreOrderItem();
+            queryItem.setOrderId(order.getOrderId());
+            List<FsStoreOrderItem> orderItems = fsStoreOrderItemMapper.selectFsStoreOrderItemList(queryItem);
+            if (orderItems != null && !orderItems.isEmpty()) {
+                for (FsStoreOrderItem item : orderItems) {
+                    Long productId = item.getProductId();
+                    Long productAttrValueId = item.getProductAttrValueId();
+                    Long num = item.getNum() != null ? item.getNum() : 1L;
+                    
+                    if (productAttrValueId != null && productAttrValueId > 0) {
+                        fsStoreProductAttrValueMapper.decProductAttrStock(productAttrValueId, num.intValue());
+                    }
+                    
+                    fsStoreProductMapper.decStockIncSales(num, productId);
+                }
+            }
+
             FsStore store = fsStoreMapper.selectFsStoreByStoreId(order.getStoreId());
             //订阅物流回调
             String lastFourNumber = "";
@@ -4703,6 +4727,87 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         return i;
     }
 
+    @Autowired
+    private FsStoreOrderFinanceAuditMapper fsStoreOrderFinanceAuditMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int applyFinanceAudit(List<Long> orderIds, Integer auditType, 
+                                  BigDecimal newTotalPrice, BigDecimal newPayPrice,
+                                  String priceChangeReason, String voucherImages, String voucherRemark,
+                                  Long applyUserId, String applyUserName) {
+        int count = 0;
+        for (Long orderId : orderIds) {
+            FsStoreOrder order = fsStoreOrderMapper.selectFsStoreOrderByOrderId(orderId);
+            if (order == null) {
+                throw new CustomException("订单不存在:" + orderId);
+            }
+
+            FsStoreOrderFinanceAudit existAudit = fsStoreOrderFinanceAuditMapper.selectByOrderId(orderId);
+            if (existAudit != null && existAudit.getAuditStatus() == 0) {
+                throw new CustomException("订单已存在待审核记录:" + order.getOrderCode());
+            }
+
+            FsStoreOrderFinanceAudit audit = new FsStoreOrderFinanceAudit();
+            audit.setOrderId(orderId);
+            audit.setOrderCode(order.getOrderCode());
+            audit.setAuditType(auditType);
+            audit.setOriginalTotalPrice(order.getTotalPrice());
+            audit.setOriginalPayPrice(order.getPayPrice());
+            audit.setNewTotalPrice(newTotalPrice);
+            audit.setNewPayPrice(newPayPrice);
+            audit.setPriceChangeReason(priceChangeReason);
+            audit.setVoucherImages(voucherImages);
+            audit.setVoucherRemark(voucherRemark);
+            audit.setAuditStatus(0);
+            audit.setApplyUserId(applyUserId);
+            audit.setApplyUserName(applyUserName);
+            audit.setApplyTime(new Date());
+
+            count += fsStoreOrderFinanceAuditMapper.insertFsStoreOrderFinanceAudit(audit);
+        }
+        return count;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int auditFinanceAudit(Long auditId, Integer auditStatus, String auditRemark,
+                                  Long auditUserId, String auditUserName) {
+        FsStoreOrderFinanceAudit audit = fsStoreOrderFinanceAuditMapper.selectFsStoreOrderFinanceAuditById(auditId);
+        if (audit == null) {
+            throw new CustomException("审核记录不存在");
+        }
+
+        if (audit.getAuditStatus() != 0) {
+            throw new CustomException("该记录已审核,不能重复审核");
+        }
+
+        audit.setAuditStatus(auditStatus);
+        audit.setAuditUserId(auditUserId);
+        audit.setAuditUserName(auditUserName);
+        audit.setAuditTime(new Date());
+        audit.setAuditRemark(auditRemark);
+
+        int result = fsStoreOrderFinanceAuditMapper.updateFsStoreOrderFinanceAudit(audit);
+
+        if (auditStatus == 1) {
+            FsStoreOrder order = new FsStoreOrder();
+            order.setOrderId(audit.getOrderId());
+
+            if (audit.getAuditType() == 1) {
+                order.setTotalPrice(audit.getNewTotalPrice());
+                order.setPayPrice(audit.getNewPayPrice());
+                order.setPayMoney(audit.getNewPayPrice());
+            } else if (audit.getAuditType() == 2) {
+               order.setStatus(4);
+            }
+
+            fsStoreOrderMapper.updateFsStoreOrder(order);
+        }
+
+        return result;
+    }
+
     @Override
     public void createJSTOmsOrder(List<Long> orderIds) throws Exception {
         // 1. 检查 ERP 是否开启
@@ -4935,6 +5040,11 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
             if (order == null) {
                 return;
             }
+            if(order.getStoreId()== null){
+                log.warn("店铺id为空,订单对象:{}", order);
+                return;
+            }
+            FsStore fsStore = fsStoreMapper.selectFsStoreByStoreId(order.getStoreId());
             ErpOrder erpOrder = new ErpOrder();
             if (order.getCompanyId() != null) {
                 erpOrder.setVip_code(order.getUserId().toString() + order.getCompanyId().toString());
@@ -4942,16 +5052,6 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
                 erpOrder.setVip_code(order.getUserId().toString());
             }
             erpOrder.setPlatform_code(order.getOrderCode());
-//        if(order.getStoreHouseCode()==null){
-//            erpOrder.setWarehouse_code("CQDS001");
-//        }else{
-//            erpOrder.setWarehouse_code(order.getStoreHouseCode());
-//        }
-            if(order.getStoreId()== null){
-                log.warn("店铺id为空,订单对象:{}", order);
-                return;
-            }
-            FsStore fsStore = fsStoreMapper.selectFsStoreByStoreId(order.getStoreId());
             erpOrder.setShop_code(fsStore.getShopCode());
             erpOrder.setSeller_memo(order.getRemark());
             List<ErpOrderPayment> payments = new ArrayList<>();
@@ -4963,7 +5063,7 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
             if (order.getPayTime() != null) {
                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                 String timeString = sdf.format(order.getPayTime());
-                Date date = sdf.parse(timeString); // 时间格式转为时间戳
+                Date date = sdf.parse(timeString);
                 long timeLong = date.getTime();
                 payment.setPaytime(new Timestamp(timeLong));
             }
@@ -5059,6 +5159,11 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
             FsStoreOrderItem itemMap = new FsStoreOrderItem();
             itemMap.setOrderId(order.getOrderId());
             List<FsStoreOrderItem> orderItems = storeOrderItemService.selectFsStoreOrderItemList(itemMap);
+            String deliveryCheckResult = checkDeliveryPermissionForErp(fsStore, orderItems);
+            if (deliveryCheckResult != null) {
+                log.warn("订单{}发货权限校验失败: {}", order.getOrderCode(), deliveryCheckResult);
+                continue;
+            }
             List<ErpOrderItem> details = new ArrayList<>();
             for (FsStoreOrderItem orderItem : orderItems) {
                 FsStoreCartDTO cartDTO = JSONUtil.toBean(orderItem.getJsonInfo(), FsStoreCartDTO.class);
@@ -5187,4 +5292,47 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         }
     }
 
+    private String checkDeliveryPermissionForErp(FsStore store, List<FsStoreOrderItem> orderItems) {
+        if (orderItems == null || orderItems.isEmpty()) {
+            return null;
+        }
+        
+        String storeChainBrands = store.getChainBrands();
+        Set<String> storeAllowedBrands = new HashSet<>();
+        if (storeChainBrands != null && !storeChainBrands.trim().isEmpty()) {
+            String[] brands = storeChainBrands.split(",");
+            for (String brand : brands) {
+                storeAllowedBrands.add(brand.trim());
+            }
+        }
+        
+        for (FsStoreOrderItem item : orderItems) {
+            FsStoreProduct product = fsStoreProductMapper.selectFsStoreProductByProductId(item.getProductId());
+            if (product == null) {
+                continue;
+            }
+            
+            Integer productSourceType = product.getProductSourceType();
+            if (productSourceType == null) {
+                productSourceType = ProductSourceTypeEnum.SELF_STORE.getCode();
+            }
+            
+            if (ProductSourceTypeEnum.BIG_PACKAGE.getCode().equals(productSourceType)) {
+                String productChainBrand = product.getChainBrand();
+                if (productChainBrand == null || productChainBrand.trim().isEmpty()) {
+                    return "商品【" + product.getProductName() + "】为大包品但未配置连锁品牌";
+                }
+                if (!storeAllowedBrands.contains(productChainBrand)) {
+                    return "店铺无权发货大包品【" + product.getProductName() + "】,连锁品牌:" + productChainBrand;
+                }
+            } else if (ProductSourceTypeEnum.SELF_STORE.getCode().equals(productSourceType)) {
+                if (!store.getStoreId().equals(product.getStoreId())) {
+                    return "商品【" + product.getProductName() + "】为自库品,不属于当前店铺";
+                }
+            }
+        }
+        
+        return null;
+    }
+
 }

+ 65 - 0
fs-service/src/main/java/com/fs/his/vo/AppOperationReportVO.java

@@ -0,0 +1,65 @@
+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;
+
+/**
+ * App运营报表统计VO
+ */
+@Data
+public class AppOperationReportVO {
+
+    /** 统计日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "统计日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date statDate;
+
+    /** 统计周期 */
+    @Excel(name = "统计周期")
+    private String period;
+
+    /** 新增用户数 */
+    @Excel(name = "新增用户数")
+    private Long newUsers;
+
+    /** 累计注册用户 */
+    @Excel(name = "累计注册用户")
+    private Long totalUsers;
+
+    /** 活跃用户数 */
+    @Excel(name = "活跃用户数")
+    private Long activeUsers;
+
+    /** 留存率(%) */
+    @Excel(name = "留存率(%)")
+    private BigDecimal retentionRate;
+
+    /** 留存天数 */
+    private Integer retentionDays;
+
+    /** 用户平均使用时长(分钟) */
+    @Excel(name = "用户平均使用时长(分钟)")
+    private BigDecimal avgUseDuration;
+
+    /** 用户生命周期价值LTV(元) */
+    @Excel(name = "用户生命周期价值LTV(元)")
+    private BigDecimal ltv;
+
+    /** 用户获取成本CAC(元) */
+    @Excel(name = "用户获取成本CAC(元)")
+    private BigDecimal cac;
+
+    /** 月流失率(%) */
+    @Excel(name = "月流失率(%)")
+    private BigDecimal monthlyChurnRate;
+
+    /** 年份 */
+    private Integer year;
+
+    /** 月份 */
+    private Integer month;
+}

+ 10 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderListVO.java

@@ -67,4 +67,14 @@ public class FsStoreOrderListVO {
 
     //erp推送账号
     private String erpAccount;
+
+    /**
+     * 是否申请审核
+     */
+    private Boolean isApplyAudit;
+
+    /**
+     * 快递单号
+     */
+    private String deliverySn;
 }

+ 3 - 0
fs-service/src/main/resources/application-druid-hdt.yml

@@ -161,3 +161,6 @@ im:
     type: OPENIM
 #是否为新商户,新商户不走mpOpenId
 isNewWxMerchant: false
+# 聚水潭API配置
+jst:
+  shop_code: "20300406"

+ 1 - 2
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -2158,7 +2158,7 @@ FROM
 
         <!-- 项目 -->
         <if test="project != null and project != ''">
-            AND cuu.project_id = #{project}
+            AND log.project = #{project}
         </if>
         <!-- 时间范围 -->
         <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
@@ -2244,7 +2244,6 @@ FROM
         LEFT JOIN company_user cu ON log.company_user_id = cu.user_id
         LEFT JOIN company c ON log.company_id = c.company_id
         LEFT JOIN company_dept cd ON cu.dept_id = cd.dept_id
-        LEFT JOIN fs_user_course_video cv ON log.video_id = cv.video_id
         LEFT JOIN fs_course_answer_logs a ON a.watch_log_id = log.log_id
         LEFT JOIN fs_course_red_packet_log rpl ON rpl.watch_log_id = log.log_id
         WHERE log.send_type = 1

+ 102 - 0
fs-service/src/main/resources/mapper/course/FsPublicCourseTrafficLogMapper.xml

@@ -0,0 +1,102 @@
+<?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.course.mapper.FsPublicCourseTrafficLogMapper">
+
+    <resultMap type="com.fs.course.domain.FsPublicCourseTrafficLog" id="FsPublicCourseTrafficLogResult">
+        <result property="logId" column="log_id"/>
+        <result property="uuId" column="uu_id"/>
+        <result property="userId" column="user_id"/>
+        <result property="courseId" column="course_id"/>
+        <result property="videoId" column="video_id"/>
+        <result property="internetTraffic" column="internet_traffic"/>
+        <result property="status" column="status"/>
+        <result property="createTime" column="create_time"/>
+        <result property="project_id" column="projectId"/>
+    </resultMap>
+
+    <sql id="selectFsPublicCourseTrafficLogVo">
+        select log_id,
+               uu_id,
+               user_id,
+               course_id,
+               video_id,
+               company_id,
+               internet_traffic,
+               status,
+               create_time,
+               project_id
+        from fs_public_course_traffic_log
+    </sql>
+
+    <select id="selectFsPublicCourseTrafficLogByLogId" parameterType="Long" resultMap="FsPublicCourseTrafficLogResult">
+        <include refid="selectFsPublicCourseTrafficLogVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <insert id="insertFsPublicCourseTrafficLog" parameterType="com.fs.course.domain.FsPublicCourseTrafficLog"
+            useGeneratedKeys="true" keyProperty="logId">
+        insert into fs_public_course_traffic_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="uuId != null">uu_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="courseId != null">course_id,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="internetTraffic != null">internet_traffic,</if>
+            <if test="status != null">status,</if>
+            create_time,
+            <if test="projectId != null">project_id,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="uuId != null">#{uuId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="courseId != null">#{courseId},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="internetTraffic != null">#{internetTraffic},</if>
+            <if test="status != null">#{status},</if>
+            sysdate(),
+            <if test="projectId != null">#{projectId},</if>
+        </trim>
+    </insert>
+
+    <update id="updateFsPublicCourseTrafficLog" parameterType="com.fs.course.domain.FsPublicCourseTrafficLog">
+        update fs_public_course_traffic_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="uuId != null">uu_id = #{uuId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="internetTraffic != null">internet_traffic = #{internetTraffic},</if>
+            <if test="status != null">status = #{status},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <insert id="insertOrUpdateTrafficLog" parameterType="com.fs.course.domain.FsPublicCourseTrafficLog">
+        insert into fs_public_course_traffic_log (uu_id, user_id, course_id, video_id,
+                                                  internet_traffic, status, create_time, project_id)
+        values (#{uuId}, #{userId}, #{courseId}, #{videoId}, #{internetTraffic}, #{status}, sysdate(),
+                #{projectId}) ON DUPLICATE KEY
+        UPDATE
+            internet_traffic = internet_traffic + #{internetTraffic}
+    </insert>
+
+    <select id="selectTrafficNew" resultType="com.fs.course.vo.FsCourseTrafficLogListVO">
+        select
+        log.course_id,
+        SUM(log.internet_traffic) AS total_internet_traffic,
+        DATE_FORMAT(log.create_time, '%Y-%m-%d') AS `month`
+        FROM fs_public_course_traffic_log log
+        <where>
+            <if test="startDate != null and endDate != null">
+                and DATE_FORMAT(log.create_time, '%Y-%m-%d') between #{startDate} AND #{endDate}
+            </if>
+            <if test="courseId != null">
+                and log.course_id = ${courseId}
+            </if>
+        </where>
+        group by log.course_id,`month`
+    </select>
+
+</mapper>

+ 193 - 0
fs-service/src/main/resources/mapper/his/AppOperationReportMapper.xml

@@ -0,0 +1,193 @@
+<?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.AppOperationReportMapper">
+
+    <select id="selectNewUsers" resultType="Long">
+        SELECT COUNT(DISTINCT u.user_id)
+        FROM fs_user_company_user ucu
+        INNER JOIN fs_user u ON ucu.user_id = u.user_id
+        WHERE (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+        AND u.status = 1
+        AND u.is_del = 0
+        AND ucu.status IN (0, 1)
+        <if test="startDate != null and startDate != ''">
+            AND u.app_create_time >= #{startDate}
+        </if>
+        <if test="endDate != null and endDate != ''">
+            AND u.app_create_time &lt;= #{endDate}
+        </if>
+    </select>
+
+    <select id="selectTotalUsers" resultType="Long">
+        SELECT COUNT(DISTINCT u.user_id)
+        FROM fs_user_company_user ucu
+        INNER JOIN fs_user u ON ucu.user_id = u.user_id
+        WHERE (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+        AND u.status = 1
+        AND u.is_del = 0
+        AND ucu.status IN (0, 1)
+    </select>
+
+    <select id="selectActiveUsers" resultType="Long">
+        SELECT COUNT(DISTINCT a.user_id)
+        FROM fs_app_active_user_daily a
+        INNER JOIN fs_user u ON a.user_id = u.user_id
+        WHERE (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+        AND u.status = 1
+        AND u.is_del = 0
+        AND a.stat_date >= #{startDate}
+        AND a.stat_date &lt;= #{endDate}
+    </select>
+
+    <select id="selectRetainedUsers" resultType="Long">
+        SELECT COUNT(DISTINCT u.user_id)
+        FROM fs_user_company_user ucu
+        INNER JOIN fs_user u ON ucu.user_id = u.user_id
+        WHERE (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+        AND u.status = 1
+        AND u.is_del = 0
+        AND ucu.status IN (0, 1)
+        AND u.app_create_time >= #{newUserStartDate}
+        AND u.app_create_time &lt;= #{newUserEndDate}
+        AND u.user_id IN (
+            SELECT DISTINCT a.user_id
+            FROM fs_app_active_user_daily a
+            INNER JOIN fs_user u2 ON a.user_id = u2.user_id
+            WHERE (u2.source IS NOT NULL OR u2.login_device IS NOT NULL OR u2.app_create_time IS NOT NULL)
+            AND u2.status = 1
+            AND u2.is_del = 0
+            AND a.stat_date >= #{activeStartDate}
+            AND a.stat_date &lt;= #{activeEndDate}
+        )
+    </select>
+
+    <select id="selectTotalWatchDuration" resultType="Long">
+        SELECT IFNULL(SUM(log.duration), 0)
+        FROM fs_course_watch_log log
+        WHERE log.create_time >= #{startDate}
+        AND log.create_time &lt;= #{endDate}
+        AND log.user_id IN (
+            SELECT DISTINCT a.user_id
+            FROM fs_app_active_user_daily a
+            INNER JOIN fs_user u ON a.user_id = u.user_id
+            WHERE (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+            AND u.status = 1
+            AND u.is_del = 0
+            AND a.stat_date >= DATE(#{startDate})
+            AND a.stat_date &lt;= DATE(#{endDate})
+        )
+    </select>
+
+    <select id="selectTotalOrderAmount" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(amount), 0) FROM (
+            SELECT SUM(o.pay_money) AS amount
+            FROM fs_store_order o
+            WHERE o.status IN (1, 2, 3, 4)
+            AND o.create_time &lt;= #{endDate}
+            AND EXISTS (
+                SELECT 1 FROM fs_user_company_user ucu
+                INNER JOIN fs_user u ON ucu.user_id = u.user_id
+                WHERE ucu.user_id = o.user_id 
+                AND (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+                AND u.status = 1 AND u.is_del = 0
+                AND ucu.status IN (0, 1)
+            )
+            UNION ALL
+            SELECT SUM(o.pay_money) AS amount
+            FROM fs_package_order o
+            WHERE o.status IN (1, 2, 3, 4)
+            AND o.create_time &lt;= #{endDate}
+            AND EXISTS (
+                SELECT 1 FROM fs_user_company_user ucu
+                INNER JOIN fs_user u ON ucu.user_id = u.user_id
+                WHERE ucu.user_id = o.user_id 
+                AND (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+                AND u.status = 1 AND u.is_del = 0
+                AND ucu.status IN (0, 1)
+            )
+            UNION ALL
+            SELECT SUM(o.pay_money) AS amount
+            FROM fs_inquiry_order o
+            WHERE o.status IN (1, 2, 3, 4)
+            AND o.create_time &lt;= #{endDate}
+            AND EXISTS (
+                SELECT 1 FROM fs_user_company_user ucu
+                INNER JOIN fs_user u ON ucu.user_id = u.user_id
+                WHERE ucu.user_id = o.user_id 
+                AND (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+                AND u.status = 1 AND u.is_del = 0
+                AND ucu.status IN (0, 1)
+            )
+            UNION ALL
+            SELECT SUM(o.pay_money) AS amount
+            FROM fs_integral_order o
+            WHERE o.status IN (1, 2, 3, 4)
+            AND o.create_time &lt;= #{endDate}
+            AND EXISTS (
+                SELECT 1 FROM fs_user_company_user ucu
+                INNER JOIN fs_user u ON ucu.user_id = u.user_id
+                WHERE ucu.user_id = o.user_id 
+                AND (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+                AND u.status = 1 AND u.is_del = 0
+                AND ucu.status IN (0, 1)
+            )
+        ) t
+    </select>
+
+    <select id="selectTotalRedPacketAmount" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(r.amount), 0)
+        FROM fs_course_red_packet_log r
+        WHERE r.create_time >= #{startDate}
+        AND r.create_time &lt;= #{endDate}
+        AND EXISTS (
+            SELECT 1 FROM fs_user_company_user ucu
+            INNER JOIN fs_user u ON ucu.user_id = u.user_id
+            WHERE ucu.user_id = r.user_id 
+            AND (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+            AND u.status = 1 AND u.is_del = 0
+            AND ucu.status IN (0, 1)
+        )
+    </select>
+
+    <select id="selectLastMonthActiveUsers" resultType="Long">
+        SELECT COUNT(DISTINCT a.user_id)
+        FROM fs_app_active_user_daily a
+        INNER JOIN fs_user u ON a.user_id = u.user_id
+        WHERE (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+        AND u.status = 1
+        AND u.is_del = 0
+        AND a.stat_date >= #{lastMonthStart}
+        AND a.stat_date &lt;= #{lastMonthEnd}
+    </select>
+
+    <select id="selectCurrentMonthInactiveUsers" resultType="Long">
+        SELECT COUNT(DISTINCT a.user_id)
+        FROM fs_app_active_user_daily a
+        INNER JOIN fs_user u ON a.user_id = u.user_id
+        WHERE (u.source IS NOT NULL OR u.login_device IS NOT NULL OR u.app_create_time IS NOT NULL)
+        AND u.status = 1
+        AND u.is_del = 0
+        AND a.stat_date >= #{lastMonthStart}
+        AND a.stat_date &lt;= #{lastMonthEnd}
+        AND a.user_id NOT IN (
+            SELECT DISTINCT a2.user_id
+            FROM fs_app_active_user_daily a2
+            INNER JOIN fs_user u2 ON a2.user_id = u2.user_id
+            WHERE (u2.source IS NOT NULL OR u2.login_device IS NOT NULL OR u2.app_create_time IS NOT NULL)
+            AND u2.status = 1
+            AND u2.is_del = 0
+            AND a2.stat_date >= #{currentMonthStart}
+            AND a2.stat_date &lt;= #{currentMonthEnd}
+        )
+    </select>
+
+    <select id="selectMonthReport" resultType="com.fs.his.vo.AppOperationReportVO">
+        SELECT 
+            #{year} AS year,
+            #{month} AS month,
+            '月度统计' AS period
+    </select>
+
+</mapper>

+ 34 - 0
fs-service/src/main/resources/mapper/his/FsAppActiveUserDailyMapper.xml

@@ -0,0 +1,34 @@
+<?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.FsAppActiveUserDailyMapper">
+
+    <insert id="batchInsert">
+        INSERT IGNORE INTO fs_app_active_user_daily (stat_date, user_id, create_time)
+        VALUES
+        <foreach collection="userIds" item="userId" separator=",">
+            (#{statDate}, #{userId}, NOW())
+        </foreach>
+    </insert>
+
+    <select id="selectActiveUserCount" resultType="Long">
+        SELECT COUNT(DISTINCT a.user_id)
+        FROM fs_app_active_user_daily a
+        INNER JOIN fs_user_company_user ucu ON a.user_id = ucu.user_id
+        INNER JOIN fs_user u ON ucu.user_id = u.user_id
+        WHERE ((u.source IS NOT NULL AND u.source != '') OR (u.login_device IS NOT NULL AND u.login_device != '') OR u.app_create_time IS NOT NULL)
+        AND u.status = 1
+        AND u.is_del = 0
+        AND ucu.status IN (0, 1)
+        AND a.stat_date >= #{startDate}
+        AND a.stat_date &lt;= #{endDate}
+    </select>
+
+    <select id="selectUserIdsByDate" resultType="Long">
+        SELECT DISTINCT user_id
+        FROM fs_app_active_user_daily
+        WHERE stat_date = #{statDate}
+    </select>
+
+</mapper>

+ 2 - 9
fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml

@@ -34,7 +34,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFsIntegralOrderVo">
-        select order_id, order_code, user_id,bar_code, user_name, user_phone, user_address, item_json, integral,total_integral,discount_integral,total_money,discount_money,pay_money,user_coupon_id,is_pay,pay_time,pay_type, status, delivery_code, delivery_name, delivery_sn, delivery_time, create_time,qw_user_id,company_user_id,company_id,remark,login_account from fs_integral_order
+        select * from fs_integral_order
     </sql>
 
     <select id="selectFsIntegralOrderList" parameterType="FsIntegralOrder" resultMap="FsIntegralOrderResult">
@@ -112,12 +112,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userAddress != null">#{userAddress},</if>
             <if test="itemJson != null">#{itemJson},</if>
             <if test="integral != null">#{integral},</if>
-            <if test="totalIntegral != null">#{totalIntegral},</if>
-            <if test="discountIntegral != null">#{discountIntegral},</if>
-            <if test="totalMoney != null">#{totalMoney},</if>
-            <if test="discountMoney != null">#{discountMoney},</if>
             <if test="payMoney != null">#{payMoney},</if>
-            <if test="userCouponId != null">#{userCouponId},</if>
             <if test="isPay != null">#{isPay},</if>
             <if test="payTime != null">#{payTime},</if>
             <if test="payType != null">#{payType},</if>
@@ -132,9 +127,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="qwUserId != null">#{qwUserId},</if>
             <if test="companyUserId != null">#{companyUserId},</if>
             <if test="companyId != null">#{companyId},</if>
-            <if test="couponId != null">#{couponId},</if>
-            <if test="totalPrice != null">#{totalPrice},</if>
-            <if test="discountPrice != null">#{discountPrice},</if>
          </trim>
     </insert>
 
@@ -166,6 +158,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">remark = #{remark},</if>
             <if test="barCode != null">bar_code = #{barCode},</if>
             <if test="loginAccount != '' and loginAccount != null">login_account = #{loginAccount},</if>
+            <if test="extendOrderId != null">extend_order_id = #{extendOrderId},</if>
         </trim>
         where order_id = #{orderId}
     </update>

+ 5 - 1
fs-service/src/main/resources/mapper/his/FsStoreMapper.xml

@@ -35,10 +35,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="deliveryType"    column="delivery_type"    />
         <result property="sendPhone"    column="send_phone"    />
         <result property="shopCode"    column="shop_code"    />
+        <result property="chainBrands"    column="chain_brands"    />
     </resultMap>
 
     <sql id="selectFsStoreVo">
-        select store_id,full_name,delivery_type,send_phone, brokerage_rate,refund_phone,refund_address,refund_consignee,city_ids, store_name, descs, brokerage_type,logo_url, address, lng, lat, phone, license_images, product_count, status, create_time, update_time, sales_count, balance, total_money, is_audit, account, password, shipping_type, shop_code from fs_store
+        select store_id,full_name,delivery_type,send_phone, brokerage_rate,refund_phone,refund_address,refund_consignee,city_ids, store_name, descs, brokerage_type,logo_url, address, lng, lat, phone, license_images, product_count, status, create_time, update_time, sales_count, balance, total_money, is_audit, account, password, shipping_type, shop_code, chain_brands from fs_store
     </sql>
 
     <select id="selectFsStoreList" parameterType="FsStore" resultMap="FsStoreResult">
@@ -91,6 +92,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="deliveryType != null">delivery_type,</if>
             <if test="sendPhone != null">send_phone,</if>
             <if test="shopCode != null">shop_code,</if>
+            <if test="chainBrands != null">chain_brands,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="cityIds != null">#{cityIds},</if>
@@ -122,6 +124,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="deliveryType != null">#{deliveryType},</if>
             <if test="sendPhone != null">#{sendPhone},</if>
             <if test="shopCode != null">#{shopCode},</if>
+            <if test="chainBrands != null">#{chainBrands},</if>
         </trim>
     </insert>
 
@@ -157,6 +160,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="deliveryType != null">delivery_type = #{deliveryType},</if>
             <if test="sendPhone != null">send_phone = #{sendPhone},</if>
             <if test="shopCode != null">shop_code = #{shopCode},</if>
+            <if test="chainBrands != null">chain_brands = #{chainBrands},</if>
         </trim>
         where store_id = #{storeId}
     </update>

+ 132 - 0
fs-service/src/main/resources/mapper/his/FsStoreOrderFinanceAuditMapper.xml

@@ -0,0 +1,132 @@
+<?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.FsStoreOrderFinanceAuditMapper">
+    
+    <resultMap type="FsStoreOrderFinanceAudit" id="FsStoreOrderFinanceAuditResult">
+        <id     property="id"                  column="id"                  />
+        <result property="orderId"             column="order_id"             />
+        <result property="orderCode"           column="order_code"           />
+        <result property="auditType"           column="audit_type"           />
+        <result property="originalTotalPrice"  column="original_total_price" />
+        <result property="originalPayPrice"    column="original_pay_price"   />
+        <result property="newTotalPrice"       column="new_total_price"      />
+        <result property="newPayPrice"         column="new_pay_price"        />
+        <result property="priceChangeReason"   column="price_change_reason"  />
+        <result property="auditStatus"         column="audit_status"         />
+        <result property="auditUserId"         column="audit_user_id"        />
+        <result property="auditUserName"       column="audit_user_name"      />
+        <result property="auditTime"           column="audit_time"           />
+        <result property="auditRemark"         column="audit_remark"         />
+        <result property="voucherImages"       column="voucher_images"       />
+        <result property="voucherRemark"       column="voucher_remark"       />
+        <result property="applyUserId"         column="apply_user_id"        />
+        <result property="applyUserName"       column="apply_user_name"      />
+        <result property="applyTime"           column="apply_time"           />
+        <result property="createTime"          column="create_time"          />
+        <result property="updateTime"          column="update_time"          />
+    </resultMap>
+
+    <sql id="selectFsStoreOrderFinanceAuditVo">
+        select id, order_id, order_code, audit_type, original_total_price, original_pay_price,
+               new_total_price, new_pay_price, price_change_reason, audit_status, audit_user_id,
+               audit_user_name, audit_time, audit_remark, voucher_images, voucher_remark,
+               apply_user_id, apply_user_name, apply_time, create_time, update_time
+        from fs_store_order_finance_audit
+    </sql>
+
+    <select id="selectFsStoreOrderFinanceAuditList" parameterType="FsStoreOrderFinanceAudit" resultMap="FsStoreOrderFinanceAuditResult">
+        <include refid="selectFsStoreOrderFinanceAuditVo"/>
+        <where>
+            <if test="orderId != null">and order_id = #{orderId}</if>
+            <if test="orderCode != null and orderCode != ''">and order_code = #{orderCode}</if>
+            <if test="auditType != null">and audit_type = #{auditType}</if>
+            <if test="auditStatus != null">and audit_status = #{auditStatus}</if>
+            <if test="applyUserId != null">and apply_user_id = #{applyUserId}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectFsStoreOrderFinanceAuditById" parameterType="Long" resultMap="FsStoreOrderFinanceAuditResult">
+        <include refid="selectFsStoreOrderFinanceAuditVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectByOrderId" parameterType="Long" resultMap="FsStoreOrderFinanceAuditResult">
+        <include refid="selectFsStoreOrderFinanceAuditVo"/>
+        where order_id = #{orderId}
+        order by create_time desc
+        limit 1
+    </select>
+
+    <insert id="insertFsStoreOrderFinanceAudit" parameterType="FsStoreOrderFinanceAudit" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_store_order_finance_audit
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="orderId != null">order_id,</if>
+            <if test="orderCode != null and orderCode != ''">order_code,</if>
+            <if test="auditType != null">audit_type,</if>
+            <if test="originalTotalPrice != null">original_total_price,</if>
+            <if test="originalPayPrice != null">original_pay_price,</if>
+            <if test="newTotalPrice != null">new_total_price,</if>
+            <if test="newPayPrice != null">new_pay_price,</if>
+            <if test="priceChangeReason != null">price_change_reason,</if>
+            <if test="auditStatus != null">audit_status,</if>
+            <if test="auditUserId != null">audit_user_id,</if>
+            <if test="auditUserName != null">audit_user_name,</if>
+            <if test="auditTime != null">audit_time,</if>
+            <if test="auditRemark != null">audit_remark,</if>
+            <if test="voucherImages != null">voucher_images,</if>
+            <if test="voucherRemark != null">voucher_remark,</if>
+            <if test="applyUserId != null">apply_user_id,</if>
+            <if test="applyUserName != null">apply_user_name,</if>
+            <if test="applyTime != null">apply_time,</if>
+            create_time
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="orderId != null">#{orderId},</if>
+            <if test="orderCode != null and orderCode != ''">#{orderCode},</if>
+            <if test="auditType != null">#{auditType},</if>
+            <if test="originalTotalPrice != null">#{originalTotalPrice},</if>
+            <if test="originalPayPrice != null">#{originalPayPrice},</if>
+            <if test="newTotalPrice != null">#{newTotalPrice},</if>
+            <if test="newPayPrice != null">#{newPayPrice},</if>
+            <if test="priceChangeReason != null">#{priceChangeReason},</if>
+            <if test="auditStatus != null">#{auditStatus},</if>
+            <if test="auditUserId != null">#{auditUserId},</if>
+            <if test="auditUserName != null">#{auditUserName},</if>
+            <if test="auditTime != null">#{auditTime},</if>
+            <if test="auditRemark != null">#{auditRemark},</if>
+            <if test="voucherImages != null">#{voucherImages},</if>
+            <if test="voucherRemark != null">#{voucherRemark},</if>
+            <if test="applyUserId != null">#{applyUserId},</if>
+            <if test="applyUserName != null">#{applyUserName},</if>
+            <if test="applyTime != null">#{applyTime},</if>
+            sysdate()
+        </trim>
+    </insert>
+
+    <update id="updateFsStoreOrderFinanceAudit" parameterType="FsStoreOrderFinanceAudit">
+        update fs_store_order_finance_audit
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="auditStatus != null">audit_status = #{auditStatus},</if>
+            <if test="auditUserId != null">audit_user_id = #{auditUserId},</if>
+            <if test="auditUserName != null">audit_user_name = #{auditUserName},</if>
+            <if test="auditTime != null">audit_time = #{auditTime},</if>
+            <if test="auditRemark != null">audit_remark = #{auditRemark},</if>
+            update_time = sysdate()
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsStoreOrderFinanceAuditById" parameterType="Long">
+        delete from fs_store_order_finance_audit where id = #{id}
+    </delete>
+
+    <delete id="deleteFsStoreOrderFinanceAuditByIds" parameterType="String">
+        delete from fs_store_order_finance_audit where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 11 - 1
fs-service/src/main/resources/mapper/his/FsStoreProductMapper.xml

@@ -44,10 +44,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="tempId"    column="temp_id"    />
         <result property="isPrescribe"    column="is_prescribe"    />
         <result property="isDrug"    column="is_drug"    />
+        <result property="brand"    column="brand"    />
+        <result property="productSourceType"    column="product_source_type"    />
+        <result property="chainBrand"    column="chain_brand"    />
     </resultMap>
 
     <sql id="selectFsStoreProductVo">
-        select product_id,is_prescribe,is_drug, store_id, img_url, images, product_name, product_introduce, keyword, bar_code, cate_id, price, ot_price, unit_name, sort, sales, stock, is_show, is_hot, is_benefit, is_best, is_new, `desc`, create_time, update_time, is_postage, is_del, give_integral, cost_price, views, code_url, spec_type, product_type, prescribe_code, prescribe_spec, prescribe_factory, prescribe_name, is_display, temp_id,brand from fs_store_product
+        select product_id,is_prescribe,is_drug, store_id, img_url, images, product_name, product_introduce, keyword, bar_code, cate_id, price, ot_price, unit_name, sort, sales, stock, is_show, is_hot, is_benefit, is_best, is_new, `desc`, create_time, update_time, is_postage, is_del, give_integral, cost_price, views, code_url, spec_type, product_type, prescribe_code, prescribe_spec, prescribe_factory, prescribe_name, is_display, temp_id,brand,product_source_type,chain_brand from fs_store_product
     </sql>
 
     <select id="selectFsStoreProductList" parameterType="FsStoreProduct" resultMap="FsStoreProductResult">
@@ -141,6 +144,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isPrescribe != null">is_prescribe,</if>
             <if test="isDrug != null">is_drug,</if>
             <if test="brand != null">brand,</if>
+            <if test="productSourceType !=null">product_source_type</if>
+            <if test="chainBrand != null">chain_brand,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="storeId != null">#{storeId},</if>
@@ -182,6 +187,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isPrescribe != null">#{isPrescribe},</if>
             <if test="isDrug != null">#{isDrug},</if>
             <if test="brand != null">#{brand},</if>
+            <if test="productSourceType !=null">#{productSourceType},</if>
+            <if test="chainBrand != null">#{chainBrand},</if>
          </trim>
     </insert>
 
@@ -226,6 +233,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isDisplay != null">is_display = #{isDisplay},</if>
             <if test="tempId != null">temp_id = #{tempId},</if>
             <if test="isDrug != null">is_drug = #{isDrug},</if>
+            <if test="brand != null">brand = #{brand},</if>
+            <if test="productSourceType !=null">product_source_type = #{productSourceType},</if>
+            <if test="chainBrand != null">chain_brand = #{chainBrand},</if>
         </trim>
         where product_id = #{productId}
     </update>

+ 12 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -151,6 +151,18 @@ public class CourseFsUserController extends AppBaseController {
         return courseVideoService.getInternetTraffic(param);
     }
 
+    /**
+     * 获取公开课流量
+     * @return
+     */
+    @ApiOperation("获取公开课缓冲流量")
+    @PostMapping("/getPublicCourseInternetTraffic")
+    @Login
+    public R getPublicCourseInternetTraffic(@RequestBody FsUserCourseVideoFinishUParam param ){
+        param.setUserId(Long.parseLong(getUserId()));
+        return courseVideoService.getPublicCourseInternetTraffic(param);
+    }
+
 
     @ApiOperation("答题")
     @PostMapping("/courseAnswer")