Prechádzať zdrojové kódy

Merge remote-tracking branch 'origin/ScrmStores' into ScrmStores

吴树波 6 dní pred
rodič
commit
8f057bc8b5
45 zmenil súbory, kde vykonal 992 pridanie a 107 odobranie
  1. 5 0
      README.md
  2. 4 4
      fs-admin/src/main/java/com/fs/company/controller/CompanyController.java
  3. 4 4
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  4. 19 20
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductScrmController.java
  5. 20 3
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreVerifyCodeScrmController.java
  6. 0 1
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  7. 3 0
      fs-common/src/main/java/com/fs/common/constant/FsConstants.java
  8. 22 0
      fs-common/src/main/java/com/fs/common/utils/txocr/IdCardVerificationResponse.java
  9. 32 1
      fs-common/src/main/java/com/fs/common/utils/txocr/TxOcrClient.java
  10. 1 1
      fs-company-app/src/main/resources/application.yml
  11. 1 1
      fs-redis/src/main/resources/application.yml
  12. 1 1
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  13. 6 1
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  14. 0 3
      fs-service/src/main/java/com/fs/course/dto/FsStoreVerifyCodeDTO.java
  15. 9 0
      fs-service/src/main/java/com/fs/course/dto/WriteOffDTO.java
  16. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsDoctorMapper.java
  17. 9 0
      fs-service/src/main/java/com/fs/his/service/IFsDoctorService.java
  18. 6 0
      fs-service/src/main/java/com/fs/his/service/impl/FsDoctorServiceImpl.java
  19. 12 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreVerifyCodeScrm.java
  20. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreCartScrmMapper.java
  21. 1 2
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java
  22. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  23. 17 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreVerifyCodeScrmMapper.java
  24. 11 1
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreVerifyCodeScrmService.java
  25. 45 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  26. 182 9
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  27. 1 5
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  28. 2 2
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  29. 1 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreScrmServiceImpl.java
  30. 111 22
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreVerifyCodeScrmServiceImpl.java
  31. 2 2
      fs-service/src/main/java/com/fs/hisStore/service/listener/FsStoreScrmListener.java
  32. 1 1
      fs-service/src/main/java/com/fs/hisStore/vo/FsStorePaymentUsetVo.java
  33. 3 0
      fs-service/src/main/java/com/fs/store/vo/FsStoreCartScrmVO.java
  34. 4 0
      fs-service/src/main/resources/mapper/his/FsDoctorMapper.xml
  35. 1 1
      fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml
  36. 1 1
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml
  37. 27 8
      fs-service/src/main/resources/mapper/hisStore/FsStoreVerifyCodeScrmMapper.xml
  38. 2 2
      fs-service/src/main/resources/mapper/system/SysDeptMapper.xml
  39. 194 0
      fs-store/src/main/java/com/fs/hisStore/controller/store/FsStoreVerifyCodeScrmController.java
  40. 8 0
      fs-user-app/src/main/java/com/fs/app/controller/UserController.java
  41. 14 1
      fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java
  42. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/store/StoreInfoCheckController.java
  43. 1 4
      fs-user-app/src/main/java/com/fs/app/exception/FSExceptionHandler.java
  44. 24 0
      fs-user-app/src/main/java/com/fs/app/redis/RedisConfiguration.java
  45. 180 0
      fs-user-app/src/main/java/com/fs/app/redis/RedisKeyExpirationListener.java

+ 5 - 0
README.md

@@ -23,6 +23,11 @@
 1.  注意调整Memory的大小,以及堆内存大小
 2.  对于maven仓库缺少的jar包引用,需要拷贝现有的文件(拷贝后依然出现错误,直接删除错误包下的_remote.repositories文件)。
 
+#### 身份证返回接口代码
+https://cloud.tencent.com/document/product/1007/33188?from=console_top_search
+
+
+
 
 
 #### sql更新

+ 4 - 4
fs-admin/src/main/java/com/fs/company/controller/CompanyController.java

@@ -116,9 +116,9 @@ public class CompanyController extends BaseController
     public R add(@RequestBody Company company)
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        if(company.getDeptId() != null){
-            company.setDeptId(loginUser.getDeptId());
-        }
+//        if(company.getDeptId() != null){//位置迁移到实现类中
+//            company.setDeptId(loginUser.getDeptId());
+//        }
         company.setCreateBy(loginUser.getUsername());
         company.setOldDeptId(loginUser.getDeptId());
         company.setAdmin(loginUser.isAdmin());
@@ -126,7 +126,7 @@ public class CompanyController extends BaseController
         company.setAppId(Md5Utils.hash(company.getUserName()));
         company.setAppKey(Md5Utils.hash(company.getPassword()));
         company.setOmsCode("SF.1");
-        return companyService.insertCompany(company);
+        return companyService.insertCompany(company,loginUser.getDeptId());
     }
 
     /**

+ 4 - 4
fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java

@@ -152,13 +152,13 @@ public class FsCompanyController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
-            company.setDeptId(loginUser.getDeptId());
-        }
+//        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
+//            company.setDeptId(loginUser.getDeptId());
+//        }
         company.setAdmin(loginUser.isAdmin());
         company.setOldDeptId(loginUser.getDeptId());
         company.setCreateBy(loginUser.getUsername());
-        return companyService.insertCompany(company);
+        return companyService.insertCompany(company,loginUser.getDeptId());
     }
 
     /**

+ 19 - 20
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductScrmController.java

@@ -110,6 +110,25 @@ public class FsStoreProductScrmController extends BaseController {
         return R.ok();
     }
 
+    @PreAuthorize("@ss.hasPermi('store:storeProduct:add')")
+    @Log(title = "商品管理", businessType = BusinessType.INSERT, businessTypeExpression = "#p0.getProductId()>0? T(com.fs.common.enums.BusinessType).UPDATE: T(com.fs.common.enums.BusinessType).INSERT"
+            , isStoreLog = true, logParamExpression = "#p0.getProductId()>0? new String[]{'商品','修改商品'}: new String[]{'商品','新增商品'}")
+    @PostMapping(value = "/addOrEdit")
+    public R addOrEdit(@RequestBody FsStoreProductAddEditParam fsStoreProduct) {
+        if (ObjectUtils.isNotNull(fsStoreProduct.getIsShow())) {
+            if (fsStoreProduct.getIsShow() == 1) {
+                logger.info("商品上架:{}", fsStoreProduct.getProductName() + new Date());
+            }
+        }
+
+        if (ObjectUtils.isNotNull(fsStoreProduct.getIsDisplay())) {
+            if (fsStoreProduct.getIsDisplay() == 1) {
+                logger.info("商品前端展示:{}", fsStoreProduct.getProductName() + new Date());
+            }
+        }
+        return fsStoreProductService.addOrEdit(fsStoreProduct);
+    }
+
     @PreAuthorize("@ss.hasPermi('store:storeProduct:list')")
     @PostMapping("/batchAudit")
     @Log(title = "商品审核", businessType = BusinessType.AUDIT, isStoreLog = true, logParam = {"商品", "批量审核商品信息"},
@@ -192,26 +211,6 @@ public class FsStoreProductScrmController extends BaseController {
         return R.ok().put("data", product).put("attrs", attrs);
     }
 
-    @PreAuthorize("@ss.hasPermi('store:storeProduct:add')")
-    @Log(title = "商品管理", businessType = BusinessType.INSERT, businessTypeExpression = "#p0.getProductId()>0? T(com.fs.common.enums.BusinessType).UPDATE: T(com.fs.common.enums.BusinessType).INSERT"
-            , isStoreLog = true, logParamExpression = "#p0.getProductId()>0? new String[]{'商品','修改商品'}: new String[]{'商品','新增商品'}")
-    @PostMapping(value = "/addOrEdit")
-    public R addOrEdit(@RequestBody FsStoreProductAddEditParam fsStoreProduct) {
-        if (ObjectUtils.isNotNull(fsStoreProduct.getIsShow())) {
-            if (fsStoreProduct.getIsShow() == 1) {
-                logger.info("商品上架:{}", fsStoreProduct.getProductName() + new Date());
-            }
-        }
-
-        if (ObjectUtils.isNotNull(fsStoreProduct.getIsDisplay())) {
-            if (fsStoreProduct.getIsDisplay() == 1) {
-                logger.info("商品前端展示:{}", fsStoreProduct.getProductName() + new Date());
-            }
-        }
-        return fsStoreProductService.addOrEdit(fsStoreProduct);
-    }
-
-
     /**
      * 删除商品
      */

+ 20 - 3
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreVerifyCodeScrmController.java

@@ -8,6 +8,7 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 
 import com.fs.course.dto.FsStoreVerifyCodeDTO;
+import com.fs.course.dto.WriteOffDTO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.hisStore.domain.FsStoreVerifyCodeScrm;
 import com.fs.hisStore.service.IFsStoreVerifyCodeScrmService;
@@ -129,9 +130,10 @@ public class FsStoreVerifyCodeScrmController extends BaseController
      * @param file 导入文件
      * @return R
      * **/
-    @Log(title = "发货同步导入", businessType = BusinessType.IMPORT)
+    @Log(title = "核销码批量导入", businessType = BusinessType.IMPORT)
+    @PreAuthorize("@ss.hasPermi('shop:scrm:importExpress')")
     @PostMapping("/importExpress")
-    public R importExpress(@RequestParam("file") MultipartFile file) {
+    public R importExpress(@RequestParam("file") MultipartFile file,@RequestParam("productId") Long productId) {
         // 1. 检查文件是否为空
         if (file.isEmpty()) {
             return R.error("上传的文件不能为空");
@@ -154,7 +156,7 @@ public class FsStoreVerifyCodeScrmController extends BaseController
                     R.error("操作失败,导入数据不能大于200条!");
                 }
                 LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-                return fsStoreVerifyCodeScrmService.importExpress(dtoList,String.valueOf(loginUser.getUserId()));
+                return fsStoreVerifyCodeScrmService.importExpress(dtoList,String.valueOf(loginUser.getUserId()),productId);
             }else {
                 R.error("操作失败,导入数据不能小于1条!");
             }
@@ -164,6 +166,21 @@ public class FsStoreVerifyCodeScrmController extends BaseController
         return R.ok();
     }
 
+    /**
+     * 核销码核销
+     * @return R
+     * **/
+    @PreAuthorize("@ss.hasPermi('shop:scrm:writeOff')")
+    @Log(title = "核销码核销", businessType = BusinessType.IMPORT)
+    @PostMapping("/writeOff")
+    public R writeOff(@RequestBody WriteOffDTO writeOffDTO){
+        if (writeOffDTO.getIds() == null || writeOffDTO.getIds().length == 0) {
+            return R.error("操作失败,核销ID列表不能为空!");
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        return fsStoreVerifyCodeScrmService.writeOff(writeOffDTO.getIds(),String.valueOf(loginUser.getUserId()));
+    }
+
 
     // 检查文件是否为有效的Excel文件
     private boolean isValidExcelFile(String fileName) {

+ 0 - 1
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -220,7 +220,6 @@ public class MallStoreTask
                                     redisCache.deleteObject(DELIVERY+":"+order.getExtendOrderId());
                                 }
                             }
-
                         }
                     }
 

+ 3 - 0
fs-common/src/main/java/com/fs/common/constant/FsConstants.java

@@ -18,4 +18,7 @@ public interface FsConstants {
     String COMPANY_MONEY_LOCK = "company_money_lock:";
     // 看客统计  按公司分组 按TimeType 0-今天,1-昨天,2-本周,3-本月,4-上月;
     String WATCH_COURSE_STATISTICS_GROUP_COMPANY = "watch_course_statistics:group_company:";
+
+    //商城订单过期
+    String REDIS_ORDER_UNPAY = "order:unpay:";
 }

+ 22 - 0
fs-common/src/main/java/com/fs/common/utils/txocr/IdCardVerificationResponse.java

@@ -0,0 +1,22 @@
+package com.fs.common.utils.txocr;
+
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/12/1 下午4:17
+ */
+@Data
+@ToString
+public class IdCardVerificationResponse {
+
+
+  private String description;
+
+  private Integer result;
+
+
+
+}

+ 32 - 1
fs-common/src/main/java/com/fs/common/utils/txocr/TxOcrClient.java

@@ -1,7 +1,10 @@
 package com.fs.common.utils.txocr;
 
+import cn.hutool.json.JSONObject;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
 import com.tencentcloudapi.common.AbstractModel;
 import com.tencentcloudapi.common.CommonClient;
 import com.tencentcloudapi.common.Credential;
@@ -14,6 +17,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -27,6 +32,28 @@ public class TxOcrClient  {
 
     private static final String YPLS = "药品零售";
 
+    public static IdCardVerificationResponse IdCardVerification(String name, String idNumber) {
+        IdCardVerificationResponse map = new IdCardVerificationResponse();
+        try {
+            Credential cred = new Credential("AKIDviPyMZbRp24udCcpqjQxHOK4cx88ze6N", "97tVwEJE81sY0StDPPGukQ2ZvkU3QceY");
+            HttpProfile httpProfile = new HttpProfile();
+            httpProfile.setEndpoint("faceid.tencentcloudapi.com");
+            ClientProfile clientProfile = new ClientProfile();
+            clientProfile.setHttpProfile(httpProfile);
+            CommonClient client = new CommonClient("faceid", "2018-03-01", cred, "", clientProfile);
+            String params = "{\"IdCard\":\""+idNumber+"\",\"Name\":\""+name+"\"}";
+            String resp = client.call("IdCardVerification", params);
+            JSONObject jsonObject = new JSONObject(resp);
+            JSONObject response = (JSONObject)jsonObject.get("Response");
+            map.setDescription(response.get("Description").toString());
+            map.setResult(Integer.parseInt(response.get("Result").toString()));
+        } catch (TencentCloudSDKException e) {
+            log.error("身份证识别请求异常:{}",e.getMessage());
+            map.setDescription("识别失败");
+            map.setResult(-8);
+        }
+        return map;
+    }
     /**
      * 医疗器械url识别
      * @return
@@ -258,9 +285,13 @@ public class TxOcrClient  {
     }
 
     public static void main(String[] args) {
-        ExtractDocBasic("https://bjyjb-1362704775.cos.ap-chongqing.myqcloud.com/fs/20251118/d175ed9771324c56a126779ef89bb1e3.jpg");
+//        ExtractDocBasic("https://bjyjb-1362704775.cos.ap-chongqing.myqcloud.com/fs/20251118/d175ed9771324c56a126779ef89bb1e3.jpg");
+        IdCardVerification("郭顺", "111");
     }
 
+
+
+
     /**
      * ClassifyDetectOCR
      * 支持身份证、护照、名片、银行卡、行驶证、驾驶证、港澳台通行证、户口本、港澳台来往内地通行证、港澳台居住证、不动产证、营业执照的智能分类。

+ 1 - 1
fs-company-app/src/main/resources/application.yml

@@ -6,4 +6,4 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-    active: dev-jnlzjk
+    active: dev-yjb

+ 1 - 1
fs-redis/src/main/resources/application.yml

@@ -4,7 +4,7 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: dev
+    active: dev-yjb
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz

+ 1 - 1
fs-service/src/main/java/com/fs/company/service/ICompanyService.java

@@ -53,7 +53,7 @@ public interface ICompanyService
      * @param company 企业
      * @return 结果
      */
-    public R insertCompany(Company company);
+    public R insertCompany(Company company,Long userDeptId);
 
     /**
      * 修改企业

+ 6 - 1
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -159,7 +159,7 @@ public class CompanyServiceImpl implements ICompanyService
      */
     @Override
     @Transactional
-    public R insertCompany(Company company)
+    public R insertCompany(Company company,Long userDeptId)
     {
         //帐号是否存在
         if(userMapper.checkUserName(company.getUserName())>0){
@@ -182,6 +182,11 @@ public class CompanyServiceImpl implements ICompanyService
             sysDeptConfigLogService.addLog(deptConfig.getDeptId(), 0, 1, padNum.toString(), first + "", subNum + "", deptConfig.getUpdateBy(), "设置公司PAD");
             sysDeptConfigService.updateById(deptConfig);
         }
+
+        if(company.getDeptId() != null){
+            company.setDeptId(userDeptId);
+        }
+
         if(companyMapper.insertCompany(company)>0){
             //写入帐号信息
             //创建部门

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

@@ -11,7 +11,4 @@ public class FsStoreVerifyCodeDTO {
      * **/
     @Excel(name = "溯源码(必填)",width = 20,sort = 1)
     private String verifyCode;
-
-    @Excel(name = "商品ID(必填)",width = 20,sort = 1)
-    private Long productId;
 }

+ 9 - 0
fs-service/src/main/java/com/fs/course/dto/WriteOffDTO.java

@@ -0,0 +1,9 @@
+package com.fs.course.dto;
+
+import lombok.Data;
+
+@Data
+public class WriteOffDTO {
+    //核销IDs
+    private Long[] ids;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/his/mapper/FsDoctorMapper.java

@@ -226,4 +226,6 @@ public interface FsDoctorMapper
      *
      * **/
     List<FsDoctorVO> getDoctorInfoByStoreId(@Param("param") FsDoctorParam param);
+
+    Boolean idCardExists(String idCard, Long userId);
 }

+ 9 - 0
fs-service/src/main/java/com/fs/his/service/IFsDoctorService.java

@@ -127,4 +127,13 @@ public interface IFsDoctorService
      * 根据门店id查询药师信息
      * **/
     List<FsDoctorVO> getDoctorInfoByStoreId(FsDoctorParam param);
+
+    /**
+     * 验证该身份证是否已经注册
+     * @param idCard
+     * @param userId
+     * @return
+     */
+    Boolean idCardExistsByUserId(String idCard, Long userId);
+
 }

+ 6 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsDoctorServiceImpl.java

@@ -561,4 +561,10 @@ public class FsDoctorServiceImpl implements IFsDoctorService
         return fsDoctorMapper.getDoctorInfoByStoreId(param);
     }
 
+
+    @Override
+    public Boolean idCardExistsByUserId(String idCard, Long userId) {
+        return fsDoctorMapper.idCardExists(idCard, userId);
+    }
+
 }

+ 12 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreVerifyCodeScrm.java

@@ -43,5 +43,17 @@ public class FsStoreVerifyCodeScrm extends BaseEntity{
     @Excel(name = "是否删除", readConverterExp = "0=未删除、1已删除")
     private Long isDel;
 
+    //订单id
+    private Long orderId;
+
+    //订单详情id
+    private Long orderItemId;
+
+    //门店id
+    private Long storeId;
+
+    //是否回收(0否1是)
+    private Long isRecycle;
+
 
 }

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreCartScrmMapper.java

@@ -171,7 +171,7 @@ public interface FsStoreCartScrmMapper
     @Select({"<script> " +
             "select q.* FROM (" +
             "select c.*,p.product_type,p.product_name,p.image as product_image,v.price,v.sku as product_attr_name," +
-            "v.image as product_attr_image,v.stock,s.store_id,s.store_name " +
+            "v.image as product_attr_image,v.stock,s.store_id,s.store_name,( SELECT COUNT(*) FROM fs_store_verify_code_scrm vc WHERE vc.product_id = p.product_id AND vc.outbound_status = 0 AND is_del = 0) AS num " +
             "from fs_store_cart_scrm c " +
             "inner join fs_store_product_scrm p on p.product_id=c.product_id " +
             "inner join fs_store_product_attr_value_scrm v on v.id=c.product_attr_value_id " +

+ 1 - 2
fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java

@@ -4,8 +4,7 @@ import java.util.List;
 import java.util.Map;
 
 import com.alibaba.fastjson.JSONObject;
-import com.fs.course.vo.FsStorePaymentUsetVo;
-import com.fs.his.domain.FsStorePayment;
+import com.fs.hisStore.vo.FsStorePaymentUsetVo;
 import com.fs.hisStore.domain.FsStorePaymentScrm;
 import com.fs.hisStore.param.FsStorePaymentParam;
 import com.fs.hisStore.param.FsStoreStatisticsParam;

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java

@@ -329,7 +329,7 @@ public interface FsStoreProductScrmMapper
 
     @Select({"<script> " +
             "  select p.*,pc.cate_name,hs.push_status from (SELECT ave.bar_code as bar_code,p.product_id, p.image, p.slider_image,p.product_name, p.product_info,p.keyword, p.cate_id, p.price, p.vip_price, " +
-            " p.ot_price,p.agent_price, p.postage,p.unit_name,p.sort,p.sales,p.stock,p.is_show,p.is_hot,p.is_benefit,p.is_best,p.is_new,p.description,p.create_time,p.update_time,p.is_postage,p.is_del,p.give_integral," +
+            " p.ot_price,p.agent_price, p.postage,p.unit_name,p.sort,( SELECT COUNT(*) FROM fs_store_verify_code_scrm vc WHERE vc.product_id = p.product_id AND vc.outbound_status = 1 AND is_del = 0) AS sales,( SELECT COUNT(*) FROM fs_store_verify_code_scrm vc WHERE vc.product_id = p.product_id AND vc.outbound_status = 0 AND is_del = 0) AS stock,p.is_show,p.is_hot,p.is_benefit,p.is_best,p.is_new,p.description,p.create_time,p.update_time,p.is_postage,p.is_del,p.give_integral," +
             " p.cost,p.is_good,p.browse,p.code_path,p.temp_id,p.spec_type,p.is_integral,p.integral,p.product_type,p.prescribe_code, p.prescribe_spec,p.prescribe_factory,p.prescribe_name,p.is_display,p.tui_cate_id" +
             " FROM fs_store_product_scrm p LEFT JOIN fs_store_product_attr_value_scrm ave on p.product_id=ave.product_id  WHERE ave.bar_code != '' and p.product_id is not null" +
             ") p left join fs_store_product_category_scrm pc on p.cate_id=pc.cate_id   " +

+ 17 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreVerifyCodeScrmMapper.java

@@ -4,6 +4,7 @@ package com.fs.hisStore.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.hisStore.domain.FsStoreVerifyCodeScrm;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
 
@@ -67,4 +68,20 @@ public interface FsStoreVerifyCodeScrmMapper extends BaseMapper<FsStoreVerifyCod
      * @param list 数据集合
      * **/
     void batchInsertVerifyCode(@Param("list") List<FsStoreVerifyCodeScrm> list);
+
+    /**
+     * 查询锁定未出库溯源码行锁
+     * @param productId 商品ID
+     * @param count 需要数量
+     * @return 锁定的溯源码列表
+     */
+    @Select("SELECT * FROM fs_store_verify_code_scrm " +
+            "WHERE product_id = #{productId} " +
+            "AND outbound_status = 0 " +
+            "AND is_del = 0 " +
+            "ORDER BY create_time ASC " +
+            "LIMIT #{count} FOR UPDATE SKIP LOCKED")
+    List<FsStoreVerifyCodeScrm> selectUnOutboundVerifyCodesWithLock(
+            @Param("productId") Long productId,
+            @Param("count") Integer count);
 }

+ 11 - 1
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreVerifyCodeScrmService.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
 import com.fs.course.dto.FsStoreVerifyCodeDTO;
 import com.fs.hisStore.domain.FsStoreVerifyCodeScrm;
+import org.springframework.web.bind.annotation.PathVariable;
 
 /**
  * 核销码Service接口
@@ -65,7 +66,16 @@ public interface IFsStoreVerifyCodeScrmService extends IService<FsStoreVerifyCod
      * 批量导入
      * @param verifyCodeDTOS
      * @param userId 用户ID
+     * @param productId 商品ID
      * @return R
      * **/
-    R importExpress(List<FsStoreVerifyCodeDTO> verifyCodeDTOS,String userId);
+    R importExpress(List<FsStoreVerifyCodeDTO> verifyCodeDTOS,String userId,Long productId);
+
+    /**
+     * 核销
+     * @param ids 核销码ID
+     * @param userId 用户id
+     * @return R
+     * **/
+    R writeOff(Long[] ids,String userId);
 }

+ 45 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -7,6 +7,7 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
@@ -85,6 +86,7 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 售后记录Service业务层处理
@@ -198,6 +200,12 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
     @Autowired
     private CloudHostProper cloudHostProper;
 
+    @Autowired
+    private FsStoreVerifyCodeScrmMapper verifyCodeScrmMapper;
+
+    @Autowired
+    private IFsStoreVerifyCodeScrmService verifyCodeScrmService;
+
     /**
      * 查询售后记录
      *
@@ -344,6 +352,25 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         order.setRefundReasonWapExplain(storeAfterSalesParam.getExplains());
         order.setRefundReasonTime(new Date());
         orderService.updateFsStoreOrder(order);
+
+        //判断发货类型
+        if(orderStatus == 2 || orderStatus == 3) {
+            List<FsStoreOrderItemVO> scrmList = fsStoreOrderItemMapper.selectMyFsStoreOrderItemListByOrderId(order.getId());
+            if (!scrmList.isEmpty()) {
+                List<Long> orderItemIds = scrmList.stream().map(FsStoreOrderItemVO::getItemId).collect(Collectors.toList());
+                //获取溯源码,进行回退
+                List<FsStoreVerifyCodeScrm> verifyCodes = verifyCodeScrmMapper.selectList(new LambdaQueryWrapper<FsStoreVerifyCodeScrm>().eq(FsStoreVerifyCodeScrm::getOrderId, order.getId()).in(FsStoreVerifyCodeScrm::getOrderItemId, orderItemIds).eq(FsStoreVerifyCodeScrm::getIsDel, "0"));
+                if (!verifyCodes.isEmpty()) {
+                    verifyCodes.forEach(v -> {
+                        v.setIsRecycle(0L);
+                    });
+                    //批量更新数据
+                    verifyCodeScrmService.updateBatchById(verifyCodes);
+                }
+
+            }
+        }
+
         //生成售后订单
         FsStoreAfterSalesScrm storeAfterSales = new FsStoreAfterSalesScrm();
         storeAfterSales.setOrderCode(storeAfterSalesParam.getOrderCode());
@@ -812,6 +839,24 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
                             payment.setStatus(-1);
                             payment.setRefundTime(new Date());
                             paymentService.updateFsStorePayment(payment);
+
+                            //溯源码状态更改
+                            //获取订单下的详情信息
+                            List<FsStoreOrderItemVO> scrmList = fsStoreOrderItemMapper.selectMyFsStoreOrderItemListByOrderId(order.getId());
+                            if (!scrmList.isEmpty()) {
+                                List<Long> orderItemIds = scrmList.stream().map(FsStoreOrderItemVO::getItemId).collect(Collectors.toList());
+                                //获取溯源码,进行回退
+                                List<FsStoreVerifyCodeScrm> verifyCodes = verifyCodeScrmMapper.selectList(new LambdaQueryWrapper<FsStoreVerifyCodeScrm>().eq(FsStoreVerifyCodeScrm::getOrderId, order.getId()).in(FsStoreVerifyCodeScrm::getOrderItemId, orderItemIds).eq(FsStoreVerifyCodeScrm::getIsRecycle,1L).eq(FsStoreVerifyCodeScrm::getIsDel, "0"));
+                                if (!verifyCodes.isEmpty()) {
+                                    verifyCodes.forEach(v -> {//回收可回退的
+                                        v.setVerifyStatus(0L);
+                                        v.setOutboundStatus(0L);
+                                    });
+                                    //批量更新数据
+                                    verifyCodeScrmService.updateBatchById(verifyCodes);
+                                }
+                            }
+
                         }else {
                             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                             return R.error(refund.getResp_desc());

+ 182 - 9
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -371,6 +371,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private FsStoreOrderDfMapper fsStoreOrderDfMapper;
 
+    @Autowired
+    private FsStoreVerifyCodeScrmMapper verifyCodeScrmMapper;
+
+    @Autowired
+    private IFsStoreVerifyCodeScrmService verifyCodeScrmService;
+
     @PostConstruct
     public void initErpServiceMap() {
         erpServiceMap = new HashMap<>();
@@ -384,6 +390,14 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private Hospital580PrescriptionScrmMapper prescriptionScrmMapper;
 
+    //溯源码
+    @Autowired
+    private IFsStoreVerifyCodeScrmService verifyCodeService;
+
+    @Autowired
+    private FsStoreVerifyCodeScrmMapper verifyCodeMapper;
+
+
     /**
      * 查询订单
      *
@@ -434,6 +448,24 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             log.error("修改商城订单地址推送到聚水潭ERP失败,orderId: {}", fsStoreOrder.getId(), e);
         }
 
+        //判断发货类型
+        if(fsStoreOrder.getStatus() == 2 || fsStoreOrder.getStatus() == 3) {//待收货
+            List<FsStoreOrderItemVO> scrmList = fsStoreOrderItemMapper.selectMyFsStoreOrderItemListByOrderId(fsStoreOrder.getId());
+            if (!scrmList.isEmpty()) {
+                List<Long> orderItemIds = scrmList.stream().map(FsStoreOrderItemVO::getItemId).collect(Collectors.toList());
+                //获取溯源码,进行回退
+                List<FsStoreVerifyCodeScrm> verifyCodes = verifyCodeScrmMapper.selectList(new LambdaQueryWrapper<FsStoreVerifyCodeScrm>().eq(FsStoreVerifyCodeScrm::getOrderId, fsStoreOrder.getId()).in(FsStoreVerifyCodeScrm::getOrderItemId, orderItemIds).eq(FsStoreVerifyCodeScrm::getIsDel, "0"));
+                if (!verifyCodes.isEmpty()) {
+                    verifyCodes.forEach(v -> {
+                        v.setIsRecycle(0L);
+                    });
+                    //批量更新数据
+                    verifyCodeScrmService.updateBatchById(verifyCodes);
+                }
+
+            }
+        }
+
         return fsStoreOrderMapper.updateFsStoreOrder(fsStoreOrder);
     }
 
@@ -727,7 +759,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
 
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class, timeout = 20)
     public R createOrder(long userId, FsStoreOrderCreateParam param) {
         FsStoreOrderComputedParam computedParam = new FsStoreOrderComputedParam();
         BeanUtils.copyProperties(param, computedParam);
@@ -957,6 +989,34 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     item.setIsPrescribe(1);
                 }
                 fsStoreOrderItemMapper.insertFsStoreOrderItem(item);
+
+                // ===================== 溯源码核心逻辑 ==================
+                Integer buyCount = vo.getCartNum();
+                Long productId = vo.getProductId();
+                String productName = vo.getProductName();
+
+                if (buyCount <= 0) {
+                    throw new ServiceException("商品[" + productName + "]购买数量不能为0");
+                }
+
+                //查询并锁定未出库溯源码带行锁
+                List<FsStoreVerifyCodeScrm> availableVerifyCodes = verifyCodeMapper.selectUnOutboundVerifyCodesWithLock(productId, buyCount);
+                if (availableVerifyCodes == null || availableVerifyCodes.size() < buyCount) {
+                    String errorMsg = String.format("商品[%s]库存不足,需%s个,仅剩余%s个",
+                            productName, buyCount, (availableVerifyCodes == null ? 0 : availableVerifyCodes.size()));
+                    throw new ServiceException(errorMsg);
+                }
+
+                for (FsStoreVerifyCodeScrm code : availableVerifyCodes) {
+                    code.setOutboundStatus(1L);
+                    code.setOrderId(storeOrder.getId());
+                    code.setOrderItemId(item.getItemId());
+                    code.setUpdateTime(new Date());
+                }
+                if (!availableVerifyCodes.isEmpty()) {
+                    verifyCodeService.updateBatchById(availableVerifyCodes);
+                }
+                // ===================== 溯源码逻辑END =====================
                 listOrderItem.add(item);
             }
             if (listOrderItem.size() > 0) {
@@ -982,9 +1042,22 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     StoreConstants.REDIS_ORDER_OUTTIME_UNPAY, storeOrder.getId()));
 
             if (config.getUnPayTime() != null && config.getUnPayTime() > 0) {
-                redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
+                if(storeOrder.getIsPrescribe() != null && storeOrder.getIsPrescribe() == 1){//处方药72小时
+                    config.setUnPayTime(4320);
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(),4320, TimeUnit.MINUTES);
+                }else {
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
+                }
             } else {
-                redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
+                if(storeOrder.getIsPrescribe() != null && storeOrder.getIsPrescribe() == 1){//处方药
+                    if(config == null){
+                        config = new StoreConfig();
+                    }
+                    config.setUnPayTime(4320);
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(),4320, TimeUnit.MINUTES);
+                }else {
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
+                }
             }
             //添加支付到期时间
             Calendar calendar = Calendar.getInstance();
@@ -1052,6 +1125,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         Long totalNum = 0l;
         BigDecimal totalPrice = new BigDecimal(0);
         BigDecimal totalCostPrice = new BigDecimal("0");
+        Map<Long,String> productMap = new HashMap<>();
         for (FsPrescribeDrug drug : drugs) {
             totalPrice = totalPrice.add(drug.getDrugPrice().multiply(BigDecimal.valueOf(drug.getDrugNum())));
             totalNum = totalNum + drug.getDrugNum();
@@ -1089,6 +1163,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             item.setIsPrescribe(1);
             item.setJsonInfo(JSON.toJSONString(dto));
             items.add(item);
+            productMap.put(drug.getProductId(),drug.getDrugName());
         }
         //中药总价处理
         if (prescribe.getPrescribeType().equals(2)) {
@@ -1109,6 +1184,34 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             for (FsStoreOrderItemScrm item : items) {
                 item.setOrderId(order.getId());
                 fsStoreOrderItemMapper.insertFsStoreOrderItem(item);
+
+                // ===================== 溯源码核心逻辑 ==================
+                Integer buyCount = item.getNum();
+                Long productId = item.getProductId();
+                String productName = productMap.get(productId) == null ? "商品" : productMap.get(productId);
+
+                if (buyCount <= 0) {
+                    throw new ServiceException("商品[" + productName + "]购买数量不能为0");
+                }
+
+                //查询并锁定未出库溯源码带行锁
+                List<FsStoreVerifyCodeScrm> availableVerifyCodes = verifyCodeMapper.selectUnOutboundVerifyCodesWithLock(productId, buyCount);
+                if (availableVerifyCodes == null || availableVerifyCodes.size() < buyCount) {
+                    String errorMsg = String.format("商品[%s]库存不足",
+                            productName, buyCount, (availableVerifyCodes == null ? 0 : availableVerifyCodes.size()));
+                    throw new ServiceException(errorMsg);
+                }
+
+                for (FsStoreVerifyCodeScrm code : availableVerifyCodes) {
+                    code.setOutboundStatus(1L);
+                    code.setOrderId(order.getId());
+                    code.setOrderItemId(item.getItemId());
+                    code.setUpdateTime(new Date());
+                }
+                if (!availableVerifyCodes.isEmpty()) {
+                    verifyCodeService.updateBatchById(availableVerifyCodes);
+                }
+                // ===================== 溯源码逻辑END =====================
             }
             fsStoreOrderLogsService.create(order.getId(), FsStoreOrderLogEnum.CREATE_ORDER.getValue(),
                     FsStoreOrderLogEnum.CREATE_ORDER.getDesc());
@@ -1277,6 +1380,22 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             order.setDeliveryId(deliveryId);
             order.setDeliverySendTime(new Date());
 
+            //溯源码
+            List<FsStoreOrderItemVO> scrmList = fsStoreOrderItemMapper.selectMyFsStoreOrderItemListByOrderId(order.getId());
+            if (!scrmList.isEmpty()) {
+                List<Long> orderItemIds = scrmList.stream().map(FsStoreOrderItemVO::getItemId).collect(Collectors.toList());
+                //获取溯源码,进行回退
+                List<FsStoreVerifyCodeScrm> verifyCodes = verifyCodeScrmMapper.selectList(new LambdaQueryWrapper<FsStoreVerifyCodeScrm>().eq(FsStoreVerifyCodeScrm::getOrderId, order.getId()).in(FsStoreVerifyCodeScrm::getOrderItemId, orderItemIds).eq(FsStoreVerifyCodeScrm::getIsDel, "0"));
+                if (!verifyCodes.isEmpty()) {
+                    verifyCodes.forEach(v -> {
+                        v.setIsRecycle(0L);
+                    });
+                    //批量更新数据
+                    verifyCodeScrmService.updateBatchById(verifyCodes);
+                }
+
+            }
+
             fsStoreOrderMapper.updateFsStoreOrder(order);
             orderStatusService.create(order.getId(), OrderLogEnum.DELIVERY_GOODS.getValue(),
                     OrderLogEnum.DELIVERY_GOODS.getDesc());
@@ -1655,9 +1774,22 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             String json = configService.selectConfigByKey("store.config");
             StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
             if (config.getUnPayTime() != null && config.getUnPayTime() > 0) {
-                redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
+                if(storeOrder.getIsPrescribe() != null && storeOrder.getIsPrescribe() == 1){//处方药72小时
+                    config.setUnPayTime(4320);
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(),4320, TimeUnit.MINUTES);
+                }else {
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
+                }
             } else {
-                redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
+                if(storeOrder.getIsPrescribe() != null && storeOrder.getIsPrescribe() == 1){//处方药
+                    if(config == null){
+                        config = new StoreConfig();
+                    }
+                    config.setUnPayTime(4320);
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(),4320, TimeUnit.MINUTES);
+                }else {
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
+                }
             }
             Calendar calendar = Calendar.getInstance();
             calendar.setTime(storeOrder.getCreateTime());
@@ -4809,7 +4941,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     }
 
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class, timeout = 20)
     public R createOrderMultiStore(long userId, FsStoreOrderCreateParam param) {
 
         FsStoreOrderComputedParam computedParam = new FsStoreOrderComputedParam();
@@ -4992,6 +5124,34 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     item.setIsPrescribe(1);
                 }
                 fsStoreOrderItemMapper.insertFsStoreOrderItem(item);
+
+                // ===================== 溯源码核心逻辑 ==================
+                Integer buyCount = vo.getCartNum();
+                Long productId = vo.getProductId();
+                String productName = vo.getProductName();
+
+                if (buyCount <= 0) {
+                    throw new ServiceException("商品[" + productName + "]购买数量不能为0");
+                }
+
+                //查询并锁定未出库溯源码带行锁
+                List<FsStoreVerifyCodeScrm> availableVerifyCodes = verifyCodeMapper.selectUnOutboundVerifyCodesWithLock(productId, buyCount);
+                if (availableVerifyCodes == null || availableVerifyCodes.size() < buyCount) {
+                    String errorMsg = String.format("商品[%s]库存不足",
+                            productName, buyCount, (availableVerifyCodes == null ? 0 : availableVerifyCodes.size()));
+                    throw new ServiceException(errorMsg);
+                }
+
+                for (FsStoreVerifyCodeScrm code : availableVerifyCodes) {
+                    code.setOutboundStatus(1L);
+                    code.setOrderId(storeOrder.getId());
+                    code.setOrderItemId(item.getItemId());
+                    code.setUpdateTime(new Date());
+                }
+                if (!availableVerifyCodes.isEmpty()) {
+                    verifyCodeService.updateBatchById(availableVerifyCodes);
+                }
+                // ===================== 溯源码逻辑END =====================
                 listOrderItem.add(item);
             }
             if (listOrderItem.size() > 0) {
@@ -5011,15 +5171,28 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             orderStatusService.create(storeOrder.getId(), OrderLogEnum.CREATE_ORDER.getValue(),
                     OrderLogEnum.CREATE_ORDER.getDesc());
 
-            //加入redis,24小时自动取消
+            //加入redis
             String redisKey = String.valueOf(StrUtil.format("{}{}",
                     StoreConstants.REDIS_ORDER_OUTTIME_UNPAY, storeOrder.getId()));
             String json = configService.selectConfigByKey("store.config");
             com.fs.store.config.StoreConfig config = JSONUtil.toBean(json, com.fs.store.config.StoreConfig.class);
             if (config.getUnPayTime() != null && config.getUnPayTime() > 0) {
-                redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
+                if(storeOrder.getIsPrescribe() != null && storeOrder.getIsPrescribe() == 1){//处方药72小时
+                    config.setUnPayTime(4320);
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(),4320, TimeUnit.MINUTES);
+                }else {
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
+                }
             } else {
-                redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
+                if(storeOrder.getIsPrescribe() != null && storeOrder.getIsPrescribe() == 1){//处方药
+                    if(config == null){
+                        config = new com.fs.store.config.StoreConfig();
+                    }
+                    config.setUnPayTime(4320);
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(),4320, TimeUnit.MINUTES);
+                }else {
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
+                }
             }
             //添加支付到期时间
             Calendar calendar = Calendar.getInstance();

+ 1 - 5
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -33,12 +33,9 @@ import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.config.RedPacketConfig;
 import com.fs.course.domain.FsCourseRedPacketLog;
-import com.fs.course.dto.FsOrderDeliveryNoteDTO;
-import com.fs.course.dto.FsPaymentDeliveryNoteDTO;
-import com.fs.course.dto.OrderOpenIdTransDTO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.service.IFsCourseRedPacketLogService;
-import com.fs.course.vo.FsStorePaymentUsetVo;
+import com.fs.hisStore.vo.FsStorePaymentUsetVo;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserWx;
 import com.fs.his.service.IFsUserService;
@@ -47,7 +44,6 @@ import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.config.StoreConfig;
 import com.fs.hisStore.enums.SysConfigEnum;
 import com.fs.hisStore.param.*;
-import com.fs.hisStore.vo.FsStoreOrderCodeOpenIdVo;
 import com.fs.huifuPay.domain.HuiFuCreateOrder;
 import com.fs.huifuPay.domain.HuifuCreateOrderResult;
 import com.fs.huifuPay.sdk.opps.core.utils.HuiFuUtils;

+ 2 - 2
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -505,8 +505,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             } catch (IllegalAccessException e) {
                 log.error("获取diff出错", e);
             }
-            //避免重复审核时第一次审核结果为不通过,第二次申请重新上架导致无法在审核列表中查询到
-            if(oldFsStoreProduct.getIsAudit() != null && "2".equals(oldFsStoreProduct.getIsAudit())){
+            //只要是编辑,都待审核,通过审核sql 去处理audit和show字段。
+            if(oldFsStoreProduct.getIsAudit() != null){
                 product.setIsAudit("0");
             }
             fsStoreProductMapper.updateFsStoreProduct(product);

+ 1 - 3
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreScrmServiceImpl.java

@@ -122,7 +122,7 @@ public class FsStoreScrmServiceImpl implements IFsStoreScrmService {
                 if (existingMerchantId != null) {
                     // 同一商家下检查店铺名称是否重复
                     if (fsStoreMapper.existsStoreNameInMerchant(existingMerchantId, fsStore.getStoreName())) {
-                        throw new BaseException("该商家下已存在同名店铺");
+                        throw new CustomException("该商家下已存在同名店铺");
                     }
                 }
             }
@@ -347,8 +347,6 @@ public class FsStoreScrmServiceImpl implements IFsStoreScrmService {
 
     @Override
     public int updateFsStoreLogo(FsStoreScrm fsStore) {
-
-
         return fsStoreMapper.updateFsStore(fsStore);
     }
 

+ 111 - 22
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreVerifyCodeScrmServiceImpl.java

@@ -36,12 +36,18 @@ public class FsStoreVerifyCodeScrmServiceImpl extends ServiceImpl<FsStoreVerifyC
     private static final Long NOT_DELETED = 0L;
     private static final Long DELETED = 1L;
     private static final Long STATUS_ENABLE = 1L;
-
+    //出库状态
+    private static final Long OUTBOUND_STATUS_UNDO = 0L;
+    //核销状态
+    private static final Long VERIFY_STATUS_WRITEOFF = 1L;
     private static final int BATCH_SIZE = 500;
 
     @Autowired
     private FsStoreProductScrmMapper fsStoreProductScrmMapper;
 
+    @Autowired
+    private IFsStoreVerifyCodeScrmService fsStoreVerifyCodeScrmService;
+
     /**
      * 查询核销码
      *
@@ -77,8 +83,15 @@ public class FsStoreVerifyCodeScrmServiceImpl extends ServiceImpl<FsStoreVerifyC
         if (baseMapper.selectCount(new LambdaQueryWrapper<FsStoreVerifyCodeScrm>().eq(FsStoreVerifyCodeScrm::getVerifyCode, fsStoreVerifyCodeScrm.getVerifyCode())) > 0) {
             throw new ServiceException("操作失败,核销码:" + fsStoreVerifyCodeScrm.getVerifyCode() + "已存在");
         }
+        //获取商品信息
+        FsStoreProductScrm product = fsStoreProductScrmMapper.selectFsStoreProductById(fsStoreVerifyCodeScrm.getProductId());
+        if(product == null){
+            throw new ServiceException("操作失败,商品不存在");
+        }
+
+        fsStoreVerifyCodeScrm.setStoreId(product.getStoreId());//获取店铺id
         fsStoreVerifyCodeScrm.setCreateTime(DateUtils.getNowDate());
-        return baseMapper.insert(fsStoreVerifyCodeScrm);
+        return baseMapper.insertFsStoreVerifyCodeScrm(fsStoreVerifyCodeScrm);
     }
 
     /**
@@ -160,11 +173,12 @@ public class FsStoreVerifyCodeScrmServiceImpl extends ServiceImpl<FsStoreVerifyC
      * 核销码导入实现类
      *
      * @param verifyCodeDTOS 数据
-     * @param userId 用户id
+     * @param userId         用户id
      * @return R
      */
     @Override
-    public R importExpress(List<FsStoreVerifyCodeDTO> verifyCodeDTOS,String userId) {
+    @Transactional(rollbackFor = Exception.class)
+    public R importExpress(List<FsStoreVerifyCodeDTO> verifyCodeDTOS, String userId, Long productId) {
         if (CollectionUtils.isEmpty(verifyCodeDTOS)) {
             return R.ok("导入成功!无待导入数据");
         }
@@ -179,10 +193,10 @@ public class FsStoreVerifyCodeScrmServiceImpl extends ServiceImpl<FsStoreVerifyC
                 .filter(dto -> dto.getVerifyCode() == null || dto.getVerifyCode().trim().isEmpty())
                 .count();
 
-        Long[] productIds = verifyCodeDTOS.stream()
-                .map(FsStoreVerifyCodeDTO::getProductId)
-                .filter(Objects::nonNull)
-                .toArray(Long[]::new);
+//        Long[] productIds = verifyCodeDTOS.stream()
+//                .map(FsStoreVerifyCodeDTO::getProductId)
+//                .filter(Objects::nonNull)
+//                .toArray(Long[]::new);
 
         if (emptyCodeCount > 0) {
             errorMsg.append("发现").append(emptyCodeCount).append("条核销码为空的数据\n");
@@ -205,16 +219,26 @@ public class FsStoreVerifyCodeScrmServiceImpl extends ServiceImpl<FsStoreVerifyC
             }
         }
 
-        //获取商品
-        Map<Long, Long> existProductrMap = new HashMap<>();
-        if (!CollectionUtils.isEmpty(Arrays.asList(productIds))) {
-            List<FsStoreProductScrm> productScrmList = fsStoreProductScrmMapper.selectProductByIds(productIds);
-
-            if (!CollectionUtils.isEmpty(productScrmList)) {
-                existProductrMap = productScrmList.stream()
-                        .collect(Collectors.toMap(FsStoreProductScrm::getProductId, FsStoreProductScrm::getProductId));
+        FsStoreProductScrm productScrm = null;
+        //获取商品信息
+        try {
+            productScrm = fsStoreProductScrmMapper.selectFsStoreProductById(productId);
+            if (productScrm == null) {
+                throw new ServiceException("操作失败,商品信息不存在!");
             }
+        } catch (Exception e) {
+            e.printStackTrace();
         }
+//        //获取商品
+//        Map<Long, Long> existProductrMap = new HashMap<>();
+//        if (!CollectionUtils.isEmpty(Arrays.asList(productIds))) {
+//            List<FsStoreProductScrm> productScrmList = fsStoreProductScrmMapper.selectProductByIds(productIds);
+//
+//            if (!CollectionUtils.isEmpty(productScrmList)) {
+//                existProductrMap = productScrmList.stream()
+//                        .collect(Collectors.toMap(FsStoreProductScrm::getProductId, FsStoreProductScrm::getProductId));
+//            }
+//        }
 
         List<FsStoreVerifyCodeScrm> insertList = new ArrayList<>();
         for (FsStoreVerifyCodeDTO dto : verifyCodeDTOS) {
@@ -227,17 +251,14 @@ public class FsStoreVerifyCodeScrmServiceImpl extends ServiceImpl<FsStoreVerifyC
             // 检查是否重复
             if (existVerifyCodeMap.containsKey(verifyCode)) {
                 errorMsg.append("核销码:").append(verifyCode)
-                        .append("(商品ID:").append(dto.getProductId()).append(")已存在,重复新增\n");
-                continue;
-            } else if (!existProductrMap.containsKey(dto.getProductId())) {
-                errorMsg.append("核销码:").append(verifyCode)
-                        .append("绑定(商品ID:不存在!");
+                        .append("(商品:").append(productScrm.getProductName()).append(")已存在,重复新增\n");
                 continue;
             }
 
             FsStoreVerifyCodeScrm entity = new FsStoreVerifyCodeScrm();
             entity.setVerifyCode(verifyCode);
-            entity.setProductId(dto.getProductId());
+            entity.setProductId(productId);
+            entity.setStoreId(productScrm.getStoreId());
             entity.setOutboundStatus(0L);
             entity.setVerifyStatus(0L);
             entity.setStatus(1L);
@@ -269,4 +290,72 @@ public class FsStoreVerifyCodeScrmServiceImpl extends ServiceImpl<FsStoreVerifyC
                 : "导入成功!共导入" + insertList.size() + "条核销码";
         return R.ok(resultMsg);
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R writeOff(Long[] ids, String userId) {
+        if (userId == null || userId.trim().isEmpty()) {
+            log.warn("溯源码核销失败:操作人ID为空,核销ids={}", Arrays.toString(ids));
+            return R.error("操作失败,操作人ID不能为空!");
+        }
+
+        LambdaQueryWrapper<FsStoreVerifyCodeScrm> queryWrapper = new LambdaQueryWrapper<FsStoreVerifyCodeScrm>()
+                .in(FsStoreVerifyCodeScrm::getId, ids)
+                .eq(FsStoreVerifyCodeScrm::getIsDel, NOT_DELETED);
+        List<FsStoreVerifyCodeScrm> dbList = baseMapper.selectList(queryWrapper);
+
+        if (CollectionUtils.isEmpty(dbList)) {
+            log.warn("溯源码核销失败:指定ID的核销数据不存在,核销ids={},userId={}", Arrays.toString(ids), userId);
+            return R.error("操作失败,核销数据不存在!");
+        }
+
+        Set<Long> inputIdSet = new HashSet<>(Arrays.asList(ids));
+        Set<Long> dbIdSet = dbList.stream().map(FsStoreVerifyCodeScrm::getId).collect(Collectors.toSet());
+        inputIdSet.removeAll(dbIdSet);
+        String notExistIds = inputIdSet.isEmpty() ? "" : inputIdSet.stream().map(String::valueOf).collect(Collectors.joining(","));
+
+        List<FsStoreVerifyCodeScrm> hasWriteOffList = dbList.stream()
+                .filter(l -> VERIFY_STATUS_WRITEOFF.equals(l.getVerifyStatus()))
+                .collect(Collectors.toList());
+        if (!CollectionUtils.isEmpty(hasWriteOffList)) {
+            String hasWriteOffCodes = hasWriteOffList.stream()
+                    .filter(l -> l.getVerifyCode() != null && !l.getVerifyCode().trim().isEmpty())
+                    .map(FsStoreVerifyCodeScrm::getVerifyCode)
+                    .collect(Collectors.joining(","));
+            log.warn("溯源码核销失败:部分数据已核销,已核销核销码={},userId={}", hasWriteOffCodes, userId);
+            return R.error("操作失败,核销码【" + hasWriteOffCodes + "】已核销,禁止重复核销!");
+        }
+
+        List<FsStoreVerifyCodeScrm> unOutboundList = dbList.stream()
+                .filter(l -> OUTBOUND_STATUS_UNDO.equals(l.getOutboundStatus()))
+                .filter(l -> l.getVerifyCode() != null && !l.getVerifyCode().trim().isEmpty())
+                .collect(Collectors.toList());
+        if (!CollectionUtils.isEmpty(unOutboundList)) {
+            String unOutboundCodes = unOutboundList.stream()
+                    .map(FsStoreVerifyCodeScrm::getVerifyCode)
+                    .collect(Collectors.joining(","));
+            log.warn("溯源码核销失败:部分核销码未出库,未出库核销码={},userId={}", unOutboundCodes, userId);
+            return R.error("操作失败,核销码【" + unOutboundCodes + "】未出库,禁止核销!");
+        }
+
+        Date updateTime = new Date();
+        dbList.forEach(l -> {
+            l.setVerifyStatus(VERIFY_STATUS_WRITEOFF);
+            l.setUpdateTime(updateTime);
+            l.setUpdateBy(userId.trim());
+        });
+
+        boolean updateResult = this.updateBatchById(dbList);
+        if (!updateResult) {
+            log.error("溯源码核销失败:批量更新数据库失败,核销ids={},userId={}", Arrays.toString(ids), userId);
+            throw new RuntimeException("核销数据更新失败,请重试!");
+        }
+
+        R successR = R.ok("核销成功,共处理" + dbList.size() + "条数据");
+        if (!notExistIds.isEmpty()) {
+            successR.put("notExistIds", notExistIds);
+            log.warn("溯源码核销提示:部分ID不存在,不存在的ids={},userId={}", notExistIds, userId);
+        }
+        return successR;
+    }
 }

+ 2 - 2
fs-service/src/main/java/com/fs/hisStore/service/listener/FsStoreScrmListener.java

@@ -63,14 +63,14 @@ public class FsStoreScrmListener {
         if(!oldBusinessLicenseFlag){
             fsStoreScrmOcr.setBusinessLicense(fsStore.getBusinessLicense());
             String result = TxOcrClient.bizLicenseOCR(fsStoreScrmOcr.getBusinessLicense());
-            if(!result.isEmpty()){
+            if(!ObjectUtils.isEmpty(result)){
                 fsStoreScrmOcr.setBusinessLicenseTxt(result);
             }
         }
         if(!oldDrugLicenseFlag){
             fsStoreScrmOcr.setDrugLicense(fsStore.getDrugLicense());
             String result = TxOcrClient.ExtractDocBasic(fsStore.getDrugLicense());
-            if(!result.isEmpty()){
+            if(!ObjectUtils.isEmpty(result)){
                 fsStoreScrmOcr.setDrugLicenseTxt(result);
             }
         }

+ 1 - 1
fs-service/src/main/java/com/fs/course/vo/FsStorePaymentUsetVo.java → fs-service/src/main/java/com/fs/hisStore/vo/FsStorePaymentUsetVo.java

@@ -1,4 +1,4 @@
-package com.fs.course.vo;
+package com.fs.hisStore.vo;
 
 import lombok.Data;
 

+ 3 - 0
fs-service/src/main/java/com/fs/store/vo/FsStoreCartScrmVO.java

@@ -41,4 +41,7 @@ import java.math.BigDecimal;
 
     //店铺ID
     private Long storeId;
+
+    //数量
+    private Integer num;
 }

+ 4 - 0
fs-service/src/main/resources/mapper/his/FsDoctorMapper.xml

@@ -391,4 +391,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                  sign_url
                  from  fs_doctor where  store_id=#{param.storeId} and doctor_type=#{param.doctorType} and status=1
     </select>
+
+    <select id="idCardExists" resultType="java.lang.Boolean">
+        select 1 from fs_doctor where id_card = #{idCard}
+    </select>
 </mapper>

+ 1 - 1
fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml

@@ -196,7 +196,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
     </select>
 
-        <select id="getPaymentUsetInfoList" resultType="com.fs.course.vo.FsStorePaymentUsetVo">
+        <select id="getPaymentUsetInfoList" resultType="com.fs.hisStore.vo.FsStorePaymentUsetVo">
             SELECT
                 sp.bank_transaction_id,
                 sp.open_id,

+ 1 - 1
fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml

@@ -872,7 +872,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <sql id="baseSql">
        DISTINCT p.product_id, p.image,p.video, p.slider_image, p.product_name, p.product_info, p.keyword, p.bar_code,
-       p.cate_id, p.price, p.vip_price, p.ot_price, p.postage, p.unit_name, p.sort, p.sales, p.stock,
+       p.cate_id, p.price, p.vip_price, p.ot_price, p.postage, p.unit_name, p.sort, ( SELECT COUNT(*) FROM fs_store_verify_code_scrm vc WHERE vc.product_id = p.product_id AND vc.outbound_status = 0 AND is_del = 0) AS stock,( SELECT COUNT(*) FROM fs_store_verify_code_scrm vc WHERE vc.product_id = p.product_id AND vc.outbound_status = 1 AND is_del = 0) AS sales,
        p.is_hot, p.is_benefit, p.is_best, p.is_new, p.description, p.create_time, p.update_time, p.is_postage,
        p.is_del, p.give_integral, p.cost, p.is_good, p.browse, p.code_path, p.temp_id, p.spec_type, p.is_integral,
        p.integral, p.product_type, p.prescribe_code, p.prescribe_spec, p.prescribe_factory, p.prescribe_name,

+ 27 - 8
fs-service/src/main/resources/mapper/hisStore/FsStoreVerifyCodeScrmMapper.xml

@@ -14,22 +14,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="isDel"    column="is_del"    />
         <result property="createTime"    column="create_time"    />
         <result property="updateTime"    column="update_time"    />
+        <result property="orderId"    column="order_id"    />
+        <result property="orderItemId"    column="order_item_id"    />
+        <result property="storeId"    column="store_id"    />
+        <result property="isRecycle"    column="is_recycle"    />
     </resultMap>
 
     <sql id="selectFsStoreVerifyCodeScrmVo">
-        select id, verify_code, product_id, outbound_status, verify_status, status, is_del, create_time, update_time from fs_store_verify_code_scrm
+        select id, verify_code, product_id, outbound_status, verify_status, status, is_del, create_time, update_time,order_id,order_item_id,store_id,is_recycle from fs_store_verify_code_scrm
     </sql>
 
     <select id="selectFsStoreVerifyCodeScrmList" parameterType="FsStoreVerifyCodeScrm" resultMap="FsStoreVerifyCodeScrmResult">
         <include refid="selectFsStoreVerifyCodeScrmVo"/>
         <where>  
-            <if test="verifyCode != null  and verifyCode != ''"> and verify_code = #{verifyCode}</if>
+            <if test="verifyCode != null  and verifyCode != ''"> and verify_code Like  concat('%',#{verifyCode},'%')</if>
             <if test="productId != null  and productId != ''"> and product_id = #{productId}</if>
             <if test="outboundStatus != null "> and outbound_status = #{outboundStatus}</if>
             <if test="verifyStatus != null "> and verify_status = #{verifyStatus}</if>
             <if test="status != null "> and status = #{status}</if>
             <if test="isDel != null "> and is_del = #{isDel}</if>
+            <if test="storeId != null "> and store_id = #{storeId}</if>
         </where>
+        order by create_time desc
     </select>
     
     <select id="selectFsStoreVerifyCodeScrmById" parameterType="String" resultMap="FsStoreVerifyCodeScrmResult">
@@ -37,7 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where id = #{id}
     </select>
         
-    <insert id="insertFsStoreVerifyCodeScrm" parameterType="FsStoreVerifyCodeScrm" useGeneratedKeys="true" keyProperty="id">
+    <insert id="insertFsStoreVerifyCodeScrm" parameterType="FsStoreVerifyCodeScrm">
         insert into fs_store_verify_code_scrm
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="verifyCode != null and verifyCode != ''">verify_code,</if>
@@ -48,6 +54,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isDel != null">is_del,</if>
             <if test="createTime != null">create_time,</if>
             <if test="updateTime != null">update_time,</if>
+            <if test="orderId != null">order_id,</if>
+            <if test="orderItemId != null">order_item_id,</if>
+            <if test="storeId != null">store_id,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="verifyCode != null and verifyCode != ''">#{verifyCode},</if>
@@ -56,8 +65,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="verifyStatus != null">#{verifyStatus},</if>
             <if test="status != null">#{status},</if>
             <if test="isDel != null">#{isDel},</if>
-            <if test="createTime != null">#{createTime},</if>
-            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createTime != null">#{createTime} ,</if>
+            <if test="updateTime != null">#{updateTime} ,</if>
+            <if test="orderId != null">#{orderId} ,</if>
+            <if test="orderItemId != null">#{orderItemId} ,</if>
+            <if test="storeId != null">#{storeId} ,</if>
          </trim>
     </insert>
 
@@ -72,7 +84,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isDel != null">is_del = #{isDel},</if>
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
-        </trim>
+            <if test="orderId != null">order_id = #{orderId} ,</if>
+            <if test="orderItemId != null">order_item_id = #{orderItemId} ,</if>
+            <if test="storeId != null">store_id = #{storeId} ,</if>
+            <if test="isRecycle != null">is_recycle = #{isRecycle} ,
+            </if>
+            </trim>
         where id = #{id}
     </update>
 
@@ -98,7 +115,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         create_time,
         update_time,
         create_by,
-        update_by
+        update_by,
+        store_id
         ) VALUES
         <foreach collection="list" item="item" separator=",">
             (
@@ -111,7 +129,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{item.createTime},
             #{item.updateTime},
             #{item.createBy},
-            #{item.updateBy}
+            #{item.updateBy},
+            #{item.storeId}
             )
         </foreach>
     </insert>

+ 2 - 2
fs-service/src/main/resources/mapper/system/SysDeptMapper.xml

@@ -42,8 +42,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 		<if test="status != null and status != ''">
 			AND status = #{status}
 		</if>
-		<!-- 数据范围过滤 -->
-		${params.dataScope}
+<!--		&lt;!&ndash; 数据范围过滤 &ndash;&gt;-->
+<!--		${params.dataScope}-->
 		order by d.parent_id, d.order_num
     </select>
     

+ 194 - 0
fs-store/src/main/java/com/fs/hisStore/controller/store/FsStoreVerifyCodeScrmController.java

@@ -0,0 +1,194 @@
+package com.fs.hisStore.controller.store;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.dto.FsStoreVerifyCodeDTO;
+import com.fs.course.dto.WriteOffDTO;
+import com.fs.framework.service.TokenServiceScrm;
+import com.fs.hisStore.domain.FsStoreVerifyCodeScrm;
+import com.fs.hisStore.domain.StoreLoginUserScrm;
+import com.fs.hisStore.service.IFsStoreVerifyCodeScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 核销码Controller
+ * 
+ * @author fs
+ * @date 2025-11-27
+ */
+@RestController
+@RequestMapping("/shop/scrm")
+public class FsStoreVerifyCodeScrmController extends BaseController
+{
+    @Autowired
+    private IFsStoreVerifyCodeScrmService fsStoreVerifyCodeScrmService;
+
+    @Autowired
+    private TokenServiceScrm tokenService;
+
+
+    // 允许的文件扩展名
+    private static final String[] ALLOWED_EXCEL_EXTENSIONS = {".xlsx", ".xls"};
+
+    // 最大文件大小(5MB)
+    private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
+
+
+    /**
+     * 查询核销码列表
+     */
+//    @PreAuthorize("@ss.hasPermi('shop:scrm:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreVerifyCodeScrm fsStoreVerifyCodeScrm)
+    {
+        startPage();
+        StoreLoginUserScrm loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        fsStoreVerifyCodeScrm.setCreateBy(loginUser.getFsStore().getStoreId().toString());
+        List<FsStoreVerifyCodeScrm> list = fsStoreVerifyCodeScrmService.selectFsStoreVerifyCodeScrmList(fsStoreVerifyCodeScrm);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出核销码列表
+     */
+//    @PreAuthorize("@ss.hasPermi('shop:scrm:export')")
+    @Log(title = "核销码", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreVerifyCodeScrm fsStoreVerifyCodeScrm)
+    {
+        List<FsStoreVerifyCodeScrm> list = fsStoreVerifyCodeScrmService.selectFsStoreVerifyCodeScrmList(fsStoreVerifyCodeScrm);
+        ExcelUtil<FsStoreVerifyCodeScrm> util = new ExcelUtil<FsStoreVerifyCodeScrm>(FsStoreVerifyCodeScrm.class);
+        return util.exportExcel(list, "核销码数据");
+    }
+
+    /**
+     * 获取核销码详细信息
+     */
+//    @PreAuthorize("@ss.hasPermi('shop:scrm:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") String id)
+    {
+        return AjaxResult.success(fsStoreVerifyCodeScrmService.selectFsStoreVerifyCodeScrmById(id));
+    }
+
+    /**
+     * 新增核销码
+     */
+//    @PreAuthorize("@ss.hasPermi('shop:scrm:add')")
+    @Log(title = "核销码", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreVerifyCodeScrm fsStoreVerifyCodeScrm)
+    {
+        StoreLoginUserScrm loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        fsStoreVerifyCodeScrm.setCreateBy(loginUser.getFsStore().getStoreId().toString());
+        return toAjax(fsStoreVerifyCodeScrmService.insertFsStoreVerifyCodeScrm(fsStoreVerifyCodeScrm));
+    }
+
+    /**
+     * 修改核销码
+     */
+//    @PreAuthorize("@ss.hasPermi('shop:scrm:edit')")
+    @Log(title = "核销码", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreVerifyCodeScrm fsStoreVerifyCodeScrm)
+    {
+        StoreLoginUserScrm loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        fsStoreVerifyCodeScrm.setCreateBy(loginUser.getFsStore().getStoreId().toString());
+        return toAjax(fsStoreVerifyCodeScrmService.updateFsStoreVerifyCodeScrm(fsStoreVerifyCodeScrm));
+    }
+
+    /**
+     * 删除核销码
+     */
+//    @PreAuthorize("@ss.hasPermi('shop:scrm:remove')")
+    @Log(title = "核销码", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        StoreLoginUserScrm loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        return toAjax(fsStoreVerifyCodeScrmService.deleteFsStoreVerifyCodeScrmByIds(ids,loginUser.getFsStore().getStoreId()));
+    }
+
+    @GetMapping("/downloadTemplate")
+    public AjaxResult downloadTemplate() {
+        ExcelUtil<FsStoreVerifyCodeDTO> util = new ExcelUtil<>(FsStoreVerifyCodeDTO.class);
+        return util.importTemplateExcel("溯源码那导入模板");
+    }
+
+    /**
+     * 溯源码导入
+     * @param file 导入文件
+     * @return R
+     * **/
+    @Log(title = "核销码批量导入", businessType = BusinessType.IMPORT)
+    @PostMapping("/importExpress")
+    public R importExpress(@RequestParam("file") MultipartFile file,@RequestParam("productId") Long productId) {
+        // 1. 检查文件是否为空
+        if (file.isEmpty()) {
+            return R.error("上传的文件不能为空");
+        }
+        // 2. 检查文件大小
+        if (file.getSize() > MAX_FILE_SIZE) {
+            return R.error("文件大小不能超过5MB");
+        }
+        // 3. 检查文件扩展名
+        String fileName = file.getOriginalFilename();
+        if (fileName == null || !isValidExcelFile(fileName)) {
+            return R.error("请上传Excel文件(.xlsx或.xls格式)");
+        }
+
+        ExcelUtil<FsStoreVerifyCodeDTO> util=new ExcelUtil<>(FsStoreVerifyCodeDTO.class);
+        try {
+            List<FsStoreVerifyCodeDTO> dtoList = util.importExcel(file.getInputStream());
+            if(!dtoList.isEmpty()){
+                if(dtoList.size() > 200){
+                    R.error("操作失败,导入数据不能大于200条!");
+                }
+                StoreLoginUserScrm loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+                return fsStoreVerifyCodeScrmService.importExpress(dtoList,loginUser.getFsStore().getStoreId().toString(),productId);
+            }else {
+                R.error("操作失败,导入数据不能小于1条!");
+            }
+        }catch (Exception e){
+            e.getStackTrace();
+        }
+        return R.ok();
+    }
+
+    /**
+     * 核销码核销
+     * @return R
+     * **/
+//    @PreAuthorize("@ss.hasPermi('shop:scrm:writeOff')")
+    @Log(title = "核销码核销", businessType = BusinessType.IMPORT)
+    @PostMapping("/writeOff")
+    public R writeOff(@RequestBody WriteOffDTO writeOffDTO){
+        if (writeOffDTO.getIds() == null || writeOffDTO.getIds().length == 0) {
+            return R.error("操作失败,核销ID列表不能为空!");
+        }
+        StoreLoginUserScrm loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        return fsStoreVerifyCodeScrmService.writeOff(writeOffDTO.getIds(),loginUser.getFsStore().getStoreId().toString());
+    }
+
+
+    // 检查文件是否为有效的Excel文件
+    private boolean isValidExcelFile(String fileName) {
+        for (String ext : ALLOWED_EXCEL_EXTENSIONS) {
+            if (fileName.toLowerCase().endsWith(ext)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 8 - 0
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -12,6 +12,8 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.sign.Md5Utils;
+import com.fs.common.utils.txocr.IdCardVerificationResponse;
+import com.fs.common.utils.txocr.TxOcrClient;
 import com.fs.company.domain.CompanyUser;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.his.domain.FsDoctor;
@@ -238,6 +240,12 @@ public class UserController extends  AppBaseController {
     @ApiOperation("注册医生")
     @PostMapping("/registerDoctor")
     public R registerDoctor(@Validated  @RequestBody FsDoctorRegisterParam param, HttpServletRequest request){
+        String idCard = param.getIdCard();
+        String doctorName = param.getDoctorName();
+        IdCardVerificationResponse idCardVerificationResponse = TxOcrClient.IdCardVerification(doctorName, idCard);
+        if(0 != idCardVerificationResponse.getResult()){
+            throw new CustomException(idCardVerificationResponse.getDescription());
+        }
         FsDoctor doctor= doctorService.selectFsDoctorByUserId(Long.parseLong(getUserId()));
         if(doctor!=null){
             if(doctor.getIsAudit().equals(0)){

+ 14 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java

@@ -2,6 +2,7 @@ package com.fs.app.controller.store;
 
 
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
 import com.fs.common.BeanCopyUtils;
@@ -11,6 +12,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.erp.service.IErpGoodsService;
 import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.hisStore.domain.*;
+import com.fs.hisStore.mapper.FsStoreVerifyCodeScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
@@ -30,6 +32,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpServletRequest;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 
 @Api("商品中心")
@@ -63,6 +66,9 @@ public class ProductScrmController extends AppBaseController {
     @Autowired
     private ISysConfigService configService;
 
+    @Autowired
+    private FsStoreVerifyCodeScrmMapper verifyCodeScrmMapper;
+
     /**
      * 获取用户信息
      * @param storeId
@@ -93,6 +99,9 @@ public class ProductScrmController extends AppBaseController {
             param.setIsDrug(isDrug);
             param.setStoreId(storeId);
             List<FsStoreProductCategoryScrm> list=categoryService.selectFsStoreProductCategoryListQuery(param);
+            if(param.getIsDrug() != null && param.getIsDrug() == 1){
+                list = list.stream().filter(item -> item.getCateName().equals("甲类非处方") || item.getCateName().equals("乙类非处方")).collect(Collectors.toList());
+            }
             return R.ok().put("data",list);
         } catch (Exception e){
             return R.error("操作异常");
@@ -145,6 +154,10 @@ public class ProductScrmController extends AppBaseController {
         List<FsStoreProductAttrScrm> productAttr=attrService.selectFsStoreProductAttrByProductId(product.getProductId());
         List<FsStoreProductAttrValueScrm> productValues=attrValueService.selectFsStoreProductAttrValueByProductId(product.getProductId());
 
+        //获取商品溯源码库存
+        int number = verifyCodeScrmMapper.selectCount(new LambdaQueryWrapper<FsStoreVerifyCodeScrm>().eq(FsStoreVerifyCodeScrm::getProductId,product.getProductId()).eq(FsStoreVerifyCodeScrm::getOutboundStatus,0L).eq(FsStoreVerifyCodeScrm::getIsDel,0L));
+
+
 //        for(FsStoreProductAttrValue value:productValues){
 //            if(StringUtils.isEmpty(value.getGroupBarCode())){
 //                //单品
@@ -238,7 +251,7 @@ public class ProductScrmController extends AppBaseController {
                 productRelationService.insertFsStoreProductRelation(relation);
             }
         }
-        return R.ok().put("product",product).put("productAttr",productAttr).put("productValues",productValues).put("store",fsStoreScrm);
+        return R.ok().put("product",product).put("productAttr",productAttr).put("productValues",productValues).put("store",fsStoreScrm).put("num",number);
     }
 
     @Login

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/StoreInfoCheckController.java

@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController;
  * @time: 2025/12/1 上午10:46
  */
 @RestController
-@RequestMapping(value="/store/app/storeAfterSales")
+@RequestMapping(value="/store/app/storeInfoCheck")
 public class StoreInfoCheckController {
 
     /**

+ 1 - 4
fs-user-app/src/main/java/com/fs/app/exception/FSExceptionHandler.java

@@ -1,9 +1,5 @@
 package com.fs.app.exception;
 
-
-
-
-import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
@@ -82,6 +78,7 @@ public class FSExceptionHandler {
 	public R handleException(CustomException e){
 		return R.error(e.getMessage());
 	}
+
 	@ExceptionHandler(Exception.class)
 	public R handleException(Exception e){
 		logger.error(e.getMessage(), e);

+ 24 - 0
fs-user-app/src/main/java/com/fs/app/redis/RedisConfiguration.java

@@ -0,0 +1,24 @@
+package com.fs.app.redis;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+
+@Configuration
+public class RedisConfiguration {
+    @Autowired
+    private RedisConnectionFactory redisConnectionFactory;
+
+    @Bean
+    public RedisMessageListenerContainer redisMessageListenerContainer() {
+        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
+        redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
+        return redisMessageListenerContainer;
+    }
+
+
+
+}

+ 180 - 0
fs-user-app/src/main/java/com/fs/app/redis/RedisKeyExpirationListener.java

@@ -0,0 +1,180 @@
+package com.fs.app.redis;//package com.fs.app.redis;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.common.constant.FsConstants;
+import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.domain.FsStoreVerifyCodeScrm;
+import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
+import com.fs.hisStore.mapper.FsStoreVerifyCodeScrmMapper;
+import com.fs.hisStore.service.IFsStoreVerifyCodeScrmService;
+import com.fs.hisStore.vo.FsStoreOrderItemVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.connection.Message;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Component
+public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
+
+    private static final Logger log = LoggerFactory.getLogger(RedisKeyExpirationListener.class);
+
+    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
+        super(listenerContainer);
+    }
+
+//    @Autowired
+//    private IQwUserService qwUserService;
+//    @Autowired
+//    private ConfigUtil configUtil;
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;  // 使用 RedisTemplate 进行分布式锁
+
+    @Autowired
+    private FsStoreOrderScrmMapper orderScrmMapper;
+
+    @Autowired
+    private FsStoreOrderItemScrmMapper orderItemScrmMapper;
+
+    @Autowired
+    private FsStoreVerifyCodeScrmMapper verifyCodeScrmMapper;
+
+    @Autowired
+    private IFsStoreVerifyCodeScrmService verifyCodeScrmService;
+
+//    @Autowired
+//    QwUserMapper qwUserMapper;
+//
+//    @Autowired
+//    QwCompanyMapper qwCompanyMapper;
+//
+//    @Autowired
+//    QwApiService qwApiService;
+
+    /**
+     * 针对redis数据失效事件,进行数据处理
+     *
+     * @param message
+     * @param pattern
+     */
+    @Override
+    public void onMessage(Message message, byte[] pattern) {
+        String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
+        //过期的key
+        String key = new String(message.getBody(), StandardCharsets.UTF_8);
+
+//        //判断是否是appKey失效
+//        if(key.contains(FsConstants.REDIS_QW_appKey_Active)) {
+//
+//            log.info("监听Qw过期appKey-redis过期:pattern={},channel={},key={}",new String(pattern),channel,key);
+//
+//            String lockKey = "lockAppKey:" + key;  // 分布式锁的 key
+//
+//            // 尝试获取锁,设置 30 秒自动过期,防止死锁
+//            Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
+//
+//            if (Boolean.TRUE.equals(lockAcquired)) {  // 只有一个实例会成功获取锁
+//                try {
+//                    String[] parts = key.split(":");
+//
+//                    if (parts.length == 3) {
+//                        String corpId = parts[1].trim();
+//                        String qwUserId = parts[2].trim();
+//
+//                        String appKey = qwUserService.selectQwUserByQwUserIdAndCorId(qwUserId, corpId);
+//
+//                        String msg="<font color=\"warning\">您的【云联融智】助手已退出,企业微信【侧边栏】有异常,发送已停止,请注意查看</font>";
+//                        sendQwMsg(appKey,msg);
+//
+//                        log.info("监听Qw过期appKey-redis过期:pattern={},channel={},key={}",new String(pattern),channel,key);
+//                    } else {
+//                        System.out.println("监听appKey失效!");
+//                    }
+//                } finally {
+//                    // 释放锁,避免影响后续任务
+//                    redisTemplate.delete(lockKey);
+//                }
+//            } else {
+//                log.info("另一个实例已经处理了 key={},当前实例跳过", key);
+//            }
+//
+//        }else
+        if (key.contains(FsConstants.REDIS_ORDER_UNPAY)) {//未支付支付订单过期
+            log.info("监听商城过期appKey-redis过期:pattern={},channel={},key={}", new String(pattern), channel, key);
+            String lockKey = "lockStoreAppKey:" + key;  // 分布式锁的 key
+            Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
+            if (Boolean.TRUE.equals(lockAcquired)) {  // 只有一个实例会成功获取锁
+                try {
+                    String[] parts = key.split(":");
+                    if (parts.length == 3) {
+                        Long orderId = Long.parseLong(parts[parts.length - 1].trim());
+                        if (orderId != null) {
+                            //获取订单信息
+                            FsStoreOrderScrm orderScrm = orderScrmMapper.selectFsStoreOrderById(orderId);
+                            if (orderScrm != null && orderScrm.getStatus() == 0) {
+                                //获取订单下的详情信息
+                                List<FsStoreOrderItemVO> scrmList = orderItemScrmMapper.selectMyFsStoreOrderItemListByOrderId(orderId);
+                                if (!scrmList.isEmpty()) {
+                                    List<Long> orderItemIds = scrmList.stream().map(FsStoreOrderItemVO::getItemId).collect(Collectors.toList());
+                                    //获取溯源码,进行回退
+                                    List<FsStoreVerifyCodeScrm> verifyCodes = verifyCodeScrmMapper.selectList(new LambdaQueryWrapper<FsStoreVerifyCodeScrm>().eq(FsStoreVerifyCodeScrm::getOrderId, orderId).in(FsStoreVerifyCodeScrm::getOrderItemId, orderItemIds).eq(FsStoreVerifyCodeScrm::getIsDel, "0"));
+                                    if(!verifyCodes.isEmpty()){
+                                        verifyCodes.forEach(v->{
+                                            v.setVerifyStatus(0L);
+                                            v.setOutboundStatus(0L);
+                                        });
+                                        //批量更新数据
+                                        verifyCodeScrmService.updateBatchById(verifyCodes);
+                                    }
+                                }
+                                //更新订单状态
+                                FsStoreOrderScrm updateOrder = new FsStoreOrderScrm();
+                                updateOrder.setId(orderId);
+                                updateOrder.setStatus(-3);
+                                orderScrmMapper.updateFsStoreOrder(updateOrder);
+                            }
+                        }
+                    } else {
+                        System.out.println("监听appKey失效!");
+                    }
+                } finally {
+                    // 释放锁,避免影响后续任务
+                    redisTemplate.delete(lockKey);
+                }
+            } else {
+                log.info("另一个实例已经处理了 key={},当前实例跳过", key);
+            }
+
+        }
+    }
+
+//    /**
+//     * 给企业微信的应用发送消息
+//     */
+//    private void sendQwMsg(String appKey,String msg){
+//        QwUser qwUserByAppKey = qwUserMapper.selectQwUserByAppKey(appKey);
+//
+//        QwSendMsgParam sendMsgParam = new QwSendMsgParam();
+//        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(qwUserByAppKey.getCorpId());
+//        sendMsgParam.setAgentid(Integer.parseInt(qwCompany.getServerAgentId().trim()));
+//        sendMsgParam.setTouser(qwUserByAppKey.getQwUserId());
+//
+//        QwSendMsgParam.Markdown markdown = new QwSendMsgParam.Markdown();
+//        markdown.setContent(msg);
+//
+//        sendMsgParam.setMarkdown(markdown);
+//        sendMsgParam.setMsgtype("markdown");
+//        qwApiService.sendMsg(sendMsgParam, qwCompany.getCorpId());
+//
+//    }
+}