瀏覽代碼

Merge branch 'feature/mall_product' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

 Conflicts:
	yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java
	yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
	yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
YunaiV 1 年之前
父節點
當前提交
8b50cd9661
共有 100 個文件被更改,包括 3195 次插入347 次删除
  1. 3 3
      pom.xml
  2. 221 0
      sql/mysql/brokerage.sql
  3. 20 21
      sql/mysql/pay_wallet.sql
  4. 2 2
      yudao-dependencies/pom.xml
  5. 1 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java
  6. 7 26
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/SortingField.java
  7. 12 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java
  8. 0 63
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/delegate/DelegatePayClient.java
  9. 3 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java
  10. 0 28
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClientConfig.java
  11. 1 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java
  12. 9 0
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java
  13. 178 0
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMpRequest.java
  14. 3 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  15. 10 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
  16. 18 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java
  17. 2 23
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/dto/BargainRecordCreateReqDTO.java
  18. 21 5
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
  19. 0 39
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordUpdateStatusReqDTO.java
  20. 19 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java
  21. 50 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityUpdateStockReqDTO.java
  22. 1 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  23. 41 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApiImpl.java
  24. 14 7
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
  25. 84 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
  26. 5 7
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java
  27. 45 33
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationRecordDO.java
  28. 10 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java
  29. 10 11
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
  30. 14 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
  31. 17 13
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
  32. 0 26
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/util/PromotionUtils.java
  33. 29 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApi.java
  34. 51 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/dto/BrokerageUserDTO.java
  35. 15 5
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  36. 48 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageBindModeEnum.java
  37. 44 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageEnabledConditionEnum.java
  38. 46 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordBizTypeEnum.java
  39. 39 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordStatusEnum.java
  40. 41 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawStatusEnum.java
  41. 40 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java
  42. 9 0
      yudao-module-mall/yudao-module-trade-biz/pom.xml
  43. 33 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApiImpl.java
  44. 10 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/TradeAfterSaleController.java
  45. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/TradeAfterSaleDetailRespVO.java
  46. 51 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/BrokerageRecordController.java
  47. 60 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordBaseVO.java
  48. 33 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordPageReqVO.java
  49. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordRespVO.java
  50. 104 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/BrokerageUserController.java
  51. 43 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserBaseVO.java
  52. 18 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserClearBrokerageUserReqVO.java
  53. 30 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserPageReqVO.java
  54. 45 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserRespVO.java
  55. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserUpdateBrokerageEnabledReqVO.java
  56. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserUpdateBrokerageUserReqVO.java
  57. 45 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/TradeConfigController.java
  58. 71 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigBaseVO.java
  59. 17 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigRespVO.java
  60. 14 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigSaveReqVO.java
  61. 5 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java
  62. 8 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderDetailRespVO.java
  63. 55 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageRecordController.java
  64. 134 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
  65. 47 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java
  66. 19 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageProductPriceRespVO.java
  67. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordPageReqVO.java
  68. 27 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordRespVO.java
  69. 17 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserBindReqVO.java
  70. 25 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserChildSummaryPageReqVO.java
  71. 33 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserChildSummaryRespVO.java
  72. 28 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserMySummaryRespVO.java
  73. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankByPriceRespVO.java
  74. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankByUserCountRespVO.java
  75. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankPageReqVO.java
  76. 19 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRespVO.java
  77. 73 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java
  78. 27 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java
  79. 37 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/config/AppTradeConfigController.java
  80. 21 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/config/vo/AppTradeConfigRespVO.java
  81. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/config/AppDeliveryConfigRespVO.java
  82. 4 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
  83. 6 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
  84. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java
  85. 50 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/record/BrokerageRecordConvert.java
  86. 56 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/user/BrokerageUserConvert.java
  87. 23 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/config/TradeConfigConvert.java
  88. 11 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  89. 82 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/BrokerageRecordDO.java
  90. 63 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/BrokerageUserDO.java
  91. 90 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java
  92. 56 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/BrokerageRecordMapper.java
  93. 115 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java
  94. 15 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/config/TradeConfigMapper.java
  95. 36 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/no/TradeOrderNoRedisDAO.java
  96. 29 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/brokerage/BrokerageRecordUnfreezeJob.java
  97. 4 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/package-info.java
  98. 1 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
  99. 39 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/BrokerageAddReqBO.java
  100. 26 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java

+ 3 - 3
pom.xml

@@ -16,11 +16,11 @@
         <module>yudao-module-member</module>
         <module>yudao-module-system</module>
         <module>yudao-module-infra</module>
-<!--        <module>yudao-module-pay</module>-->
-<!--        <module>yudao-module-bpm</module>-->
+        <!--        <module>yudao-module-bpm</module>-->
 <!--        <module>yudao-module-report</module>-->
 <!--        <module>yudao-module-mp</module>-->
-<!--        <module>yudao-module-mall</module>-->
+        <module>yudao-module-pay</module>
+        <module>yudao-module-mall</module>
         <!-- 示例项目 -->
         <module>yudao-example</module>
     </modules>

+ 221 - 0
sql/mysql/brokerage.sql

@@ -0,0 +1,221 @@
+-- 增加配置表
+create table trade_config
+(
+    id                           bigint auto_increment comment '自增主键' primary key,
+    brokerage_enabled            bit                                    default 1                 not null comment '是否启用分佣',
+    brokerage_enabled_condition  tinyint                                default 0                 not null comment '分佣模式:1-人人分销 2-指定分销',
+    brokerage_bind_mode          tinyint                                default 0                 not null comment '分销关系绑定模式: 1-没有推广人,2-新用户, 3-扫码覆盖',
+    brokerage_post_urls          varchar(2000)                          default ''                null comment '分销海报图地址数组',
+    brokerage_first_percent      int                                    default 0                 not null comment '一级返佣比例',
+    brokerage_second_percent     int                                    default 0                 not null comment '二级返佣比例',
+    brokerage_withdraw_min_price int                                    default 0                 not null comment '用户提现最低金额',
+    brokerage_bank_names         varchar(200)                           default ''                not null comment '提现银行(字典类型=brokerage_bank_name)',
+    brokerage_frozen_days        int                                    default 7                 not null comment '佣金冻结时间(天)',
+    brokerage_withdraw_type      varchar(32)                            default '1,2,3,4'         not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝',
+    creator                      varchar(64) collate utf8mb4_unicode_ci default ''                null comment '创建者',
+    create_time                  datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater                      varchar(64) collate utf8mb4_unicode_ci default ''                null comment '更新者',
+    update_time                  datetime                               default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted                      bit                                    default b'0'              not null comment '是否删除',
+    tenant_id                    bigint                                 default 0                 not null comment '租户编号'
+) comment '交易中心配置';
+
+-- 增加分销用户扩展表
+create table trade_brokerage_user
+(
+    id                bigint auto_increment comment '用户编号' primary key,
+    bind_user_id      bigint                                                           null comment '推广员编号',
+    bind_user_time    datetime                                                         null comment '推广员绑定时间',
+    brokerage_enabled bit                                    default 1                 not null comment '是否成为推广员',
+    brokerage_time    datetime                                                         null comment '成为分销员时间',
+    price             int                                    default 0                 not null comment '可用佣金',
+    frozen_price      int                                    default 0                 not null comment '冻结佣金',
+    creator           varchar(64) collate utf8mb4_unicode_ci default ''                null comment '创建者',
+    create_time       datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater           varchar(64) collate utf8mb4_unicode_ci default ''                null comment '更新者',
+    update_time       datetime                               default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted           bit                                    default b'0'              not null comment '是否删除',
+    tenant_id         bigint                                 default 0                 not null comment '租户编号'
+) comment '分销用户';
+
+create index idx_invite_user_id on trade_brokerage_user (bind_user_id) comment '推广员编号';
+create index idx_agent on trade_brokerage_user (brokerage_enabled) comment '是否成为推广员';
+
+
+create table trade_brokerage_record
+(
+    id            int auto_increment comment '编号'
+        primary key,
+    user_id       bigint                                                           not null comment '用户编号',
+    biz_id        varchar(64)                            default ''                not null comment '业务编号',
+    biz_type      tinyint                                default 0                 not null comment '业务类型:0-订单,1-提现',
+    title         varchar(64)                            default ''                not null comment '标题',
+    price         int                                    default 0                 not null comment '金额',
+    total_price   int                                    default 0                 not null comment '当前总佣金',
+    description   varchar(500)                           default ''                not null comment '说明',
+    status        tinyint                                default 0                 not null comment '状态:0-待结算,1-已结算,2-已取消',
+    frozen_days   int                                    default 0                 not null comment '冻结时间(天)',
+    unfreeze_time datetime                                                         null comment '解冻时间',
+    creator       varchar(64) collate utf8mb4_general_ci default ''                null comment '创建者',
+    create_time   datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater       varchar(64) collate utf8mb4_general_ci default ''                null comment '更新者',
+    update_time   datetime                               default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted       bit                                    default b'0'              not null comment '是否删除',
+    tenant_id     bigint                                 default 0                 not null comment '租户编号'
+)
+    comment '佣金记录';
+
+create index idx_user_id on trade_brokerage_record (user_id) comment '用户编号';
+create index idx_biz on trade_brokerage_record (biz_type, biz_id) comment '业务';
+create index idx_status on trade_brokerage_record (status) comment '状态';
+
+
+create table trade_brokerage_withdraw
+(
+    id                  int auto_increment comment '编号'
+        primary key,
+    user_id             bigint                                                           not null comment '用户编号',
+    price               int                                    default 0                 not null comment '提现金额',
+    fee_price           int                                    default 0                 not null comment '提现手续费',
+    total_price         int                                    default 0                 not null comment '当前总佣金',
+    type                tinyint                                default 0                 not null comment '提现类型:1-钱包;2-银行卡;3-微信;4-支付宝',
+    name                varchar(64)                                                      null comment '真实姓名',
+    account_no          varchar(64)                                                      null comment '账号',
+    bank_name           varchar(100)                                                     null comment '银行名称',
+    bank_address        varchar(200)                                                     null comment '开户地址',
+    account_qr_code_url varchar(512)                                                     null comment '收款码',
+    status              tinyint(2)                             default 0                 not null comment '状态:0-审核中,10-审核通过 20-审核不通过;预留:11 - 提现成功;21-提现失败',
+    audit_reason        varchar(128)                                                     null comment '审核驳回原因',
+    audit_time          datetime                                                         null comment '审核时间',
+    remark              varchar(500)                                                     null comment '备注',
+    creator             varchar(64) collate utf8mb4_general_ci default ''                null comment '创建者',
+    create_time         datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater             varchar(64) collate utf8mb4_general_ci default ''                null comment '更新者',
+    update_time         datetime                               default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted             bit                                    default b'0'              not null comment '是否删除',
+    tenant_id           bigint                                 default 0                 not null comment '租户编号'
+)
+    comment '佣金提现';
+
+create index idx_user_id on trade_brokerage_withdraw (user_id) comment '用户编号';
+create index idx_audit_status on trade_brokerage_withdraw (status) comment '状态';
+
+-- 增加字典
+insert into system_dict_type(type, name)
+values ('brokerage_enabled_condition', '分佣模式');
+insert into system_dict_data(dict_type, label, value, sort, remark)
+values ('brokerage_enabled_condition', '人人分销', 1, 1, '所有用户都可以分销'),
+       ('brokerage_enabled_condition', '指定分销', 2, 2, '仅可后台手动设置推广员');
+
+insert into system_dict_type(type, name)
+values ('brokerage_bind_mode', '分销关系绑定模式');
+insert into system_dict_data(dict_type, label, value, sort, remark)
+values ('brokerage_bind_mode', '没有推广人', 1, 1, '只要用户没有推广人,随时都可以绑定推广关系'),
+       ('brokerage_bind_mode', '新用户', 2, 2, '仅新用户注册时才能绑定推广关系'),
+       ('brokerage_bind_mode', '扫码覆盖', 3, 3, '如果用户已经有推广人,推广人会被变更');
+
+insert into system_dict_type(type, name)
+values ('brokerage_withdraw_type', '佣金提现类型');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_withdraw_type', '钱包', 1, 1),
+       ('brokerage_withdraw_type', '银行卡', 2, 2),
+       ('brokerage_withdraw_type', '微信', 3, 3),
+       ('brokerage_withdraw_type', '支付宝', 4, 4);
+
+insert into system_dict_type(type, name)
+values ('brokerage_record_biz_type', '佣金记录业务类型');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_record_biz_type', '订单返佣', 1, 1),
+       ('brokerage_record_biz_type', '申请提现', 2, 2);
+
+insert into system_dict_type(type, name)
+values ('brokerage_record_status', '佣金记录状态');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_record_status', '待结算', 0, 0),
+       ('brokerage_record_status', '已结算', 1, 1),
+       ('brokerage_record_status', '已取消', 2, 2);
+
+insert into system_dict_type(type, name)
+values ('brokerage_withdraw_status', '佣金提现状态');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_withdraw_status', '审核中', 0, 0),
+       ('brokerage_withdraw_status', '审核通过', 10, 10),
+       ('brokerage_withdraw_status', '提现成功', 11, 11),
+       ('brokerage_withdraw_status', '审核不通过', 20, 20),
+       ('brokerage_withdraw_status', '提现失败', 21, 21);
+
+insert into system_dict_type(type, name)
+values ('brokerage_bank_name', '佣金提现银行');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_bank_name', '工商银行', 0, 0),
+       ('brokerage_bank_name', '建设银行', 1, 1),
+       ('brokerage_bank_name', '农业银行', 2, 2),
+       ('brokerage_bank_name', '中国银行', 3, 3),
+       ('brokerage_bank_name', '交通银行', 4, 4),
+       ('brokerage_bank_name', '招商银行', 5, 5);
+
+
+-- 交易中心配置:菜单 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('交易中心配置', '', 2, 0, 2072, 'config', 'ep:setting', 'trade/config/index', 0, 'TradeConfig');
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('交易中心配置查询', 'trade:config:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('交易中心配置保存', 'trade:config:save', 3, 2, @parentId, '', '', '', 0);
+
+
+-- 增加菜单:分销
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('分销', '', 1, 5, 2072, 'brokerage', 'fa-solid:project-diagram', '', 0, '');
+-- 按钮父菜单ID
+SELECT @brokerageMenuId := LAST_INSERT_ID();
+
+-- 增加菜单:分销员
+-- 菜单 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('分销用户', '', 2, 0, @brokerageMenuId, 'brokerage-user', 'fa-solid:user-tie', 'trade/brokerage/user/index', 0,
+        'TradeBrokerageUser');
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销用户查询', 'trade:brokerage-user:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销用户推广人查询', 'trade:brokerage-user:user-query', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销用户推广订单查询', 'trade:brokerage-user:order-query', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销用户修改推广资格', 'trade:brokerage-user:update-brokerage-enable', 3, 4, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销用户修改推广员', 'trade:brokerage-user:update-brokerage-user', 3, 5, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销用户清除推广员', 'trade:brokerage-user:clear-brokerage-user', 3, 6, @parentId, '', '', '', 0);
+
+-- 增加菜单:佣金记录
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('佣金记录', '', 2, 1, @brokerageMenuId, 'brokerage-record', 'fa:money', 'trade/brokerage/record/index', 0,
+        'TradeBrokerageRecord');
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('佣金记录查询', 'trade:brokerage-record:query', 3, 1, @parentId, '', '', '', 0);
+
+-- 增加菜单:佣金提现
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('佣金提现', '', 2, 2, @brokerageMenuId, 'brokerage-withdraw', 'fa:credit-card',
+        'trade/brokerage/withdraw/index', 0, 'TradeBrokerageWithdraw');
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('佣金提现查询', 'trade:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);

+ 20 - 21
sql/mysql/pay_wallet.sql

@@ -1,5 +1,5 @@
 -- ----------------------------
--- 支付-钱包表
+-- 会员钱包表
 -- ----------------------------
 DROP TABLE IF EXISTS `pay_wallet`;
 CREATE TABLE `pay_wallet`
@@ -8,8 +8,8 @@ CREATE TABLE `pay_wallet`
     `user_id`        bigint   NOT NULL COMMENT '用户编号',
     `user_type`      tinyint  NOT NULL DEFAULT 0 COMMENT '用户类型',
     `balance`        int      NOT NULL DEFAULT 0 COMMENT '余额,单位分',
-    `total_expense`  bigint      NOT NULL DEFAULT 0 COMMENT '累计支出,单位分',
-    `total_recharge` bigint      NOT NULL DEFAULT 0 COMMENT '累计充值,单位分',
+    `total_expense`  int      NOT NULL DEFAULT 0 COMMENT '累计支出,单位分',
+    `total_recharge` int      NOT NULL DEFAULT 0 COMMENT '累计充值,单位分',
     `creator`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
     `create_time`    datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     `updater`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@@ -17,28 +17,27 @@ CREATE TABLE `pay_wallet`
     `deleted`        bit(1)   NOT NULL DEFAULT b'0' COMMENT '是否删除',
     `tenant_id`      bigint   NOT NULL DEFAULT 0 COMMENT '租户编号',
     PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB COMMENT='支付钱包表';
+) ENGINE=InnoDB COMMENT='会员钱包表';
 
 -- ----------------------------
--- 支付- 钱包余额明细
+-- 会员钱包流水
 -- ----------------------------
 DROP TABLE IF EXISTS `pay_wallet_transaction`;
 CREATE TABLE `pay_wallet_transaction`
 (
-    `id`               bigint      NOT NULL AUTO_INCREMENT COMMENT '编号',
-    `wallet_id`        bigint      NOT NULL COMMENT '会员钱包 id',
-    `biz_type`         tinyint     NOT NULL COMMENT '关联类型',
-    `biz_id`           bigint      NOT NULL COMMENT '关联业务编号',
-    `no`               varchar(64) NOT NULL COMMENT '流水号',
-    `description`      varchar(255)         COMMENT '操作说明',
-    `amount`           int         NOT NULL COMMENT '交易金额, 单位分',
-    `balance`          int         NOT NULL COMMENT '余额, 单位分',
-    `transaction_time` datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '交易时间',
-    `creator`          varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
-    `create_time`      datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    `updater`          varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
-    `update_time`      datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-    `deleted`          bit(1)      NOT NULL DEFAULT b'0' COMMENT '是否删除',
-    `tenant_id`        bigint      NOT NULL DEFAULT 0 COMMENT '租户编号',
+    `id`               bigint       NOT NULL AUTO_INCREMENT COMMENT '编号',
+    `wallet_id`        bigint       NOT NULL COMMENT '会员钱包 id',
+    `biz_type`         tinyint      NOT NULL COMMENT '关联类型',
+    `biz_id`           varchar(64)  NOT NULL COMMENT '关联业务编号',
+    `no`               varchar(64)  NOT NULL COMMENT '流水号',
+    `title`            varchar(128) NOT NULL COMMENT '流水标题',
+    `price`            int          NOT NULL COMMENT '交易金额, 单位分',
+    `balance`          int          NOT NULL COMMENT '余额, 单位分',
+    `creator`          varchar(64)  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+    `create_time`      datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `updater`          varchar(64)  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+    `update_time`      datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`          bit(1)       NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`        bigint       NOT NULL DEFAULT 0 COMMENT '租户编号',
     PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB COMMENT='支付钱包余额明细表';
+) ENGINE=InnoDB COMMENT='会员钱包流水表';

+ 2 - 2
yudao-dependencies/pom.xml

@@ -27,7 +27,7 @@
         <mybatis-plus.version>3.5.3.2</mybatis-plus.version>
         <mybatis-plus-generator.version>3.5.3.2</mybatis-plus-generator.version>
         <dynamic-datasource.version>3.6.1</dynamic-datasource.version>
-        <mybatis-plus-join-boot-starter.version>1.4.5</mybatis-plus-join-boot-starter.version>
+        <mybatis-plus-join.version>1.4.6</mybatis-plus-join.version>
         <redisson.version>3.18.0</redisson.version>
         <dm8.jdbc.version>8.1.2.141</dm8.jdbc.version>
         <!-- 服务保障相关 -->
@@ -220,7 +220,7 @@
             <dependency>
                 <groupId>com.github.yulichang</groupId>
                 <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
-                <version>${mybatis-plus-join-boot-starter.version}</version>
+                <version>${mybatis-plus-join.version}</version>
             </dependency>
 
             <dependency>

+ 1 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java

@@ -41,7 +41,7 @@ public class CommonResult<T> implements Serializable {
      * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
      *
      * @param result 传入的 result 对象
-     * @param <T> 返回的泛型
+     * @param <T>    返回的泛型
      * @return 新的 CommonResult 对象
      */
     public static <T> CommonResult<T> error(CommonResult<?> result) {

+ 7 - 26
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/SortingField.java

@@ -1,5 +1,9 @@
 package cn.iocoder.yudao.framework.common.pojo;
 
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
 import java.io.Serializable;
 
 /**
@@ -7,6 +11,9 @@ import java.io.Serializable;
  *
  * 类名加了 ing 的原因是,避免和 ES SortField 重名。
  */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class SortingField implements Serializable {
 
     /**
@@ -27,30 +34,4 @@ public class SortingField implements Serializable {
      */
     private String order;
 
-    // 空构造方法,解决反序列化
-    public SortingField() {
-    }
-
-    public SortingField(String field, String order) {
-        this.field = field;
-        this.order = order;
-    }
-
-    public String getField() {
-        return field;
-    }
-
-    public SortingField setField(String field) {
-        this.field = field;
-        return this;
-    }
-
-    public String getOrder() {
-        return order;
-    }
-
-    public SortingField setOrder(String order) {
-        this.order = order;
-        return this;
-    }
 }

+ 12 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/util/MoneyUtils.java → yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.pay.util;
+package cn.iocoder.yudao.framework.common.util.number;
 
 import cn.hutool.core.util.NumberUtil;
 
@@ -23,6 +23,17 @@ public class MoneyUtils {
         return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue();
     }
 
+    /**
+     * 计算百分比金额,向下传入
+     *
+     * @param price 金额
+	 * @param rate 百分比,例如说 56.77% 则传入 56.77
+     * @return 百分比金额
+     */
+    public static Integer calculateRatePriceFloor(Integer price, Double rate) {
+        return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue();
+    }
+
 	/**
 	 * 计算百分比金额
 	 *

+ 0 - 63
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/delegate/DelegatePayClient.java

@@ -1,63 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.client.impl.delegate;
-
-import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
-import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
-
-import java.util.Map;
-
-// TODO @jason:其它模块,主要是无法 pay client 初始化存在问题,所以我感觉,是不是可以搞个 PayClientInitializer 接口。这样,PayClientFactory 去 get 这个支付模式对应的 PayClientInitializer,通过它来创建。具体注入的地方,可以在 PayChannel init 方法那;
-/**
- * 代理支付 Client 的抽象类。
- *
- * 用于支付 Client 由其它模块实现,例如钱包支付
- *
- * @author jason
- */
-public abstract class DelegatePayClient<Config extends PayClientConfig> extends AbstractPayClient<PayClientConfig> {
-
-    private final DelegatePayClient<Config> delegate;
-
-    public DelegatePayClient(Long channelId, String channelCode, PayClientConfig config) {
-        super(channelId, channelCode, config);
-        delegate = this;
-    }
-
-    @Override
-    protected void doInit() {
-        delegate.doInit();
-    }
-
-    @Override
-    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)  {
-        return delegate.doUnifiedOrder(reqDTO);
-    }
-
-    @Override
-    protected PayOrderRespDTO doGetOrder(String outTradeNo)  {
-        return delegate.doGetOrder(outTradeNo);
-    }
-
-    @Override
-    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
-        return delegate.doUnifiedRefund(reqDTO);
-    }
-
-    @Override
-    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
-        return delegate.doGetRefund(outTradeNo, outRefundNo);
-    }
-
-    @Override
-    protected PayRefundRespDTO doParseRefundNotify(Map<String,String> params, String body)  {
-        return delegate.doParseRefundNotify(params, body);
-    }
-
-    @Override
-    protected PayOrderRespDTO doParseOrderNotify(Map<String,String> params, String body)  {
-        return delegate.doParseOrderNotify(params, body);
-    }
-}

+ 3 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDT
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
+import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
 
 import java.time.LocalDateTime;
@@ -17,11 +18,11 @@ import java.util.Map;
  *
  * @author jason
  */
-public class MockPayClient extends AbstractPayClient<MockPayClientConfig> {
+public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
 
     private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS";
 
-    public MockPayClient(Long channelId, MockPayClientConfig config) {
+    public MockPayClient(Long channelId, NonePayClientConfig config) {
         super(channelId, PayChannelEnum.MOCK.getCode(), config);
     }
 

+ 0 - 28
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClientConfig.java

@@ -1,28 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
-
-import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
-import lombok.Data;
-
-import javax.validation.Validator;
-
-/**
- * 模拟支付的 PayClientConfig 实现类
- *
- * @author jason
- */
-@Data
-public class MockPayClientConfig implements PayClientConfig {
-
-    /**
-     * 配置名称
-     *
-     * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称
-     */
-    private String name;
-
-    @Override
-    public void validate(Validator validator) {
-        // 模拟支付配置无需校验
-    }
-
-}

+ 1 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java

@@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil;
 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
-import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -30,7 +29,7 @@ public enum PayChannelEnum {
     ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
     ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
 
-    MOCK("mock", "模拟支付", MockPayClientConfig.class),
+    MOCK("mock", "模拟支付", NonePayClientConfig.class),
 
     WALLET("wallet", "钱包支付", NonePayClientConfig.class);
 

+ 9 - 0
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java

@@ -4,6 +4,7 @@ import cn.hutool.core.util.EnumUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;
 import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniAppRequest;
+import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMpRequest;
 import com.xingyuv.jushauth.cache.AuthStateCache;
 import com.xingyuv.jushauth.config.AuthConfig;
 import com.xingyuv.jushauth.config.AuthSource;
@@ -13,6 +14,8 @@ import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
 
 import java.lang.reflect.Method;
 
+import static com.xingyuv.jushauth.config.AuthDefaultSource.WECHAT_MP;
+
 /**
  * 第三方授权拓展 request 工厂类
  * 为使得拓展配置 {@link AuthConfig} 和默认配置齐平,所以自定义本工厂类
@@ -55,6 +58,12 @@ public class YudaoAuthRequestFactory extends AuthRequestFactory {
     }
 
     protected AuthRequest getExtendRequest(String source) {
+        // TODO 芋艿:临时兼容 justauth 迁移的类型不对问题;
+        if (WECHAT_MP.name().equalsIgnoreCase(source)) {
+            AuthConfig config = properties.getType().get(WECHAT_MP.name());
+            return new AuthWeChatMpRequest(config, authStateCache);
+        }
+
         AuthExtendSource authExtendSource;
         try {
             authExtendSource = EnumUtil.fromString(AuthExtendSource.class, source.toUpperCase());

+ 178 - 0
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMpRequest.java

@@ -0,0 +1,178 @@
+package cn.iocoder.yudao.framework.social.core.request;
+
+import com.alibaba.fastjson.JSONObject;
+import com.xingyuv.jushauth.cache.AuthStateCache;
+import com.xingyuv.jushauth.config.AuthConfig;
+import com.xingyuv.jushauth.config.AuthDefaultSource;
+import com.xingyuv.jushauth.enums.AuthResponseStatus;
+import com.xingyuv.jushauth.enums.AuthUserGender;
+import com.xingyuv.jushauth.enums.scope.AuthWechatMpScope;
+import com.xingyuv.jushauth.exception.AuthException;
+import com.xingyuv.jushauth.model.AuthCallback;
+import com.xingyuv.jushauth.model.AuthResponse;
+import com.xingyuv.jushauth.model.AuthToken;
+import com.xingyuv.jushauth.model.AuthUser;
+import com.xingyuv.jushauth.request.AuthDefaultRequest;
+import com.xingyuv.jushauth.utils.AuthScopeUtils;
+import com.xingyuv.jushauth.utils.GlobalAuthUtils;
+import com.xingyuv.jushauth.utils.HttpUtils;
+import com.xingyuv.jushauth.utils.UrlBuilder;
+
+/**
+ * 微信公众平台登录
+ *
+ * @author yangkai.shen (https://xkcoding.com)
+ * @since 1.1.0
+ */
+public class AuthWeChatMpRequest extends AuthDefaultRequest {
+    public AuthWeChatMpRequest(AuthConfig config) {
+        super(config, AuthDefaultSource.WECHAT_MP);
+    }
+
+    public AuthWeChatMpRequest(AuthConfig config, AuthStateCache authStateCache) {
+        super(config, AuthDefaultSource.WECHAT_MP, authStateCache);
+    }
+
+    /**
+     * 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
+     *
+     * @param authCallback 回调返回的参数
+     * @return 所有信息
+     */
+    @Override
+    protected AuthToken getAccessToken(AuthCallback authCallback) {
+        return this.getToken(accessTokenUrl(authCallback.getCode()));
+    }
+
+    @Override
+    protected AuthUser getUserInfo(AuthToken authToken) {
+        String openId = authToken.getOpenId();
+
+        String response = doGetUserInfo(authToken);
+        JSONObject object = JSONObject.parseObject(response);
+
+        this.checkResponse(object);
+
+        String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city"));
+
+        if (object.containsKey("unionid")) {
+            authToken.setUnionId(object.getString("unionid"));
+        }
+
+        return AuthUser.builder()
+                .rawUserInfo(object)
+                .username(object.getString("nickname"))
+                .nickname(object.getString("nickname"))
+                .avatar(object.getString("headimgurl"))
+                .location(location)
+                .uuid(openId)
+                .gender(AuthUserGender.getWechatRealGender(object.getString("sex")))
+                .token(authToken)
+                .source(source.toString())
+                .build();
+    }
+
+    @Override
+    public AuthResponse refresh(AuthToken oldToken) {
+        return AuthResponse.builder()
+                .code(AuthResponseStatus.SUCCESS.getCode())
+                .data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken())))
+                .build();
+    }
+
+    /**
+     * 检查响应内容是否正确
+     *
+     * @param object 请求响应内容
+     */
+    private void checkResponse(JSONObject object) {
+        if (object.containsKey("errcode")) {
+            throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg"));
+        }
+    }
+
+    /**
+     * 获取token,适用于获取access_token和刷新token
+     *
+     * @param accessTokenUrl 实际请求token的地址
+     * @return token对象
+     */
+    private AuthToken getToken(String accessTokenUrl) {
+        String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl).getBody();
+        JSONObject accessTokenObject = JSONObject.parseObject(response);
+
+        this.checkResponse(accessTokenObject);
+
+        return AuthToken.builder()
+                .accessToken(accessTokenObject.getString("access_token"))
+                .refreshToken(accessTokenObject.getString("refresh_token"))
+                .expireIn(accessTokenObject.getIntValue("expires_in"))
+                .openId(accessTokenObject.getString("openid"))
+                .scope(accessTokenObject.getString("scope"))
+                .build();
+    }
+
+    /**
+     * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
+     *
+     * @param state state 验证授权流程的参数,可以防止csrf
+     * @return 返回授权地址
+     * @since 1.9.3
+     */
+    @Override
+    public String authorize(String state) {
+        return UrlBuilder.fromBaseUrl(source.authorize())
+                .queryParam("appid", config.getClientId())
+                .queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
+                .queryParam("response_type", "code")
+                .queryParam("scope", this.getScopes(",", false, AuthScopeUtils.getDefaultScopes(AuthWechatMpScope.values())))
+                .queryParam("state", getRealState(state).concat("#wechat_redirect"))
+                .build();
+    }
+
+    /**
+     * 返回获取accessToken的url
+     *
+     * @param code 授权码
+     * @return 返回获取accessToken的url
+     */
+    @Override
+    protected String accessTokenUrl(String code) {
+        return UrlBuilder.fromBaseUrl(source.accessToken())
+                .queryParam("appid", config.getClientId())
+                .queryParam("secret", config.getClientSecret())
+                .queryParam("code", code)
+                .queryParam("grant_type", "authorization_code")
+                .build();
+    }
+
+    /**
+     * 返回获取userInfo的url
+     *
+     * @param authToken 用户授权后的token
+     * @return 返回获取userInfo的url
+     */
+    @Override
+    protected String userInfoUrl(AuthToken authToken) {
+        return UrlBuilder.fromBaseUrl(source.userInfo())
+                .queryParam("access_token", authToken.getAccessToken())
+                .queryParam("openid", authToken.getOpenId())
+                .queryParam("lang", "zh_CN")
+                .build();
+    }
+
+    /**
+     * 返回获取userInfo的url
+     *
+     * @param refreshToken getAccessToken方法返回的refreshToken
+     * @return 返回获取userInfo的url
+     */
+    @Override
+    protected String refreshTokenUrl(String refreshToken) {
+        return UrlBuilder.fromBaseUrl(source.refresh())
+                .queryParam("appid", config.getClientId())
+                .queryParam("grant_type", "refresh_token")
+                .queryParam("refresh_token", refreshToken)
+                .build();
+    }
+}

+ 3 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -18,6 +18,9 @@ import java.util.List;
 
 /**
  * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
+ *
+ * 1. {@link BaseMapper} 为 MyBatis Plus 的基础接口,提供基础的 CRUD 能力
+ * 2. {@link MPJBaseMapper} 为 MyBatis Plus Join 的基础接口,提供连表 Join 能力
  */
 public interface BaseMapperX<T> extends MPJBaseMapper<T> {
 

+ 10 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java

@@ -60,4 +60,14 @@ public class ProductSkuRespDTO {
      */
     private Double volume;
 
+    // TODO @puhui:这 2 字段,需要改下;firstBrokerageRecord、secondBrokerageRecord;和分佣保持一致;
+    /**
+     * 一级分销的佣金,单位:分
+     */
+    private Integer subCommissionFirstPrice;
+    /**
+     * 二级分销的佣金,单位:分
+     */
+    private Integer subCommissionSecondPrice;
+
 }

+ 18 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.promotion.api.bargain;
+
+/**
+ * 砍价活动 Api 接口
+ *
+ * @author HUIHUI
+ */
+public interface BargainActivityApi {
+
+    /**
+     * 更新砍价活动库存
+     *
+     * @param activityId 砍价活动编号
+     * @param count      购买数量
+     */
+    void updateBargainActivityStock(Long activityId, Integer count);
+
+}

+ 2 - 23
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/dto/BargainRecordCreateReqDTO.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.bargain.dto;
 
 import lombok.Data;
 
-import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 
 // TODO @芋艿:这块要在看看
@@ -40,17 +39,7 @@ public class BargainRecordCreateReqDTO {
      */
     @NotNull(message = "订单编号不能为空")
     private Long orderId;
-    // TODO @puhui999:spuName、picUrl、 之类字段不用传递;
-    /**
-     * 商品名字
-     */
-    @NotEmpty(message = "商品名字不能为空")
-    private String spuName;
-    /**
-     * 商品图片
-     */
-    @NotEmpty(message = "商品图片不能为空")
-    private String picUrl;
+
     /**
      * 砍价商品单价
      */
@@ -61,17 +50,7 @@ public class BargainRecordCreateReqDTO {
      */
     @NotNull(message = "商品原价不能为空")
     private Integer price;
-    // TODO @puhui999:nickname、avatar 不用传递,去查询;
-    /**
-     * 用户昵称
-     */
-    @NotEmpty(message = "用户昵称不能为空")
-    private String nickname;
-    /**
-     * 用户头像
-     */
-    @NotEmpty(message = "用户头像不能为空")
-    private String avatar;
+
     /**
      * 开团状态:进行中 砍价成功 砍价失败
      */

+ 21 - 5
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java

@@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.promotion.api.combination;
 
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
 
 import javax.validation.Valid;
+import java.time.LocalDateTime;
 import java.util.List;
 
 // TODO @芋艿:后面也再撸撸这几个接口
@@ -51,13 +51,29 @@ public interface CombinationRecordApi {
      */
     void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount);
 
-    // TODO @puhui999:是不是搞成具体的方法,拼团成功,拼团失败,这种方法;
+    /**
+     * 更新拼团状态为成功
+     *
+     * @param userId  用户编号
+     * @param orderId 订单编号
+     */
+    void updateRecordStatusToSuccess(Long userId, Long orderId);
 
     /**
-     * 更新开团记录状态
+     * 更新拼团状态为失败
      *
-     * @param reqDTO 请求 DTO
+     * @param userId  用户编号
+     * @param orderId 订单编号
+     */
+    void updateRecordStatusToFailed(Long userId, Long orderId);
+
+    /**
+     * 更新拼团状态为 进行中
+     *
+     * @param userId    用户编号
+     * @param orderId   订单编号
+     * @param startTime 开始时间
      */
-    void updateCombinationRecordStatus(CombinationRecordUpdateStatusReqDTO reqDTO);
+    void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime);
 
 }

+ 0 - 39
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordUpdateStatusReqDTO.java

@@ -1,39 +0,0 @@
-package cn.iocoder.yudao.module.promotion.api.combination.dto;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotNull;
-import java.time.LocalDateTime;
-
-/**
- * 拼团记录的更新状态 Request DTO
- *
- * @author HUIHUI
- */
-@Data
-public class CombinationRecordUpdateStatusReqDTO {
-
-    /**
-     * 用户编号
-     */
-    @NotNull(message = "用户编号不能为空")
-    private Long userId;
-
-    /**
-     * 订单编号
-     */
-    @NotNull(message = "订单编号不能为空")
-    private Long orderId;
-
-    /**
-     * 开团状态:正在开团 拼团成功 拼团失败
-     */
-    @NotNull(message = "开团状态不能为空")
-    private Integer status;
-
-    /**
-     * 团开始时间
-     */
-    private LocalDateTime startTime;
-
-}

+ 19 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.promotion.api.seckill;
+
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
+
+/**
+ * 秒杀活动 API 接口
+ *
+ * @author HUIHUI
+ */
+public interface SeckillActivityApi {
+
+    /**
+     * 更新秒杀库存
+     *
+     * @param updateStockReqDTO 请求
+     */
+    void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO);
+
+}

+ 50 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityUpdateStockReqDTO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.promotion.api.seckill.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 更新秒杀库存 request DTO
+ *
+ * @author HUIHUI
+ */
+@Data
+public class SeckillActivityUpdateStockReqDTO {
+
+    // TODO @puhui999:参数校验
+
+    // TODO @puhui999:秒杀的话,一次只能购买一种商品哈;不能多个哈;
+
+    /**
+     * 活动编号
+     */
+    private Long activityId;
+    /**
+     * 总购买数量
+     */
+    private Integer count;
+    /**
+     * 活动商品
+     */
+    private List<Item> items;
+
+    @Data
+    public static class Item {
+
+        /**
+         * SPU 编号
+         */
+        private Long spuId;
+        /**
+         * SKU 编号
+         */
+        private Long skuId;
+        /**
+         * 购买数量
+         */
+        private Integer count;
+
+    }
+
+}

+ 1 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java

@@ -55,6 +55,7 @@ public interface ErrorCodeConstants {
     ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_003, "秒杀活动已关闭,不能修改");
     ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除");
     ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭");
+    ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1013008006, "更新秒杀活动库存失败,原因秒杀库存不足");
 
     // ========== 秒杀时段 1-013-009-000 ==========
     ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在");

+ 41 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApiImpl.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.promotion.api.bargain;
+
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
+import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.BARGAIN_ACTIVITY_NOT_EXISTS;
+
+/**
+ * 砍价活动 Api 接口实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+public class BargainActivityApiImpl implements BargainActivityApi {
+
+    @Resource
+    private BargainActivityService bargainActivityService;
+
+    @Override
+    public void updateBargainActivityStock(Long activityId, Integer count) {
+        // TODO @puhui999:可以整个实现到 bargainActivityService 中
+        // 查询砍价活动
+        BargainActivityDO activity = bargainActivityService.getBargainActivity(activityId);
+        if (activity == null) {
+            throw exception(BARGAIN_ACTIVITY_NOT_EXISTS);
+        }
+
+        // 更新砍价库存
+        // TODO @puhui999:考虑下并发更新问题
+        BargainActivityUpdateReqVO reqVO = new BargainActivityUpdateReqVO();
+        reqVO.setId(activityId);
+        reqVO.setStock(activity.getStock() - count);
+        bargainActivityService.updateBargainActivity(reqVO);
+    }
+
+}

+ 14 - 7
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java

@@ -2,13 +2,13 @@ package cn.iocoder.yudao.module.promotion.api.combination;
 
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
@@ -43,12 +43,19 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
     }
 
     @Override
-    public void updateCombinationRecordStatus(CombinationRecordUpdateStatusReqDTO reqDTO) {
-        if (null == reqDTO.getStartTime()) {
-            recordService.updateCombinationRecordStatusByUserIdAndOrderId(reqDTO);
-        } else {
-            recordService.updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(reqDTO);
-        }
+    public void updateRecordStatusToSuccess(Long userId, Long orderId) {
+        recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.SUCCESS.getStatus(), userId, orderId);
+    }
+
+    @Override
+    public void updateRecordStatusToFailed(Long userId, Long orderId) {
+        recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.FAILED.getStatus(), userId, orderId);
+    }
+
+    @Override
+    public void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime) {
+        recordService.updateRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordStatusEnum.IN_PROGRESS.getStatus(),
+                userId, orderId, startTime);
     }
 
 }

+ 84 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java

@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.promotion.api.seckill;
+
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
+import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_ACTIVITY_UPDATE_STOCK_FAIL;
+
+/**
+ * 秒杀活动接口 Api 接口实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+public class SeckillActivityApiImpl implements SeckillActivityApi {
+
+    @Resource
+    private SeckillActivityService activityService;
+
+    // TODO @puhui:建议这块弄到 activityService 实现哈;
+    // TODO @puhui:这个方法,要考虑事务性
+    @Override
+    public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
+        // TODO @puhui999:长方法,最好有 1.1 1.2 2.1 这种步骤哈;
+        SeckillActivityDO seckillActivity = activityService.getSeckillActivity(updateStockReqDTO.getActivityId());
+        if (seckillActivity.getStock() < updateStockReqDTO.getCount()) {
+            throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
+        }
+        // 获取活动商品
+        // TODO @puhui999:在一个方法里,dos 和 dolist 最好保持一致,要么用 s,要么用 list 哈;
+        List<SeckillProductDO> productDOs = activityService.getSeckillProductListByActivityId(updateStockReqDTO.getActivityId());
+        // TODO @puhui999:这个是不是搞成  CollectionUtils.convertMultiMap()
+        List<SeckillActivityUpdateStockReqDTO.Item> items = updateStockReqDTO.getItems();
+        Map<Long, List<Long>> map = new HashMap<>();
+        items.forEach(item -> {
+            if (map.containsKey(item.getSpuId())) {
+                List<Long> skuIds = map.get(item.getSpuId());
+                skuIds.add(item.getSkuId());
+                map.put(item.getSpuId(), skuIds);
+            } else {
+                List<Long> list = new ArrayList<>();
+                list.add(item.getSkuId());
+                map.put(item.getSpuId(), list);
+            }
+        });
+        // 过滤出购买的商品
+        // TODO @puhui999:productDOList 可以简化成 productList;一般来说,do 之类不用带着哈,在变量里;
+        List<SeckillProductDO> productDOList = CollectionUtils.filterList(productDOs, item -> map.get(item.getSpuId()).contains(item.getSkuId()));
+        Map<Long, SeckillActivityUpdateStockReqDTO.Item> productDOMap = CollectionUtils.convertMap(items, SeckillActivityUpdateStockReqDTO.Item::getSkuId, p -> p);
+        // 检查活动商品库存是否充足
+        // TODO @puhui999:避免 b 这种无业务含义的变量;
+        boolean b = CollectionUtils.anyMatch(productDOList, item -> {
+            SeckillActivityUpdateStockReqDTO.Item item1 = productDOMap.get(item.getSkuId());
+            return (item.getStock() < item1.getCount()) || (item.getStock() - item1.getCount()) < 0;
+        });
+        if (b) {
+            throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
+        }
+        // TODO @puhui999:类似 doList,应该和下面的 update 逻辑粘的更紧密一点;so 在空行的时候,应该挪到 74 之后里去;甚至更合理,应该是 79 之后;说白了,逻辑要分块,每个模块涉及的代码要紧密在一起;
+        List<SeckillProductDO> doList = CollectionUtils.convertList(productDOList, item -> {
+            item.setStock(item.getStock() - productDOMap.get(item.getSkuId()).getCount());
+            return item;
+        });
+
+        // 更新活动库存
+        // TODO @puhui999:考虑下并发更新
+        seckillActivity.setStock(seckillActivity.getStock() + updateStockReqDTO.getCount());
+        seckillActivity.setTotalStock(seckillActivity.getTotalStock() - updateStockReqDTO.getCount());
+        activityService.updateSeckillActivity(seckillActivity);
+        // 更新活动商品库存
+        activityService.updateSeckillActivityProductList(doList);
+    }
+
+}

+ 5 - 7
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.promotion.controller.app.combination;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordDetailRespVO;
 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordRespVO;
 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordSummaryRespVO;
@@ -16,8 +16,8 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.validation.constraints.Max;
 import java.time.Duration;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -52,14 +52,13 @@ public class AppCombinationRecordController {
             @RequestParam(value = "activityId", required = false) Long activityId,
             @RequestParam("status") Integer status,
             @RequestParam(value = "count", defaultValue = "20") @Max(20) Integer count) {
-        ZoneId zoneId = ZoneId.systemDefault();
         List<AppCombinationRecordRespVO> list = new ArrayList<>();
         for (int i = 1; i <= count; i++) {
             AppCombinationRecordRespVO record = new AppCombinationRecordRespVO();
             record.setId((long) i);
             record.setNickname("用户" + i);
             record.setAvatar("头像" + i);
-            record.setExpireTime(LocalDateTime.ofInstant(new Date().toInstant(), zoneId));
+            record.setExpireTime(LocalDateTime.now());
             record.setUserSize(10);
             record.setUserCount(i);
             record.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
@@ -74,14 +73,13 @@ public class AppCombinationRecordController {
     @Operation(summary = "获得拼团记录明细")
     @Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024")
     public CommonResult<AppCombinationRecordDetailRespVO> getCombinationRecordDetail(@RequestParam("id") Long id) {
-        ZoneId zoneId = ZoneId.systemDefault();
         AppCombinationRecordDetailRespVO detail = new AppCombinationRecordDetailRespVO();
         // 团长
         AppCombinationRecordRespVO headRecord = new AppCombinationRecordRespVO();
         headRecord.setId(1L);
         headRecord.setNickname("用户" + 1);
         headRecord.setAvatar("头像" + 1);
-        headRecord.setExpireTime((LocalDateTime.ofInstant(DateUtils.addTime(Duration.ofDays(1)).toInstant(), zoneId)));
+        headRecord.setExpireTime(LocalDateTimeUtils.addTime(Duration.ofDays(1)));
         headRecord.setUserSize(10);
         headRecord.setUserCount(3);
         headRecord.setStatus(1);
@@ -96,7 +94,7 @@ public class AppCombinationRecordController {
             record.setId((long) i);
             record.setNickname("用户" + i);
             record.setAvatar("头像" + i);
-            record.setExpireTime(LocalDateTime.ofInstant(new Date().toInstant(), zoneId));
+            record.setExpireTime(LocalDateTime.now());
             record.setUserSize(10);
             record.setUserCount(i);
             record.setStatus(1);

+ 45 - 33
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationRecordDO.java

@@ -9,6 +9,7 @@ import lombok.*;
 
 import java.time.LocalDateTime;
 
+// TODO 芋艿:把字段的顺序,和 do 顺序对齐下
 /**
  * 拼团记录 DO
  *
@@ -27,34 +28,28 @@ import java.time.LocalDateTime;
 @AllArgsConstructor
 public class CombinationRecordDO extends BaseDO {
 
+    /**
+     * 编号,主键自增
+     */
     @TableId
     private Long id;
+
     /**
      * 拼团活动编号
+     *
+     * 关联 {@link CombinationActivityDO#getId()}
      */
     private Long activityId;
     /**
-     * SPU 编号
-     */
-    private Long spuId;
-    /**
-     * SKU 编号
-     */
-    private Long skuId;
-    /**
-     * 用户编号
-     */
-    private Long userId;
-    /**
-     * 订单编号
+     * 拼团商品单价
+     *
+     * 冗余 {@link CombinationProductDO#getCombinationPrice()}
      */
-    private Long orderId;
+    private Integer combinationPrice;
     /**
-     * 团长编号
-     *
-     * 关联 {@link CombinationRecordDO#getId()}
+     * SPU 编号
      */
-    private Long headId;
+    private Long spuId;
     /**
      * 商品名字
      */
@@ -64,9 +59,14 @@ public class CombinationRecordDO extends BaseDO {
      */
     private String picUrl;
     /**
-     * 拼团商品单价
+     * SKU 编号
      */
-    private Integer combinationPrice;
+    private Long skuId;
+
+    /**
+     * 用户编号
+     */
+    private Long userId;
     /**
      * 用户昵称
      */
@@ -75,22 +75,44 @@ public class CombinationRecordDO extends BaseDO {
      * 用户头像
      */
     private String avatar;
+
+    /**
+     * 团长编号
+     *
+     * 关联 {@link CombinationRecordDO#getId()}
+     */
+    private Long headId;
     /**
      * 开团状态
      *
      * 关联 {@link CombinationRecordStatusEnum}
      */
     private Integer status;
+    /**
+     * 订单编号
+     */
+    private Long orderId;
+    /**
+     * 开团需要人数
+     *
+     * 关联 {@link CombinationActivityDO#getUserSize()}
+     */
+    private Integer userSize;
+    /**
+     * 已加入拼团人数
+     */
+    private Integer userCount;
     /**
      * 是否虚拟成团
      */
     private Boolean virtualGroup;
+
     /**
-     * 过期时间,单位:小时
+     * 过期时间
      *
-     * 关联 {@link CombinationActivityDO#getLimitDuration()}
+     * 基于 {@link CombinationRecordDO#getStartTime()} + {@link CombinationActivityDO#getLimitDuration()} 计算
      */
-    private Integer expireTime;
+    private LocalDateTime expireTime;
     /**
      * 开始时间 (订单付款后开始的时间)
      */
@@ -99,15 +121,5 @@ public class CombinationRecordDO extends BaseDO {
      * 结束时间(成团时间/失败时间)
      */
     private LocalDateTime endTime;
-    /**
-     * 开团需要人数
-     *
-     * 关联 {@link CombinationActivityDO#getUserSize()}
-     */
-    private Integer userSize;
-    /**
-     * 已加入拼团人数
-     */
-    private Integer userCount;
 
 }

+ 10 - 5
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.promotion.service.combination;
 
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
@@ -16,9 +16,11 @@ public interface CombinationRecordService {
     /**
      * 更新拼团状态
      *
-     * @param reqDTO 请求 DTO
+     * @param status 状态
+     * @param userId 用户编号
+     * @param orderId 订单编号
      */
-    void updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO);
+    void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId);
 
     /**
      * 创建拼团记录
@@ -30,9 +32,12 @@ public interface CombinationRecordService {
     /**
      * 更新拼团状态和开始时间
      *
-     * @param reqDTO 请求 DTO
+     * @param status 状态
+     * @param userId 用户编号
+     * @param orderId 订单编号
+     * @param startTime 开始时间
      */
-    void updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO);
+    void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime);
 
     /**
      * 获得拼团状态

+ 10 - 11
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.service.combination;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
@@ -21,6 +20,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 
 // TODO 芋艿:等拼团记录做完,完整 review 下
+
 /**
  * 拼团记录 Service 实现类
  *
@@ -38,27 +38,27 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO) {
+    public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) {
         // 校验拼团是否存在
-        CombinationRecordDO recordDO = validateCombinationRecord(reqDTO.getUserId(), reqDTO.getOrderId());
+        CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
 
         // 更新状态
-        recordDO.setStatus(reqDTO.getStatus());
+        recordDO.setStatus(status);
         recordMapper.updateById(recordDO);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO) {
-        CombinationRecordDO recordDO = validateCombinationRecord(reqDTO.getUserId(), reqDTO.getOrderId());
+    public void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime) {
+        CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
         // 更新状态
-        recordDO.setStatus(reqDTO.getStatus());
+        recordDO.setStatus(status);
         // 更新开始时间
-        recordDO.setStartTime(reqDTO.getStartTime());
+        recordDO.setStartTime(startTime);
         recordMapper.updateById(recordDO);
 
         // 更新拼团参入人数
-        List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), reqDTO.getStatus());
+        List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), status);
         if (CollUtil.isNotEmpty(recordDOs)) {
             recordDOs.forEach(item -> {
                 item.setUserCount(recordDOs.size());
@@ -115,8 +115,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         // 2. 创建拼团记录
         CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO);
         record.setVirtualGroup(false);
-        // TODO @puhui999:过期时间,应该是 Date 哈;
-        record.setExpireTime(activity.getLimitDuration());
+        record.setExpireTime(record.getStartTime().plusHours(activity.getLimitDuration()));
         record.setUserSize(activity.getUserSize());
         recordMapper.insert(record);
     }

+ 14 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java

@@ -33,6 +33,20 @@ public interface SeckillActivityService {
      */
     void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO);
 
+    /**
+     * 更新秒杀活动
+     *
+     * @param activityDO 秒杀活动
+     */
+    void updateSeckillActivity(SeckillActivityDO activityDO);
+
+    /**
+     * 更新秒杀活动商品
+     *
+     * @param productList 活动商品列表
+     */
+    void updateSeckillActivityProductList(List<SeckillProductDO> productList);
+
     /**
      * 关闭秒杀活动
      *

+ 17 - 13
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java

@@ -79,8 +79,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
      * 1. 校验秒杀时段是否存在
      * 2. 秒杀商品是否参加其它活动
      *
-     * @param configIds 秒杀时段数组
-     * @param spuId 商品 SPU 编号
+     * @param configIds  秒杀时段数组
+     * @param spuId      商品 SPU 编号
      * @param activityId 秒杀活动编号
      */
     private void validateProductConflict(List<Long> configIds, Long spuId, Long activityId) {
@@ -92,15 +92,9 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
         if (activityId != null) { // 排除自己
             activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
         }
-        // TODO @puhui999:一个 spu,参与两个活动应该没关系,关键是活动时间不充能重叠;
-        // 2.2 过滤出所有 spuId 有交集的活动,判断是否存在重叠
-        List<SeckillActivityDO> activityDOs1 = filterList(activityList, s -> ObjectUtil.equal(s.getSpuId(), spuId));
-        if (isNotEmpty(activityDOs1)) {
-            throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
-        }
-        // 2.3 过滤出所有 configIds 有交集的活动,判断是否存在重叠
-        List<SeckillActivityDO> activityDOs2 = filterList(activityList, s -> containsAny(s.getConfigIds(), configIds));
-        if (isNotEmpty(activityDOs2)) {
+        // 2.2 过滤出所有 configIds 有交集的活动,判断是否存在重叠
+        List<SeckillActivityDO> conflictActivityList = filterList(activityList, s -> containsAny(s.getConfigIds(), configIds));
+        if (isNotEmpty(conflictActivityList)) {
             throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
         }
     }
@@ -108,7 +102,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
     /**
      * 校验秒杀商品是否都存在
      *
-     * @param spuId 商品 SPU 编号
+     * @param spuId    商品 SPU 编号
      * @param products 秒杀商品
      */
     private void validateProductExists(Long spuId, List<SeckillProductBaseVO> products) {
@@ -150,11 +144,21 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
         updateSeckillProduct(updateObj, updateReqVO.getProducts());
     }
 
+    @Override
+    public void updateSeckillActivity(SeckillActivityDO activityDO) {
+        seckillActivityMapper.updateById(activityDO);
+    }
+
+    @Override
+    public void updateSeckillActivityProductList(List<SeckillProductDO> productList) {
+        seckillProductMapper.updateBatch(productList);
+    }
+
     /**
      * 更新秒杀商品
      *
      * @param activity 秒杀活动
-     * @param products  该活动的最新商品配置
+     * @param products 该活动的最新商品配置
      */
     private void updateSeckillProduct(SeckillActivityDO activity, List<SeckillProductBaseVO> products) {
         // 第一步,对比新老数据,获得添加、修改、删除的列表

+ 0 - 26
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/util/PromotionUtils.java

@@ -1,18 +1,9 @@
 package cn.iocoder.yudao.module.promotion.util;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
-import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 
 import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
-import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
 
 /**
  * 活动工具类
@@ -31,21 +22,4 @@ public class PromotionUtils {
         return LocalDateTimeUtils.beforeNow(endTime) ? CommonStatusEnum.DISABLE.getStatus() : CommonStatusEnum.ENABLE.getStatus();
     }
 
-    /**
-     * 校验商品 sku 是否都存在
-     *
-     * @param skus     数据库中的商品 skus
-     * @param products 需要校验的商品
-     * @param func     获取需要校验的商品的 skuId
-     */
-    public static <T> void validateProductSkuAllExists(List<ProductSkuRespDTO> skus, List<T> products, Function<T, Long> func) {
-        // 校验 sku 个数是否一致
-        Set<Long> skuIdsSet = CollectionUtils.convertSet(products, func);
-        Set<Long> skuIdsSet1 = CollectionUtils.convertSet(skus, ProductSkuRespDTO::getId);
-        // 校验 skuId 是否存在
-        if (anyMatch(skuIdsSet, s -> !skuIdsSet1.contains(s))) {
-            throw exception(SKU_NOT_EXISTS);
-        }
-    }
-
 }

+ 29 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApi.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.trade.api.brokerage;
+
+import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
+
+/**
+ * 分销 API 接口
+ *
+ * @author owen
+ */
+public interface BrokerageApi {
+
+    /**
+     * 获得分销用户
+     *
+     * @param userId 用户编号
+     * @return 分销用户信息
+     */
+    BrokerageUserDTO getBrokerageUser(Long userId);
+
+    /**
+     * 绑定推广员
+     *
+     * @param userId     用户编号
+     * @param bindUserId 推广员编号
+     * @param isNewUser  是否为新用户
+     * @return 是否绑定
+     */
+    boolean bindUser(Long userId, Long bindUserId, Boolean isNewUser);
+}

+ 51 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/dto/BrokerageUserDTO.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.trade.api.brokerage.dto;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 分销用户 DTO
+ *
+ * @author owen
+ */
+@Data
+public class BrokerageUserDTO {
+
+    /**
+     * 用户编号
+     * <p>
+     * 对应 MemberUserDO 的 id 字段
+     */
+    private Long id;
+
+    /**
+     * 推广员编号
+     * <p>
+     * 关联 MemberUserDO 的 id 字段
+     */
+    private Long bindUserId;
+    /**
+     * 推广员绑定时间
+     */
+    private LocalDateTime bindUserTime;
+
+    /**
+     * 推广资格
+     */
+    private Boolean brokerageEnabled;
+    /**
+     * 成为分销员时间
+     */
+    private LocalDateTime brokerageTime;
+
+    /**
+     * 可用佣金
+     */
+    private Integer price;
+    /**
+     * 冻结佣金
+     */
+    private Integer frozenPrice;
+
+}

+ 15 - 5
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java

@@ -11,7 +11,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface ErrorCodeConstants {
 
-    // ==========  Order 模块 1-011-000-000 ==========
+    // ========== Order 模块 1-011-000-000 ==========
     ErrorCode ORDER_CREATE_SKU_NOT_FOUND = new ErrorCode(1_011_000_001, "商品 SKU 不存在");
     ErrorCode ORDER_CREATE_SPU_NOT_SALE = new ErrorCode(1_011_000_002, "商品 SPU 不可售卖");
     ErrorCode ORDER_CREATE_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1_011_000_004, "商品 SKU 库存不足");
@@ -35,7 +35,7 @@ public interface ErrorCodeConstants {
     ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1_011_000_024, "交易订单发货失败,发货类型不是快递");
     ErrorCode ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID = new ErrorCode(1_011_000_025, "交易订单取消失败,订单不是【待支付】状态");
 
-    // ==========  After Sale 模块 1-011-000-100 ==========
+    // ========== After Sale 模块 1-011-000-100 ==========
     ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
     ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1_011_000_101, "申请退款金额错误");
     ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1_011_000_102, "订单已关闭,无法申请售后");
@@ -50,7 +50,7 @@ public interface ErrorCodeConstants {
     ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
             new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
 
-    // ==========  Cart 模块 1-011-002-000 ==========
+    // ========== Cart 模块 1-011-002-000 ==========
     ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
 
     // ========== Price 相关 1-011-003-000 ============
@@ -58,7 +58,7 @@ public interface ErrorCodeConstants {
     ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY = new ErrorCode(1_011_003_001, "计算快递运费异常,收件人地址编号为空");
     ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板");
 
-    // ==========  物流 Express 模块 1-011-004-000 ==========
+    // ========== 物流 Express 模块 1-011-004-000 ==========
     ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
     ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1_011_004_001, "已经存在该编码的快递公司");
     ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1_011_004_002, "需要接入快递服务商,比如【快递100】");
@@ -67,11 +67,21 @@ public interface ErrorCodeConstants {
     ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1_011_004_101, "快递查询接口异常");
     ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1_011_004_102, "快递查询返回失败,原因:{}");
 
-    // ==========  物流 Template 模块 1-011-005-000 ==========
+    // ========== 物流 Template 模块 1-011-005-000 ==========
     ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_011_005_000, "已经存在该运费模板名");
     ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_011_005_001, "运费模板不存在");
 
     // ==========  物流 PICK_UP 模块 1-011-006-000 ==========
     ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店不存在");
 
+    // ========== 分销用户 模块 1011007000 ==========
+    ErrorCode BROKERAGE_USER_NOT_EXISTS = new ErrorCode(1011007000, "分销用户不存在");
+    ErrorCode BROKERAGE_USER_FROZEN_PRICE_NOT_ENOUGH = new ErrorCode(1011007001, "用户冻结佣金({})数量不足");
+    ErrorCode BROKERAGE_BIND_SELF = new ErrorCode(1011007002, "不能绑定自己");
+    ErrorCode BROKERAGE_BIND_USER_NOT_ENABLED = new ErrorCode(1011007003, "绑定用户没有推广资格");
+    ErrorCode BROKERAGE_BIND_CONDITION_ADMIN = new ErrorCode(1011007004, "仅可在后台绑定推广员");
+    ErrorCode BROKERAGE_BIND_MODE_REGISTER = new ErrorCode(1011007005, "只有在注册时可以绑定");
+    ErrorCode BROKERAGE_BIND_OVERRIDE = new ErrorCode(1011007006, "已绑定了推广人");
+    ErrorCode BROKERAGE_BIND_LOOP = new ErrorCode(1011007007, "下级不能绑定自己的上级");
+
 }

+ 48 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageBindModeEnum.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.trade.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 分销关系绑定模式枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageBindModeEnum implements IntArrayValuable {
+
+    /**
+     * 只要用户没有推广人,随时都可以绑定分销关系
+     */
+    ANYTIME(1, "没有推广人"),
+    /**
+     * 仅新用户注册时才能绑定推广关系
+     */
+    REGISTER(2, "新用户"),
+    /**
+     * 每次扫码都覆盖
+     */
+    OVERRIDE(3, "扫码覆盖"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageBindModeEnum::getMode).toArray();
+
+    /**
+     * 模式
+     */
+    private final Integer mode;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 44 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageEnabledConditionEnum.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.trade.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 分佣模式枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageEnabledConditionEnum implements IntArrayValuable {
+
+    /**
+     * 所有用户都可以分销
+     */
+    ALL(1, "人人分销"),
+    /**
+     * 仅可后台手动设置推广员
+     */
+    ADMIN(2, "指定分销"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageEnabledConditionEnum::getCondition).toArray();
+
+    /**
+     * 模式
+     */
+    private final Integer condition;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 46 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordBizTypeEnum.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.trade.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 佣金记录业务类型枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageRecordBizTypeEnum implements IntArrayValuable {
+
+    ORDER(1, "获得推广佣金", "获得推广佣金 {}", true),
+    WITHDRAW(2, "提现申请", "提现申请扣除佣金 {}", false),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordBizTypeEnum::getType).toArray();
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+    /**
+     * 标题
+     */
+    private final String title;
+    /**
+     * 描述
+     */
+    private final String description;
+    /**
+     * 是否为增加佣金
+     */
+    private final boolean add;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 39 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordStatusEnum.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.trade.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 佣金记录状态枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageRecordStatusEnum implements IntArrayValuable {
+
+    WAIT_SETTLEMENT(0, "待结算"),
+    SETTLEMENT(1, "已结算"),
+    CANCEL(2, "已取消"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordStatusEnum::getStatus).toArray();
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 41 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawStatusEnum.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.trade.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 佣金提现状态枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageWithdrawStatusEnum implements IntArrayValuable {
+
+    AUDITING(0, "审核中"),
+    AUDIT_SUCCESS(10, "审核通过"),
+    WITHDRAW_SUCCESS(11, "提现成功"),
+    AUDIT_FAIL(20, "审核不通过"),
+    WITHDRAW_FAIL(21, "提现失败"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawStatusEnum::getStatus).toArray();
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 40 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.trade.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 佣金提现类型枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
+
+    WALLET(1, "钱包"),
+    BANK(2, "银行卡"),
+    WECHAT(3, "微信"),
+    ALIPAY(4, "支付宝"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawTypeEnum::getType).toArray();
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 9 - 0
yudao-module-mall/yudao-module-trade-biz/pom.xml

@@ -54,6 +54,10 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
+        </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
@@ -82,6 +86,11 @@
             <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-redis</artifactId>
+        </dependency>
+
         <!-- Test 测试相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 33 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApiImpl.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.trade.api.brokerage;
+
+import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
+import cn.iocoder.yudao.module.trade.convert.brokerage.user.BrokerageUserConvert;
+import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 分销 API 接口实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class BrokerageApiImpl implements BrokerageApi {
+
+    @Resource
+    private BrokerageUserService brokerageUserService;
+
+    @Override
+    public BrokerageUserDTO getBrokerageUser(Long userId) {
+        return BrokerageUserConvert.INSTANCE.convertDTO(brokerageUserService.getBrokerageUser(userId));
+    }
+
+    @Override
+    public boolean bindUser(Long userId, Long bindUserId, Boolean isNewUser) {
+        return brokerageUserService.bindBrokerageUser(userId, bindUserId, isNewUser);
+    }
+
+}

+ 10 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/TradeAfterSaleController.java

@@ -76,6 +76,11 @@ public class TradeAfterSaleController {
     public CommonResult<TradeAfterSaleDetailRespVO> getOrderDetail(@RequestParam("id") Long id) {
         // 查询订单
         TradeAfterSaleDO afterSale = afterSaleService.getAfterSale(id);
+        // TODO @puhui999:这里建议改成,如果为 null,直接返回 success null;主要查询操作,尽量不要有非空的提示哈;交给前端处理;
+//        if (afterSale == null) {
+//            return success(null, AFTER_SALE_NOT_FOUND.getMsg());
+//        }
+
         // 查询订单
         TradeOrderDO order = tradeOrderQueryService.getOrder(afterSale.getOrderId());
         // 查询订单项
@@ -92,7 +97,11 @@ public class TradeAfterSaleController {
             TradeAfterSaleLogRespDTO respVO = new TradeAfterSaleLogRespDTO();
             respVO.setId((long) i);
             respVO.setUserId((long) i);
-            respVO.setUserType(1);
+            respVO.setUserType(i % 2 == 0 ? 2 : 1);
+            // 模拟系统操作
+            if (i == 2) {
+                respVO.setUserType(3);
+            }
             respVO.setAfterSaleId(id);
             respVO.setOrderId((long) i);
             respVO.setOrderItemId((long) i);

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/TradeAfterSaleDetailRespVO.java

@@ -35,7 +35,7 @@ public class TradeAfterSaleDetailRespVO extends TradeAfterSaleBaseVO {
     /**
      * 售后日志
      */
-    private List<TradeAfterSaleLogRespVO> afterSaleLog;
+    private List<TradeAfterSaleLogRespVO> logs;
 
     @Schema(description = "管理后台 - 交易订单的详情的订单项目")
     @Data

+ 51 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/BrokerageRecordController.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordRespVO;
+import cn.iocoder.yudao.module.trade.convert.brokerage.record.BrokerageRecordConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 佣金记录")
+@RestController
+@RequestMapping("/trade/brokerage-record")
+@Validated
+public class BrokerageRecordController {
+
+    @Resource
+    private BrokerageRecordService brokerageRecordService;
+
+    @GetMapping("/get")
+    @Operation(summary = "获得佣金记录")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
+    public CommonResult<BrokerageRecordRespVO> getBrokerageRecord(@RequestParam("id") Integer id) {
+        BrokerageRecordDO brokerageRecord = brokerageRecordService.getBrokerageRecord(id);
+        return success(BrokerageRecordConvert.INSTANCE.convert(brokerageRecord));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得佣金记录分页")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
+    public CommonResult<PageResult<BrokerageRecordRespVO>> getBrokerageRecordPage(@Valid BrokerageRecordPageReqVO pageVO) {
+        PageResult<BrokerageRecordDO> pageResult = brokerageRecordService.getBrokerageRecordPage(pageVO);
+        return success(BrokerageRecordConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 60 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordBaseVO.java

@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 佣金记录 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class BrokerageRecordBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25973")
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23353")
+    @NotEmpty(message = "业务编号不能为空")
+    private String bizId;
+
+    @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "业务类型不能为空")
+    private Integer bizType;
+
+    @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "标题不能为空")
+    private String title;
+
+    @Schema(description = "金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "28731")
+    @NotNull(message = "金额不能为空")
+    private Integer price;
+
+    @Schema(description = "当前总佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "13226")
+    @NotNull(message = "当前总佣金不能为空")
+    private Integer totalPrice;
+
+    @Schema(description = "说明", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
+    @NotNull(message = "说明不能为空")
+    private String description;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @Schema(description = "冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "冻结时间(天)不能为空")
+    private Integer frozenDays;
+
+    @Schema(description = "解冻时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime unfreezeTime;
+
+}

+ 33 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordPageReqVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 佣金记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BrokerageRecordPageReqVO extends PageParam {
+
+    @Schema(description = "用户编号", example = "25973")
+    private Long userId;
+
+    @Schema(description = "业务类型", example = "1")
+    private Integer bizType;
+
+    @Schema(description = "状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 佣金记录 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BrokerageRecordRespVO extends BrokerageRecordBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28896")
+    private Integer id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 104 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/BrokerageUserController.java

@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.*;
+import cn.iocoder.yudao.module.trade.convert.brokerage.user.BrokerageUserConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
+import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - 分销用户")
+@RestController
+@RequestMapping("/trade/brokerage-user")
+@Validated
+public class BrokerageUserController {
+
+    @Resource
+    private BrokerageUserService brokerageUserService;
+    @Resource
+    private BrokerageRecordService brokerageRecordService;
+
+    @Resource
+    private MemberUserApi memberUserApi;
+
+    @PutMapping("/update-brokerage-user")
+    @Operation(summary = "修改推广员")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:update-brokerage-user')")
+    public CommonResult<Boolean> updateBrokerageUser(@Valid @RequestBody BrokerageUserUpdateBrokerageUserReqVO updateReqVO) {
+        brokerageUserService.updateBrokerageUserId(updateReqVO.getId(), updateReqVO.getBindUserId());
+        return success(true);
+    }
+
+    @PutMapping("/clear-brokerage-user")
+    @Operation(summary = "清除推广员")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:clear-brokerage-user')")
+    public CommonResult<Boolean> clearBrokerageUser(@Valid @RequestBody BrokerageUserClearBrokerageUserReqVO updateReqVO) {
+        brokerageUserService.updateBrokerageUserId(updateReqVO.getId(), null);
+        return success(true);
+    }
+
+    @PutMapping("/update-brokerage-enable")
+    @Operation(summary = "修改推广资格")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:update-brokerage-enable')")
+    public CommonResult<Boolean> updateBrokerageEnabled(@Valid @RequestBody BrokerageUserUpdateBrokerageEnabledReqVO updateReqVO) {
+        brokerageUserService.updateBrokerageUserEnabled(updateReqVO.getId(), updateReqVO.getEnabled());
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得分销用户")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
+    public CommonResult<BrokerageUserRespVO> getBrokerageUser(@RequestParam("id") Long id) {
+        BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(id);
+        return success(BrokerageUserConvert.INSTANCE.convert(brokerageUser));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得分销用户分页")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
+    public CommonResult<PageResult<BrokerageUserRespVO>> getBrokerageUserPage(@Valid BrokerageUserPageReqVO pageVO) {
+        // 分页查询
+        PageResult<BrokerageUserDO> pageResult = brokerageUserService.getBrokerageUserPage(pageVO);
+
+        // 涉及到的用户
+        Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
+        // 查询用户信息
+        Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
+        // 合计分佣订单
+        Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = convertMap(userIds,
+                userId -> userId,
+                userId -> brokerageRecordService.getUserBrokerageSummaryByUserId(userId,
+                        BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus()));
+        // 合计推广用户数量
+        Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
+                userId -> userId,
+                userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId));
+
+        // todo 合计提现
+
+        return success(BrokerageUserConvert.INSTANCE.convertPage(pageResult, userMap, brokerageUserCountMap, userOrderSummaryMap));
+    }
+
+}

+ 43 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserBaseVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 分销用户 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class BrokerageUserBaseVO {
+
+    @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
+    @NotNull(message = "推广员编号不能为空")
+    private Long bindUserId;
+
+    @Schema(description = "推广员绑定时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime bindUserTime;
+
+    @Schema(description = "推广资格", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "推广资格不能为空")
+    private Boolean brokerageEnabled;
+
+    @Schema(description = "成为分销员时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime brokerageTime;
+
+    @Schema(description = "可用佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "11089")
+    @NotNull(message = "可用佣金不能为空")
+    private Integer price;
+
+    @Schema(description = "冻结佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "30916")
+    @NotNull(message = "冻结佣金不能为空")
+    private Integer frozenPrice;
+
+}

+ 18 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserClearBrokerageUserReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 分销用户 - 清除推广员 Request VO")
+@Data
+@ToString(callSuper = true)
+public class BrokerageUserClearBrokerageUserReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
+    @NotNull(message = "用户编号不能为空")
+    private Long id;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 分销用户分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BrokerageUserPageReqVO extends PageParam {
+
+    @Schema(description = "推广员编号", example = "4587")
+    private Long bindUserId;
+
+    @Schema(description = "推广资格")
+    private Boolean brokerageEnabled;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 45 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserRespVO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 分销用户 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BrokerageUserRespVO extends BrokerageUserBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    // ========== 用户信息 ==========
+
+    @Schema(description = "用户头像", example = "https://www.iocoder.cn/xxx.png")
+    private String avatar;
+    @Schema(description = "用户昵称", example = "李四")
+    private String nickname;
+
+    // ========== 推广信息 ==========
+
+    @Schema(description = "推广用户数量(一级)", example = "20019")
+    private Integer brokerageUserCount;
+    @Schema(description = "推广订单数量", example = "20019")
+    private Integer brokerageOrderCount;
+    @Schema(description = "推广订单金额", example = "20019")
+    private Integer brokerageOrderPrice;
+
+    // ========== 提现信息 ==========
+
+    @Schema(description = "已提现金额", example = "20019")
+    private Integer withdrawPrice;
+    @Schema(description = "已提现次数", example = "20019")
+    private Integer withdrawCount;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserUpdateBrokerageEnabledReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
+@Data
+@ToString(callSuper = true)
+public class BrokerageUserUpdateBrokerageEnabledReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
+    @NotNull(message = "用户编号不能为空")
+    private Long id;
+
+    @Schema(description = "推广资格", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "推广资格不能为空")
+    private Boolean enabled;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserUpdateBrokerageUserReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
+@Data
+@ToString(callSuper = true)
+public class BrokerageUserUpdateBrokerageUserReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
+    @NotNull(message = "用户编号不能为空")
+    private Long id;
+
+    @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
+    @NotNull(message = "推广员编号不能为空")
+    private Long bindUserId;
+
+}

+ 45 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/TradeConfigController.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.trade.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigRespVO;
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
+import cn.iocoder.yudao.module.trade.convert.config.TradeConfigConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 交易中心配置")
+@RestController
+@RequestMapping("/trade/config")
+@Validated
+public class TradeConfigController {
+
+    @Resource
+    private TradeConfigService tradeConfigService;
+
+    @PutMapping("/save")
+    @Operation(summary = "更新交易中心配置")
+    @PreAuthorize("@ss.hasPermission('trade:config:save')")
+    public CommonResult<Boolean> updateConfig(@Valid @RequestBody TradeConfigSaveReqVO updateReqVO) {
+        tradeConfigService.saveTradeConfig(updateReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得交易中心配置")
+    @PreAuthorize("@ss.hasPermission('trade:config:query')")
+    public CommonResult<TradeConfigRespVO> getConfig() {
+        TradeConfigDO config = tradeConfigService.getTradeConfig();
+        return success(TradeConfigConvert.INSTANCE.convert(config));
+    }
+
+}

+ 71 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigBaseVO.java

@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.PositiveOrZero;
+import java.util.List;
+
+/**
+ * 交易中心配置 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class TradeConfigBaseVO {
+
+    // ========== 分销相关 ==========
+
+    @Schema(description = "是否启用分佣", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    @NotNull(message = "是否启用分佣不能为空")
+    private Boolean brokerageEnabled;
+
+    @Schema(description = "分佣模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "分佣模式不能为空")
+    @InEnum(value = BrokerageEnabledConditionEnum.class, message = "分佣模式必须是 {value}")
+    private Integer brokerageEnabledCondition;
+
+    @Schema(description = "分销关系绑定模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "分销关系绑定模式不能为空")
+    @InEnum(value = BrokerageBindModeEnum.class, message = "分销关系绑定模式必须是 {value}")
+    private Integer brokerageBindMode;
+
+    @Schema(description = "分销海报图地址数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/yudao.jpg]")
+    private List<String> brokeragePostUrls;
+
+    @Schema(description = "一级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "一级返佣比例不能为空")
+    @Range(min = 0, max = 100, message = "一级返佣比例必须在 0 - 100 之间")
+    private Integer brokerageFirstPercent;
+
+    @Schema(description = "二级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "二级返佣比例不能为空")
+    @Range(min = 0, max = 100, message = "二级返佣比例必须在 0 - 100 之间")
+    private Integer brokerageSecondPercent;
+
+    @Schema(description = "用户提现最低金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    @NotNull(message = "用户提现最低金额不能为空")
+    @PositiveOrZero(message = "用户提现最低金额不能是负数")
+    private Integer brokerageWithdrawMinPrice;
+
+    @Schema(description = "提现银行", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
+    @NotEmpty(message = "提现银行不能为空")
+    private List<Integer> brokerageBankNames;
+
+    @Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
+    @NotNull(message = "佣金冻结时间(天)不能为空")
+    @PositiveOrZero(message = "佣金冻结时间不能是负数")
+    private Integer brokerageFrozenDays;
+
+    @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
+    @NotNull(message = "提现方式不能为空")
+    @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
+    private List<Integer> brokerageWithdrawType;
+
+}

+ 17 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigRespVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 交易中心配置 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TradeConfigRespVO extends TradeConfigBaseVO {
+
+    @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigSaveReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 交易中心配置更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TradeConfigSaveReqVO extends TradeConfigBaseVO {
+
+}

+ 5 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java

@@ -67,6 +67,11 @@ public class TradeOrderController {
     public CommonResult<TradeOrderDetailRespVO> getOrderDetail(@RequestParam("id") Long id) {
         // 查询订单
         TradeOrderDO order = tradeOrderQueryService.getOrder(id);
+        // TODO @puhui999:这里建议改成,如果为 null,直接返回 success null;主要查询操作,尽量不要有非空的提示哈;交给前端处理;
+//        if (order == null) {
+//            return success(null, ORDER_NOT_FOUND.getMsg());
+//        }
+
         // 查询订单项
         List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id);
         // orderLog

+ 8 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderDetailRespVO.java

@@ -26,24 +26,23 @@ public class TradeOrderDetailRespVO extends TradeOrderBaseVO {
     private MemberUserRespVO user;
 
     /**
-     * TODO 订单操作日志, 先模拟一波;返回 logs,简洁,然后复数哈
+     * TODO 订单操作日志, 先模拟一波
      */
-    private List<OrderLog> orderLog;
+    private List<OrderLog> logs;
 
-    // TODO @puhui999:swagger 注解
+    @Schema(description = "管理后台 - 交易订单的操作日志")
     @Data
     public static class OrderLog {
 
-        /**
-         * 内容
-         */
+        @Schema(description = "操作详情", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单发货")
         private String content;
 
-        /**
-         * 创建时间
-         */
+        @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-01 10:50:20")
         private LocalDateTime createTime;
 
+        @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Integer userType;
+
     }
 
     @Schema(description = "管理后台 - 交易订单的详情的订单项目")

+ 55 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageRecordController.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageProductPriceRespVO;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordRespVO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static java.util.Arrays.asList;
+
+@Tag(name = "用户 APP - 分销用户")
+@RestController
+@RequestMapping("/trade/brokerage-record")
+@Validated
+@Slf4j
+public class AppBrokerageRecordController {
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/page")
+    @Operation(summary = "获得分销记录分页")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppBrokerageRecordRespVO>> getBrokerageRecordPage(@Valid AppBrokerageRecordPageReqVO pageReqVO) {
+        AppBrokerageRecordRespVO vo1 = new AppBrokerageRecordRespVO()
+                .setId(1L).setPrice(10).setTitle("收到钱").setCreateTime(LocalDateTime.now())
+                .setFinishTime(LocalDateTime.now());
+        AppBrokerageRecordRespVO vo2 = new AppBrokerageRecordRespVO()
+                .setId(2L).setPrice(-20).setTitle("提现钱").setCreateTime(LocalDateTime.now())
+                .setFinishTime(LocalDateTime.now());
+        return success(new PageResult<>(asList(vo1, vo2), 10L));
+    }
+
+    @GetMapping("/get-product-brokerage-price")
+    @Operation(summary = "获得商品的分销金额")
+    public CommonResult<AppBrokerageProductPriceRespVO> getProductBrokeragePrice(@RequestParam("spuId") Long spuId) {
+        AppBrokerageProductPriceRespVO respVO = new AppBrokerageProductPriceRespVO();
+        respVO.setEnabled(true); // TODO @疯狂:需要开启分销 + 人允许分销
+        respVO.setBrokerageMinPrice(1);
+        respVO.setBrokerageMaxPrice(2);
+        return success(respVO);
+    }
+
+}

+ 134 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java

@@ -0,0 +1,134 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.*;
+import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static java.util.Arrays.asList;
+
+@Tag(name = "用户 APP - 分销用户")
+@RestController
+@RequestMapping("/trade/brokerage-user")
+@Validated
+@Slf4j
+public class AppBrokerageUserController {
+    @Resource
+    private BrokerageUserService brokerageUserService;
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/get")
+    @Operation(summary = "获得个人分销信息")
+    @PreAuthenticated
+    public CommonResult<AppBrokerageUserRespVO> getBrokerageUser() {
+        AppBrokerageUserRespVO respVO = new AppBrokerageUserRespVO()
+                .setBrokerageEnabled(true)
+                .setPrice(2000)
+                .setFrozenPrice(3000);
+        return success(respVO);
+    }
+
+    @PutMapping("/bind")
+    @Operation(summary = "绑定推广员")
+    @PreAuthenticated
+    public CommonResult<Boolean> bindBrokerageUser(@Valid @RequestBody AppBrokerageUserBindReqVO reqVO) {
+        return success(brokerageUserService.bindBrokerageUser(getLoginUserId(), reqVO.getBindUserId(), false));
+    }
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/get-summary")
+    @Operation(summary = "获得个人分销统计")
+    @PreAuthenticated
+    public CommonResult<AppBrokerageUserMySummaryRespVO> getBrokerageUserSummary() {
+        AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
+                .setYesterdayPrice(1)
+                .setBrokeragePrice(2)
+                .setFrozenPrice(3)
+                .setWithdrawPrice(4)
+                .setFirstBrokerageUserCount(166)
+                .setSecondBrokerageUserCount(233);
+        return success(respVO);
+    }
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/rank-page-by-user-count")
+    @Operation(summary = "获得分销用户排行分页(基于用户量)")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppBrokerageUserRankByUserCountRespVO>> getBrokerageUserRankPageByUserCount(AppBrokerageUserRankPageReqVO pageReqVO) {
+        AppBrokerageUserRankByUserCountRespVO vo1 = new AppBrokerageUserRankByUserCountRespVO()
+                .setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokerageUserCount(10);
+        AppBrokerageUserRankByUserCountRespVO vo2 = new AppBrokerageUserRankByUserCountRespVO()
+                .setId(2L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokerageUserCount(6);
+        AppBrokerageUserRankByUserCountRespVO vo3 = new AppBrokerageUserRankByUserCountRespVO()
+                .setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokerageUserCount(4);
+        AppBrokerageUserRankByUserCountRespVO vo4 = new AppBrokerageUserRankByUserCountRespVO()
+                .setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokerageUserCount(4);
+        return success(new PageResult<>(asList(vo1, vo2, vo3, vo4), 10L));
+    }
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/rank-page-by-price")
+    @Operation(summary = "获得分销用户排行分页(基于佣金)")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppBrokerageUserRankByPriceRespVO>> getBrokerageUserChildSummaryPageByPrice(AppBrokerageUserRankPageReqVO pageReqVO) {
+        AppBrokerageUserRankByPriceRespVO vo1 = new AppBrokerageUserRankByPriceRespVO()
+                .setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokeragePrice(10);
+        AppBrokerageUserRankByPriceRespVO vo2 = new AppBrokerageUserRankByPriceRespVO()
+                .setId(2L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokeragePrice(6);
+        AppBrokerageUserRankByPriceRespVO vo3 = new AppBrokerageUserRankByPriceRespVO()
+                .setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokeragePrice(4);
+        AppBrokerageUserRankByPriceRespVO vo4 = new AppBrokerageUserRankByPriceRespVO()
+                .setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokeragePrice(4);
+        return success(new PageResult<>(asList(vo1, vo2, vo3, vo4), 10L));
+    }
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/child-summary-page")
+    @Operation(summary = "获得下级分销统计分页")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppBrokerageUserChildSummaryRespVO>> getBrokerageUserChildSummaryPage(
+            AppBrokerageUserChildSummaryPageReqVO pageReqVO) {
+        AppBrokerageUserChildSummaryRespVO vo1 = new AppBrokerageUserChildSummaryRespVO()
+                .setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokeragePrice(10).setBrokeragePrice(20).setBrokerageOrderCount(30)
+                .setBrokerageTime(LocalDateTime.now());
+        AppBrokerageUserChildSummaryRespVO vo2 = new AppBrokerageUserChildSummaryRespVO()
+                .setId(1L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokeragePrice(20).setBrokeragePrice(30).setBrokerageOrderCount(40)
+                .setBrokerageTime(LocalDateTime.now());
+        return success(new PageResult<>(asList(vo1, vo2), 10L));
+    }
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/get-rank-by-price")
+    @Operation(summary = "获得分销用户排行(基于佣金)")
+    @Parameter(name = "times", description = "时间段", required = true)
+    public CommonResult<Integer> bindBrokerageUser(
+            @RequestParam("times") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime[] times) {
+        return success(1);
+    }
+
+}

+ 47 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawRespVO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static java.util.Arrays.asList;
+
+@Tag(name = "用户 APP - 分销提现")
+@RestController
+@RequestMapping("/trade/brokerage-withdraw")
+@Validated
+@Slf4j
+public class AppBrokerageWithdrawController {
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/page")
+    @Operation(summary = "获得分销提现分页")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppBrokerageWithdrawRespVO>> getBrokerageWithdrawPage() {
+        AppBrokerageWithdrawRespVO vo1 = new AppBrokerageWithdrawRespVO()
+                .setId(1L).setStatus(10).setPrice(10).setStatusName("审批通过").setCreateTime(LocalDateTime.now());
+        AppBrokerageWithdrawRespVO vo2 = new AppBrokerageWithdrawRespVO()
+                .setId(2L).setStatus(0).setPrice(20).setStatusName("审批中").setCreateTime(LocalDateTime.now());
+        return success(new PageResult<>(asList(vo1, vo2), 10L));
+    }
+
+    // TODO 芋艿:临时 mock =>
+    @PostMapping("/create")
+    @Operation(summary = "创建分销提现")
+    @PreAuthenticated
+    public CommonResult<Long> createBrokerageWithdraw(@RequestBody @Valid AppBrokerageWithdrawCreateReqVO createReqVO) {
+        return success(1L);
+    }
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageProductPriceRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 商品的分销金额 Response VO")
+@Data
+public class AppBrokerageProductPriceRespVO {
+
+    @Schema(description = "是否开启", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Boolean enabled;
+
+    @Schema(description = "分销最小金额,单位:分", example = "100")
+    private Integer brokerageMinPrice;
+
+    @Schema(description = "分销最大金额,单位:分", example = "100")
+    private Integer brokerageMaxPrice;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordPageReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "应用 App - 分销记录分页 Request VO")
+@Data
+public class AppBrokerageRecordPageReqVO extends PageParam {
+
+    @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @InEnum(value = BrokerageRecordBizTypeEnum.class, message = "业务类型必须是 {value}")
+    private Integer bizType;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @InEnum(value = BrokerageRecordStatusEnum.class, message = "状态必须是 {value}")
+    private Integer status;
+
+}

+ 27 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordRespVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 分销记录 Response VO")
+@Data
+public class AppBrokerageRecordRespVO {
+
+    @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long id;
+
+    @Schema(description = "分销标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户下单")
+    private String title;
+
+    @Schema(description = "分销金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    private Integer price;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime finishTime;
+
+}

+ 17 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserBindReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "应用 App - 绑定推广员 Request VO")
+@Data
+public class AppBrokerageUserBindReqVO extends PageParam {
+
+    @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "推广员编号不能为空")
+    private Long bindUserId;
+
+}

+ 25 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserChildSummaryPageReqVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.SortingField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 下级分销统计分页 Request VO")
+@Data
+public class AppBrokerageUserChildSummaryPageReqVO extends PageParam {
+
+    public static final String SORT_FIELD_USER_COUNT = "userCount";
+    public static final String SORT_FIELD_ORDER_COUNT = "orderCount";
+    public static final String SORT_FIELD_PRICE = "price";
+
+    @Schema(description = "用户昵称", example = "李") // 模糊匹配
+    private String nickname;
+
+    @Schema(description = "排序字段", example = "userCount")
+    private SortingField sortingField;
+
+    @Schema(description = "下级的级别", example = "1") // 1 - 直接下级;2 - 间接下级
+    private Integer level;
+
+}

+ 33 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserChildSummaryRespVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 下级分销统计 Response VO")
+@Data
+public class AppBrokerageUserChildSummaryRespVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long id;
+
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
+    private String nickname;
+
+    @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
+    private String avatar;
+
+    @Schema(description = "佣金金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer brokeragePrice;
+
+    @Schema(description = "分销订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    private Integer brokerageOrderCount;
+
+    @Schema(description = "分销用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
+    private Integer brokerageUserCount;
+
+    @Schema(description = "成为分销员时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime brokerageTime;
+
+}

+ 28 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserMySummaryRespVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 个人分销统计 Response VO")
+@Data
+public class AppBrokerageUserMySummaryRespVO {
+
+    @Schema(description = "昨天的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer yesterdayPrice;
+
+    @Schema(description = "提现的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer withdrawPrice;
+
+    @Schema(description = "可用的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2408")
+    private Integer brokeragePrice;
+
+    @Schema(description = "冻结的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "234")
+    private Integer frozenPrice;
+
+    @Schema(description = "分销用户数量(一级)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer firstBrokerageUserCount;
+
+    @Schema(description = "分销用户数量(二级)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer secondBrokerageUserCount;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankByPriceRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 分销排行用户(基于用户量) Response VO")
+@Data
+public class AppBrokerageUserRankByPriceRespVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long id;
+
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
+    private String nickname;
+
+    @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
+    private String avatar;
+
+    @Schema(description = "佣金金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer brokeragePrice;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankByUserCountRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 分销排行用户(基于用户量) Response VO")
+@Data
+public class AppBrokerageUserRankByUserCountRespVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long id;
+
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
+    private String nickname;
+
+    @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
+    private String avatar;
+
+    @Schema(description = "邀请用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer brokerageUserCount;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankPageReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotEmpty;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "应用 App - 分销用户排行 Request VO")
+@Data
+public class AppBrokerageUserRankPageReqVO extends PageParam {
+
+    @Schema(description = "开始 + 结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @NotEmpty(message = "时间不能为空")
+    private LocalDateTime[] times;
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 分销用户信息 Response VO")
+@Data
+public class AppBrokerageUserRespVO {
+
+    @Schema(description = "是否有分销资格", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean brokerageEnabled;
+
+    @Schema(description = "可用的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2408")
+    private Integer price;
+
+    @Schema(description = "冻结的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "234")
+    private Integer frozenPrice;
+
+}

+ 73 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw;
+
+import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.Validator;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+
+@Schema(description = "用户 App - 分销提现创建 Request VO")
+@Data
+public class AppBrokerageWithdrawCreateReqVO {
+
+    @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
+    private Integer type;
+
+    @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    @Min(value = 1, message = "提现金额不能小于 1")
+    private Integer price;
+
+    // ========== 银行卡、微信、支付宝 提现相关字段 ==========
+
+    @Schema(description = "提现账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456789")
+    @NotBlank(message = "提现账号不能为空", groups = {Bank.class, Wechat.class, Alipay.class})
+    private String accountNo;
+
+    // ========== 微信、支付宝 提现相关字段 ==========
+
+    @Schema(description = "收款码的图片", example = "https://www.iocoder.cn/1.png")
+    @URL(message = "收款码的图片,必须是一个 URL")
+    private String accountQrCodeUrl;
+
+    // ========== 银行卡 提现相关字段 ==========
+
+    @Schema(description = "持卡人姓名", example = "张三")
+    @NotBlank(message = "持卡人姓名不能为空", groups = {Bank.class})
+    private String name;
+    @Schema(description = "提现银行", example = "1")
+    @NotBlank(message = "提现银行不能为空", groups = {Bank.class})
+    private Integer bankName;
+    @Schema(description = "开户地址", example = "海淀支行")
+    private String bankAddress;
+
+    public interface Wallet {
+    }
+
+    public interface Bank {
+    }
+
+    public interface Wechat {
+    }
+
+    public interface Alipay {
+    }
+
+    public void validate(Validator validator) {
+        if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(type)) {
+            ValidationUtils.validate(validator, this, Wallet.class);
+        } else if (BrokerageWithdrawTypeEnum.BANK.getType().equals(type)) {
+            ValidationUtils.validate(validator, this, Bank.class);
+        } else if (BrokerageWithdrawTypeEnum.WECHAT.getType().equals(type)) {
+            ValidationUtils.validate(validator, this, Wechat.class);
+        } else if (BrokerageWithdrawTypeEnum.ALIPAY.getType().equals(type)) {
+            ValidationUtils.validate(validator, this, Alipay.class);
+        }
+    }
+
+}

+ 27 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 分销提现 Response VO")
+@Data
+public class AppBrokerageWithdrawRespVO {
+
+    @Schema(description = "提现编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long id;
+
+    @Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer status;
+
+    @Schema(description = "提现状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "审核中")
+    private String statusName;
+
+    @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    private Integer price;
+
+    @Schema(description = "提现时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 37 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/config/AppTradeConfigController.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.trade.controller.app.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.trade.controller.app.config.vo.AppTradeConfigRespVO;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static java.util.Arrays.asList;
+
+@Tag(name = "用户 App - 交易配置")
+@RestController
+@RequestMapping("/trade/config")
+@RequiredArgsConstructor
+@Validated
+@Slf4j
+public class AppTradeConfigController {
+
+    @GetMapping("/get")
+    public CommonResult<AppTradeConfigRespVO> getTradeConfig() {
+        AppTradeConfigRespVO respVO = new AppTradeConfigRespVO();
+        respVO.setBrokeragePosterUrls(asList(
+                "https://api.java.crmeb.net/crmebimage/product/2020/08/03/755bf516b1ca4b6db3bfeaa4dd5901cdh71kob20re.jpg",
+                "https://api.java.crmeb.net/crmebimage/maintain/2021/03/01/406d729b84ed4ec9a2171bfcf6fd0634ughzbz9kfi.jpg",
+                "https://api.java.crmeb.net/crmebimage/maintain/2021/03/01/efb1e4e7fe604fe1988b4213ce08cb11tdsyijtd2r.jpg"
+        ));
+        respVO.setBrokerageFrozenDays(10);
+        respVO.setBrokerageWithdrawMinPrice(100);
+        return success(respVO);
+    }
+
+}

+ 21 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/config/vo/AppTradeConfigRespVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.trade.controller.app.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "用户 App - 交易配置 Response VO")
+@Data
+public class AppTradeConfigRespVO {
+
+    @Schema(description = "分销海报地址数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> brokeragePosterUrls;
+
+    @Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer brokerageFrozenDays;
+
+    @Schema(description = "佣金提现最小金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Integer brokerageWithdrawMinPrice;
+
+}

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/config/AppDeliveryConfigRespVO.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.config;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-// TODO 芋艿:后续要实现下,配送配置
+// TODO 芋艿:后续要实现下,配送配置;后续融合到 AppTradeConfigRespVO 中
 @Schema(description = "用户 App - 配送配置 Response VO")
 @Data
 public class AppDeliveryConfigRespVO {

+ 4 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java

@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
-import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
@@ -82,9 +81,10 @@ public class AppTradeOrderController {
     public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) {
         // 查询订单
         TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id);
-        if (order == null) {
-            return success(null);
-        }
+        // TODO @puhui999:这里建议改成,如果为 null,直接返回 success null;主要查询操作,尽量不要有非空的提示哈;交给前端处理;
+//        if (order == null) {
+//            return success(null, ORDER_NOT_FOUND.getMsg());
+//        }
 
         // 查询订单项
         List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId());

+ 6 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java

@@ -50,12 +50,18 @@ public class AppTradeOrderSettlementReqVO {
     private Long seckillActivityId;
 
     // ========== 拼团活动相关字段 ==========
+    // TODO @puhui999:是不是拼团记录的编号哈?
     @Schema(description = "拼团活动编号", example = "1024")
     private Long combinationActivityId;
 
     @Schema(description = "拼团团长编号", example = "2048")
     private Long combinationHeadId;
 
+    // ========== 砍价活动相关字段 ==========
+    // TODO @puhui999:是不是砍价记录的编号哈?
+    @Schema(description = "砍价活动编号", example = "123")
+    private Long bargainActivityId;
+
     @Data
     @Schema(description = "用户 App - 商品项")
     @Valid

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java

@@ -78,7 +78,7 @@ public interface TradeAfterSaleConvert {
         // 处理订单信息
         respVO.setOrder(convert(order));
         // 处理售后日志
-        respVO.setAfterSaleLog(convertList1(logs));
+        respVO.setLogs(convertList1(logs));
         return respVO;
     }
     List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespDTO> list);

+ 50 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/record/BrokerageRecordConvert.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.trade.convert.brokerage.record;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordRespVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 佣金记录 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface BrokerageRecordConvert {
+
+    BrokerageRecordConvert INSTANCE = Mappers.getMapper(BrokerageRecordConvert.class);
+
+    BrokerageRecordRespVO convert(BrokerageRecordDO bean);
+
+    List<BrokerageRecordRespVO> convertList(List<BrokerageRecordDO> list);
+
+    PageResult<BrokerageRecordRespVO> convertPage(PageResult<BrokerageRecordDO> page);
+
+    // TODO @疯狂:可能 title 不是很固化,会存在类似:沐晴成功购买《XXX JVM 实战》
+    default BrokerageRecordDO convert(BrokerageUserDO user, BrokerageRecordBizTypeEnum bizType, String bizId,
+                                      Integer brokerageFrozenDays, int brokeragePrice, LocalDateTime unfreezeTime,
+                                      String title) {
+        brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
+        // 不冻结时,佣金直接就是结算状态
+        Integer status = brokerageFrozenDays > 0
+                ? BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus()
+                : BrokerageRecordStatusEnum.SETTLEMENT.getStatus();
+        return new BrokerageRecordDO().setUserId(user.getId())
+                .setBizType(bizType.getType()).setBizId(bizId)
+                .setPrice(brokeragePrice).setTotalPrice(user.getBrokeragePrice())
+                .setTitle(title)
+                .setDescription(StrUtil.format(bizType.getDescription(), String.valueOf(brokeragePrice / 100.0)))
+                .setStatus(status).setFrozenDays(brokerageFrozenDays).setUnfreezeTime(unfreezeTime);
+    }
+
+}

+ 56 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/user/BrokerageUserConvert.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.trade.convert.brokerage.user;
+
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserRespVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 分销用户 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface BrokerageUserConvert {
+
+    BrokerageUserConvert INSTANCE = Mappers.getMapper(BrokerageUserConvert.class);
+
+    BrokerageUserRespVO convert(BrokerageUserDO bean);
+
+    List<BrokerageUserRespVO> convertList(List<BrokerageUserDO> list);
+
+    PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> page);
+
+    default PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> pageResult,
+                                                        Map<Long, MemberUserRespDTO> userMap,
+                                                        Map<Long, Long> brokerageUserCountMap,
+                                                        Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap) {
+        PageResult<BrokerageUserRespVO> result = convertPage(pageResult);
+        for (BrokerageUserRespVO vo : result.getList()) {
+            // 用户信息
+            Optional.ofNullable(userMap.get(vo.getId())).ifPresent(
+                    user -> vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
+            // 推广用户数量(一级)
+            vo.setBrokerageUserCount(MapUtil.getInt(brokerageUserCountMap, vo.getId(), 0));
+            // 推广订单数量、推广订单金额
+            Optional<UserBrokerageSummaryBO> orderSummaryOptional = Optional.ofNullable(userOrderSummaryMap.get(vo.getId()));
+            vo.setBrokerageOrderCount(orderSummaryOptional.map(UserBrokerageSummaryBO::getCount).orElse(0))
+                    .setBrokerageOrderPrice(orderSummaryOptional.map(UserBrokerageSummaryBO::getPrice).orElse(0));
+            // todo 已提现次数、已提现金额
+            vo.setWithdrawCount(0);
+            vo.setWithdrawPrice(0);
+        }
+        return result;
+    }
+
+    BrokerageUserDTO convertDTO(BrokerageUserDO brokerageUser);
+}

+ 23 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/config/TradeConfigConvert.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.trade.convert.config;
+
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigRespVO;
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * 交易中心配置 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeConfigConvert {
+
+    TradeConfigConvert INSTANCE = Mappers.getMapper(TradeConfigConvert.class);
+
+    TradeConfigDO convert(TradeConfigSaveReqVO bean);
+
+    TradeConfigRespVO convert(TradeConfigDO bean);
+
+}

+ 11 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java

@@ -7,11 +7,13 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
@@ -93,6 +95,7 @@ public interface TradeOrderConvert {
         items.forEach(item -> item.setIncrCount(-item.getIncrCount()));
         return new ProductSkuUpdateStockReqDTO(items);
     }
+
     List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
 
     @Mappings({
@@ -151,9 +154,10 @@ public interface TradeOrderConvert {
             TradeOrderDetailRespVO.OrderLog orderLog = new TradeOrderDetailRespVO.OrderLog();
             orderLog.setContent("订单操作" + i);
             orderLog.setCreateTime(LocalDateTime.now());
+            orderLog.setUserType(i % 2 == 0 ? 2 : 1);
             orderLogs.add(orderLog);
         }
-        orderVO.setOrderLog(orderLogs);
+        orderVO.setLogs(orderLogs);
         return orderVO;
     }
 
@@ -273,4 +277,10 @@ public interface TradeOrderConvert {
 
     TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
 
+    default BrokerageAddReqBO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
+        return new BrokerageAddReqBO().setBizId(String.valueOf(item.getId()))
+                .setBasePrice(item.getPayPrice() * item.getCount())
+                .setFirstFixedPrice(sku.getSubCommissionFirstPrice())
+                .setSecondFixedPrice(sku.getSubCommissionSecondPrice());
+    }
 }

+ 82 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/BrokerageRecordDO.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 佣金记录 DO
+ *
+ * @author owen
+ */
+@TableName("trade_brokerage_record")
+@KeySequence("trade_brokerage_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrokerageRecordDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Integer id;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 业务编号
+     */
+    private String bizId;
+    /**
+     * 业务类型
+     * <p>
+     * 枚举 {@link BrokerageRecordBizTypeEnum}
+     */
+    private Integer bizType;
+
+    /**
+     * 标题
+     */
+    private String title;
+    /**
+     * 说明
+     */
+    private String description;
+
+    /**
+     * 金额
+     */
+    private Integer price;
+    /**
+     * 当前总佣金
+     */
+    private Integer totalPrice;
+
+    /**
+     * 状态
+     * <p>
+     * 枚举 {@link BrokerageRecordStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 冻结时间(天)
+     */
+    private Integer frozenDays;
+    /**
+     * 解冻时间
+     */
+    private LocalDateTime unfreezeTime;
+
+}

+ 63 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/BrokerageUserDO.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 分销用户 DO
+ *
+ * @author owen
+ */
+@TableName("trade_brokerage_user")
+@KeySequence("trade_brokerage_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrokerageUserDO extends BaseDO {
+
+    /**
+     * 用户编号
+     * <p>
+     * 对应 MemberUserDO 的 id 字段
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 推广员编号
+     * <p>
+     * 关联 MemberUserDO 的 id 字段
+     */
+    private Long bindUserId;
+    /**
+     * 推广员绑定时间
+     */
+    private LocalDateTime bindUserTime;
+
+    /**
+     * 是否有分销资格
+     */
+    private Boolean brokerageEnabled;
+    /**
+     * 成为分销员时间
+     */
+    private LocalDateTime brokerageTime;
+
+    /**
+     * 可用佣金
+     */
+    private Integer brokeragePrice;
+    /**
+     * 冻结佣金
+     */
+    private Integer frozenPrice;
+
+}

+ 90 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * 交易中心配置 DO
+ *
+ * @author owen
+ */
+@TableName(value = "trade_config", autoResultMap = true)
+@KeySequence("trade_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TradeConfigDO extends BaseDO {
+
+    /**
+     * 自增主键
+     */
+    @TableId
+    private Long id;
+
+    // ========== 分销相关 ==========
+
+    /**
+     * 是否启用分佣
+     */
+    private Boolean brokerageEnabled;
+    /**
+     * 分佣模式
+     * <p>
+     * 枚举 {@link BrokerageEnabledConditionEnum 对应的类}
+     */
+    private Integer brokerageEnabledCondition;
+    /**
+     * 分销关系绑定模式
+     * <p>
+     * 枚举 {@link BrokerageBindModeEnum 对应的类}
+     */
+    private Integer brokerageBindMode;
+    /**
+     * 分销海报图地址数组
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<String> brokeragePostUrls;
+    /**
+     * 一级返佣比例
+     */
+    private Integer brokerageFirstPercent;
+    /**
+     * 二级返佣比例
+     */
+    private Integer brokerageSecondPercent;
+    /**
+     * 用户提现最低金额
+     */
+    private Integer brokerageWithdrawMinPrice;
+    /**
+     * 提现银行
+     */
+    @TableField(typeHandler = IntegerListTypeHandler.class)
+    private List<Integer> brokerageBankNames;
+    /**
+     * 佣金冻结时间(天)
+     */
+    private Integer brokerageFrozenDays;
+    /**
+     * 提现方式
+     * <p>
+     * 枚举 {@link BrokerageWithdrawTypeEnum 对应的类}
+     */
+    @TableField(typeHandler = IntegerListTypeHandler.class)
+    private List<Integer> brokerageWithdrawType;
+
+}

+ 56 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/BrokerageRecordMapper.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 佣金记录 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
+
+    default PageResult<BrokerageRecordDO> selectPage(BrokerageRecordPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageRecordDO>()
+                .eqIfPresent(BrokerageRecordDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(BrokerageRecordDO::getBizType, reqVO.getBizType())
+                .eqIfPresent(BrokerageRecordDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(BrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(BrokerageRecordDO::getId));
+    }
+
+    default List<BrokerageRecordDO> selectListByStatusAndUnfreezeTimeLt(Integer status, LocalDateTime unfreezeTime) {
+        return selectList(new LambdaQueryWrapper<BrokerageRecordDO>()
+                .eq(BrokerageRecordDO::getStatus, status)
+                .lt(BrokerageRecordDO::getUnfreezeTime, unfreezeTime));
+    }
+
+    default int updateByIdAndStatus(Integer id, Integer status, BrokerageRecordDO updateObj) {
+        return update(updateObj, new LambdaQueryWrapper<BrokerageRecordDO>()
+                .eq(BrokerageRecordDO::getId, id)
+                .eq(BrokerageRecordDO::getStatus, status));
+    }
+
+    default BrokerageRecordDO selectByBizTypeAndBizId(Integer bizType, String bizId) {
+        return selectOne(BrokerageRecordDO::getBizType, bizType,
+                BrokerageRecordDO::getBizId, bizId);
+    }
+
+    // TODO @疯狂:mysql 关键字,大写哈;这样看起来清晰点;例如说 SELECT COUNT(1)
+    @Select("select count(1), sum(price) from trade_brokerage_record where user_id = #{userId} and biz_type = #{bizType} and status = #{status}")
+    UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
+                                                                             @Param("bizType") Integer bizType,
+                                                                             @Param("status") Integer status);
+}

+ 115 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java

@@ -0,0 +1,115 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 分销用户 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
+
+    default PageResult<BrokerageUserDO> selectPage(BrokerageUserPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageUserDO>()
+                .eqIfPresent(BrokerageUserDO::getBindUserId, reqVO.getBindUserId())
+                .eqIfPresent(BrokerageUserDO::getBrokerageEnabled, reqVO.getBrokerageEnabled())
+                .betweenIfPresent(BrokerageUserDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(BrokerageUserDO::getId));
+    }
+
+    /**
+     * 更新用户可用佣金(增加)
+     *
+     * @param id        用户编号
+     * @param incrCount 增加佣金(正数)
+     */
+    default void updatePriceIncr(Long id, Integer incrCount) {
+        Assert.isTrue(incrCount > 0);
+        LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
+                .setSql(" price = price + " + incrCount)
+                .eq(BrokerageUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户可用佣金(减少)
+     * 注意:理论上佣金可能已经提现,这时会扣出负数,确保平台不会造成损失
+     *
+     * @param id        用户编号
+     * @param incrCount 增加佣金(负数)
+     */
+    default void updatePriceDecr(Long id, Integer incrCount) {
+        Assert.isTrue(incrCount < 0);
+        LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
+                .setSql(" price = price + " + incrCount) // 负数,所以使用 + 号
+                .eq(BrokerageUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户冻结佣金(增加)
+     *
+     * @param id        用户编号
+     * @param incrCount 增加冻结佣金(正数)
+     */
+    default void updateFrozenPriceIncr(Long id, Integer incrCount) {
+        Assert.isTrue(incrCount > 0);
+        LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
+                .setSql(" frozen_price = frozen_price + " + incrCount)
+                .eq(BrokerageUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户冻结佣金(减少)
+     * 注意:理论上冻结佣金可能已经解冻,这时会扣出负数,确保平台不会造成损失
+     *
+     * @param id        用户编号
+     * @param incrCount 减少冻结佣金(负数)
+     */
+    default void updateFrozenPriceDecr(Long id, Integer incrCount) {
+        Assert.isTrue(incrCount < 0);
+        LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
+                .setSql(" frozen_price = frozen_price + " + incrCount) // 负数,所以使用 + 号
+                .eq(BrokerageUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户冻结佣金(减少), 更新用户佣金(增加)
+     *
+     * @param id        用户编号
+     * @param incrCount 减少冻结佣金(负数)
+     * @return 更新条数
+     */
+    default int updateFrozenPriceDecrAndPriceIncr(Long id, Integer incrCount) {
+        Assert.isTrue(incrCount < 0);
+        LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
+                .setSql(" frozen_price = frozen_price + " + incrCount + // 负数,所以使用 + 号
+                        ", price = price + " + -incrCount) // 负数,所以使用 - 号
+                .eq(BrokerageUserDO::getId, id)
+                .ge(BrokerageUserDO::getFrozenPrice, -incrCount); // cas 逻辑
+        return update(null, lambdaUpdateWrapper);
+    }
+
+    default void updateBindUserIdAndBindUserTimeToNull(Long id) {
+        update(null, new LambdaUpdateWrapper<BrokerageUserDO>()
+                .eq(BrokerageUserDO::getId, id)
+                .set(BrokerageUserDO::getBindUserId, null).set(BrokerageUserDO::getBindUserTime, null));
+    }
+
+    default void updateEnabledFalseAndBrokerageTimeToNull(Long id) {
+        update(null, new LambdaUpdateWrapper<BrokerageUserDO>()
+                .eq(BrokerageUserDO::getId, id)
+                .set(BrokerageUserDO::getBrokerageEnabled, false).set(BrokerageUserDO::getBrokerageTime, null));
+    }
+
+}

+ 15 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/config/TradeConfigMapper.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 交易中心配置 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeConfigMapper extends BaseMapperX<TradeConfigDO> {
+
+}

+ 36 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/no/TradeOrderNoRedisDAO.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.trade.dal.redis.no;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+
+/**
+ * 订单序号的 Redis DAO
+ *
+ * @author HUIHUI
+ */
+@Repository
+public class TradeOrderNoRedisDAO {
+
+    public static final String TRADE_ORDER_NO_PREFIX = "O";
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 生成序号
+     *
+     * @param prefix 前缀
+     * @return 序号
+     */
+    public String generate(String prefix) {
+        String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
+        Long no = stringRedisTemplate.opsForValue().increment(noPrefix);
+        return noPrefix + no;
+    }
+
+}

+ 29 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/brokerage/BrokerageRecordUnfreezeJob.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.trade.job.brokerage;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 佣金解冻 Job
+ *
+ * @author owen
+ */
+@Component
+@TenantJob
+public class BrokerageRecordUnfreezeJob implements JobHandler {
+
+    @Resource
+    private BrokerageRecordService brokerageRecordService;
+
+    @Override
+    public String execute(String param) {
+        int count = brokerageRecordService.unfreezeRecord();
+        return StrUtil.format("解冻佣金 {} 个", count);
+    }
+
+}

+ 4 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位文件,无特殊用途
+ */
+package cn.iocoder.yudao.module.trade.job;

+ 1 - 6
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java

@@ -90,12 +90,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
 
     @Override
     public TradeAfterSaleDO getAfterSale(Long id) {
-        TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
-        // TODO @puhui999;读不到,不要这里报错哈;交给前端报错;一般是读取信息不到,message 提示,然后 close tab;
-        if (afterSale == null) {
-            throw exception(AFTER_SALE_NOT_FOUND);
-        }
-        return afterSale;
+        return tradeAfterSaleMapper.selectById(id);
     }
 
     // TODO 芋艿:拼团失败,要不要发起售后的方式退款?还是走取消逻辑?

+ 39 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/BrokerageAddReqBO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.bo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 佣金 增加 Request BO
+ *
+ * @author owen
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrokerageAddReqBO {
+
+    /**
+     * 业务编号
+     */
+    @NotBlank(message = "业务编号不能为空")
+    private String bizId;
+    /**
+     * 佣金基数
+     */
+    @NotNull(message = "佣金基数不能为空")
+    private Integer basePrice;
+    /**
+     * 一级佣金(固定)
+     */
+    private Integer firstFixedPrice;
+    /**
+     * 二级佣金(固定)
+     */
+    private Integer secondFixedPrice;
+
+}

+ 26 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.bo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 用户佣金合计 BO
+ *
+ * @author owen
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserBrokerageSummaryBO {
+
+    /**
+     * 佣金数量
+     */
+    private Integer count;
+    /**
+     * 佣金总额
+     */
+    private Integer price;
+
+}

Some files were not shown because too many files changed in this diff