lk пре 4 дана
родитељ
комит
f4aee92a49

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

@@ -93,7 +93,7 @@ public interface FsStoreCartScrmMapper
             "delete from fs_store_cart_scrm where id in"+
            "<foreach collection='array' item='id' open='(' separator=',' close=')'>#{id}</foreach>"+
             "</script>"})
-    int delCart(Long[] ids);
+    int delCart(@Param("array") List<Long> array);
     @Select("select c.*,p.cate_id,p.product_name,p.image as product_image,p.temp_id,p.product_type,v.price,v.sku as product_attr_name,v.image as product_attr_image,v.stock,v.cost,v.integral,v.weight,v.volume,v.bar_code,v.group_bar_code,v.brokerage,v.brokerage_two,v.brokerage_three from fs_store_cart_scrm c left join fs_store_product_scrm p on p.product_id=c.product_id left join fs_store_product_attr_value_scrm v on v.id=c.product_attr_value_id where find_in_set(c.id,#{ids})")
     List<FsStoreCartQueryVO> selectFsStoreCartListByIds(String ids);
     @Update("update  fs_store_cart_scrm set is_pay=1 where find_in_set(id,#{cartIds})")

+ 7 - 1
java/fs-service/src/main/java/com/fs/hisStore/param/FsStoreCartDelParam.java

@@ -1,18 +1,24 @@
 package com.fs.hisStore.param;
 
 import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.w3c.dom.stylesheets.LinkStyle;
 
 import javax.validation.constraints.NotNull;
+import java.util.List;
 
 @Getter
 @Setter
+@Accessors(chain = true)
+@AllArgsConstructor
 public class FsStoreCartDelParam {
 
     @NotNull(message = "参数有误")
     @ApiModelProperty(value = "购物车ID,多个用,分隔开")
-    Long[] ids;
+    List<Long> ids;
 
     @ApiModelProperty(value = "店铺ID")
     private Long storeId;

+ 3 - 0
java/fs-service/src/main/java/com/fs/hisStore/service/IFsStoreCartScrmService.java

@@ -89,6 +89,9 @@ public interface IFsStoreCartScrmService
     /** 多店铺改变购物车数量 */
     R changeNumByStore(long userId, FsStoreCartNumParam cartParam);
 
+    /** 多店铺添加购物车(数量覆盖,不累加) */
+    R addCartByStoreOverride(long userId, FsStoreCartParam cartParam);
+
     /** 多店铺删除购物车 */
     R delCartByStore(long userId, FsStoreCartDelParam cartParam);
 

+ 60 - 0
java/fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreCartScrmServiceImpl.java

@@ -325,6 +325,66 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
         }
     }
 
+    @Override
+    public R addCartByStoreOverride(long uid, FsStoreCartParam cartParam) {
+        // 检查并调整限购数量
+        Integer adjustedNum = adjustPurchaseLimit(uid, cartParam.getProductId(), cartParam.getCartNum());
+        cartParam.setCartNum(adjustedNum);
+
+        if(cartParam.getIsBuy()==1){
+            FsStoreCartScrm storeCart = FsStoreCartScrm.builder()
+                    .cartNum(cartParam.getCartNum())
+                    .productAttrValueId(cartParam.getAttrValueId())
+                    .productId(cartParam.getProductId())
+                    .userId(uid)
+                    .isPay(0)
+                    .isDel(0)
+                    .isBuy(cartParam.getIsBuy())
+                    .storeId(cartParam.getStoreId())
+                    .build();
+            storeCart.setCreateTime(new Date());
+            checkProductStock(cartParam.getProductId(),storeCart.getProductAttrValueId(),cartParam.getCartNum());
+            fsStoreCartMapper.insertFsStoreCart(storeCart);
+            return R.ok().put("id",storeCart.getId());
+        }
+        else{
+            FsStoreCartScrm cartMap=new FsStoreCartScrm();
+            cartMap.setUserId(uid);
+            cartMap.setIsPay(0);
+            cartMap.setIsBuy(0);
+            cartMap.setProductId(cartParam.getProductId());
+            cartMap.setProductAttrValueId(cartParam.getAttrValueId());
+            cartMap.setStoreId(cartParam.getStoreId());
+            List<FsStoreCartScrm> cart = fsStoreCartMapper.selectFsStoreCartList(cartMap);
+            FsStoreCartScrm storeCart;
+            if(cart==null||cart.size()==0){
+                storeCart = FsStoreCartScrm.builder()
+                        .cartNum(cartParam.getCartNum())
+                        .productAttrValueId(cartMap.getProductAttrValueId())
+                        .productId(cartMap.getProductId())
+                        .userId(uid)
+                        .isPay(0)
+                        .isDel(0)
+                        .isBuy(0)
+                        .storeId(cartParam.getStoreId())
+                        .build();
+                storeCart.setCreateTime(new Date());
+                checkProductStock(cartParam.getProductId(),storeCart.getProductAttrValueId(),cartParam.getCartNum());
+                fsStoreCartMapper.insertFsStoreCart(storeCart);
+                return R.ok().put("id",storeCart.getId());
+            }
+            else{
+                // 覆盖模式:不累加已有数量,直接用请求中的 cartNum 覆盖
+                storeCart=cart.get(0);
+                storeCart.setCartNum(cartParam.getCartNum());
+                storeCart.setUpdateTime(new Date());
+                checkProductStock(cartParam.getProductId(),storeCart.getProductAttrValueId(),cartParam.getCartNum());
+                fsStoreCartMapper.updateFsStoreCart(storeCart);
+                return R.ok().put("id",storeCart.getId());
+            }
+        }
+    }
+
     @Override
     public R changeNumByStore(long userId, FsStoreCartNumParam cartParam) {
         return changeNum(userId, cartParam);

+ 9 - 0
java/fs-service/src/main/resources/db/tenant-initData.sql

@@ -5735,3 +5735,12 @@ VALUES (1, '开始节点', 'start', '自动生成触发条件和变量初始化'
        (52, '商品推送', 'product_push', '推送商品小程序地址', 'extended', 52),
        (53, '物流推送', 'logistics_notify', '推送物流信息', 'extended', 53),
        (100, '外部API', 'external_api', '调用外部API接口', 'external', 100);
+-- 3. 插入系统菜单(代理管理)
+INSERT INTO `sys_menu` (`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `is_show`, `link_type`, `link_url`, `sort`, `app_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
+('代理管理', 0, 2, 'agent', NULL, NULL, 1, 0, 'M', '0', '0', NULL, 'user', b'1', b'0', NULL, 1, NULL, 'admin', now(), '', now(), '代理管理目录'),
+('代理列表', (SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name = '代理管理' AND parent_id = 0) AS t), 1, 'list', 'agent/index', NULL, 1, 0, 'C', '0', '0', 'agent:agent:list', '#', b'1', b'0', NULL, 1, NULL, 'admin', now(), '', now(), '代理列表菜单'),
+('代理新增', (SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name = '代理管理' AND parent_id = 0) AS t), 2, NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'agent:agent:add', '#', b'1', b'0', NULL, 2, NULL, 'admin', now(), '', now(), ''),
+('代理编辑', (SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name = '代理管理' AND parent_id = 0) AS t), 3, NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'agent:agent:edit', '#', b'1', b'0', NULL, 3, NULL, 'admin', now(), '', now(), ''),
+('代理删除', (SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name = '代理管理' AND parent_id = 0) AS t), 4, NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'agent:agent:remove', '#', b'1', b'0', NULL, 4, NULL, 'admin', now(), '', now(), ''),
+('代理导出', (SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name = '代理管理' AND parent_id = 0) AS t), 5, NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'agent:agent:export', '#', b'1', b'0', NULL, 5, NULL, 'admin', now(), '', now(), ''),
+('代理查询', (SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name = '代理管理' AND parent_id = 0) AS t), 6, NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'agent:agent:query', '#', b'1', b'0', NULL, 6, NULL, 'admin', now(), '', now(), '');

+ 186 - 1
java/fs-service/src/main/resources/db/tenant-initTable.sql

@@ -7807,13 +7807,15 @@ CREATE TABLE `fs_store_cart_scrm`
     `is_del`                tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
     `is_buy`                tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否为立即购买',
     `change_price`          decimal(10, 2) NULL DEFAULT NULL COMMENT '改价(商品单价)',
+    `store_id` BIGINT NULL COMMENT '店铺ID' AFTER change_price,
     PRIMARY KEY (`id`) USING BTREE,
     INDEX                   `user_id`(`user_id` ASC) USING BTREE,
     INDEX                   `goods_id`(`product_id` ASC) USING BTREE,
     INDEX                   `uid`(`user_id` ASC, `is_pay` ASC) USING BTREE,
     INDEX                   `uid_2`(`user_id` ASC, `is_del` ASC) USING BTREE,
     INDEX                   `uid_3`(`user_id` ASC, `is_buy` ASC) USING BTREE,
-    INDEX                   `type`(`type` ASC) USING BTREE
+    INDEX                   `type`(`type` ASC) USING BTREE,
+    INDEX idx_store_id(store_id) using BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '购物车表' ROW_FORMAT = DYNAMIC;
 
 -- ----------------------------
@@ -9515,6 +9517,8 @@ CREATE TABLE `fs_store_scrm`
     `other_special_qualification_start` date NULL DEFAULT NULL,
     `other_special_qualification_end`   date NULL DEFAULT NULL,
     `is_business_license_permanent`     int NULL DEFAULT NULL,
+    `opening_hours`                    VARCHAR(128) DEFAULT NULL COMMENT '营业时间',
+    `store_score`                       DECIMAL(3,1) NULL DEFAULT 4.9 COMMENT '店铺评分',
     PRIMARY KEY (`store_id`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '店铺表' ROW_FORMAT = DYNAMIC;
 
@@ -21311,3 +21315,184 @@ CREATE TABLE IF NOT EXISTS `video_pushplatform` (
     KEY `idx_user_id` (`user_id`),
     KEY `idx_platform` (`platform`)
     ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短视频平台视频分发发布记录';
+-- =====================================================
+-- 库存调拨模块:调拨单、调拨明细、调拨日志
+-- Tenant DB
+-- =====================================================
+
+-- 1. 库存调拨单主表
+CREATE TABLE IF NOT EXISTS `fs_inventory_transfer` (
+                                                       `id`                BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                                       `transfer_no`       VARCHAR(64)  NOT NULL COMMENT '调拨单号(DB+日期+序号)',
+    `from_store_id`     BIGINT       NOT NULL COMMENT '调出门店ID',
+    `from_store_name`   VARCHAR(200) DEFAULT NULL COMMENT '调出门店名称',
+    `to_store_id`       BIGINT       NOT NULL COMMENT '调入门店ID',
+    `to_store_name`     VARCHAR(200) DEFAULT NULL COMMENT '调入门店名称',
+    `total_quantity`    INT          DEFAULT 0 COMMENT '调拨总数量',
+    `total_amount`      DECIMAL(12,2) DEFAULT 0.00 COMMENT '调拨总金额(成本价)',
+    `status`            VARCHAR(20)  NOT NULL DEFAULT 'DRAFT' COMMENT 'DRAFT-草稿 SUBMITTED-待审核 APPROVED-已审核 REJECTED-已驳回 IN_TRANSIT-运输中 RECEIVED-已收货 CANCELLED-已取消',
+    `apply_user_id`     BIGINT       DEFAULT NULL COMMENT '申请人ID',
+    `apply_user_name`   VARCHAR(64)  DEFAULT NULL COMMENT '申请人姓名',
+    `apply_time`        DATETIME     DEFAULT NULL COMMENT '申请时间',
+    `approve_user_id`   BIGINT       DEFAULT NULL COMMENT '审批人ID',
+    `approve_user_name` VARCHAR(64)  DEFAULT NULL COMMENT '审批人姓名',
+    `approve_time`      DATETIME     DEFAULT NULL COMMENT '审批时间',
+    `approve_remark`    VARCHAR(500) DEFAULT NULL COMMENT '审批备注',
+    `ship_user_id`      BIGINT       DEFAULT NULL COMMENT '发货人ID',
+    `ship_user_name`    VARCHAR(64)  DEFAULT NULL COMMENT '发货人姓名',
+    `ship_time`         DATETIME     DEFAULT NULL COMMENT '发货时间',
+    `receive_user_id`   BIGINT       DEFAULT NULL COMMENT '收货人ID',
+    `receive_user_name` VARCHAR(64)  DEFAULT NULL COMMENT '收货人姓名',
+    `receive_time`      DATETIME     DEFAULT NULL COMMENT '收货时间',
+    `receive_remark`    VARCHAR(500) DEFAULT NULL COMMENT '收货备注',
+    `create_by`         VARCHAR(64)  DEFAULT '' COMMENT '创建人',
+    `create_time`       DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_by`         VARCHAR(64)  DEFAULT '' COMMENT '更新人',
+    `update_time`       DATETIME     DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `remark`            VARCHAR(500) DEFAULT NULL COMMENT '备注',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_transfer_no` (`transfer_no`),
+    KEY `idx_from_store` (`from_store_id`),
+    KEY `idx_to_store` (`to_store_id`),
+    KEY `idx_status` (`status`),
+    KEY `idx_apply_user` (`apply_user_id`),
+    KEY `idx_create_time` (`create_time`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存调拨单';
+
+-- 2. 库存调拨明细表
+CREATE TABLE IF NOT EXISTS `fs_inventory_transfer_item` (
+                                                            `id`                BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                                            `transfer_no`       VARCHAR(64)   NOT NULL COMMENT '关联调拨单号',
+    `product_id`        BIGINT        NOT NULL COMMENT '商品ID',
+    `product_name`      VARCHAR(200)  DEFAULT NULL COMMENT '商品名称',
+    `product_image`     VARCHAR(500)  DEFAULT NULL COMMENT '商品图片',
+    `product_spec`      VARCHAR(200)  DEFAULT NULL COMMENT '商品规格',
+    `unit`              VARCHAR(50)   DEFAULT NULL COMMENT '单位',
+    `quantity`          INT           NOT NULL DEFAULT 0 COMMENT '调拨数量',
+    `cost_price`        DECIMAL(12,2) DEFAULT 0.00 COMMENT '成本单价',
+    `total_amount`      DECIMAL(12,2) DEFAULT 0.00 COMMENT '小计金额',
+    `from_stock_before` INT           DEFAULT 0 COMMENT '调出前库存',
+    `from_stock_after`  INT           DEFAULT 0 COMMENT '调出后库存',
+    `to_stock_before`   INT           DEFAULT 0 COMMENT '调入前库存',
+    `to_stock_after`    INT           DEFAULT 0 COMMENT '调入后库存',
+    `create_time`       DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_transfer_no` (`transfer_no`),
+    KEY `idx_product_id` (`product_id`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存调拨明细';
+
+-- 3. 库存调拨操作日志表
+CREATE TABLE IF NOT EXISTS `fs_inventory_transfer_log` (
+                                                           `id`              BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                                           `transfer_no`     VARCHAR(64)  NOT NULL COMMENT '关联调拨单号',
+    `operation`       VARCHAR(50)  NOT NULL COMMENT '操作类型: CREATE/SUBMIT/APPROVE/REJECT/SHIP/RECEIVE/CANCEL',
+    `operator_id`     BIGINT       DEFAULT NULL COMMENT '操作人ID',
+    `operator_name`   VARCHAR(64)  DEFAULT NULL COMMENT '操作人姓名',
+    `content`         VARCHAR(1000) DEFAULT NULL COMMENT '操作内容',
+    `create_time`     DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_transfer_no` (`transfer_no`),
+    KEY `idx_operation` (`operation`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存调拨操作日志';
+
+-- =====================================================
+-- 供应链模块:库存表 & 调拨记录表
+-- 表前缀 sc_ (supply chain)
+-- =====================================================
+
+-- 1. 库存表
+CREATE TABLE IF NOT EXISTS `sc_inventory` (
+                                              `id`              BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                              `company_id`      BIGINT       DEFAULT NULL COMMENT '租户ID',
+                                              `goods_name`      VARCHAR(200) NOT NULL COMMENT '商品名称',
+    `spec`            VARCHAR(100) DEFAULT NULL COMMENT '规格',
+    `warehouse`       VARCHAR(100) DEFAULT NULL COMMENT '仓库名称',
+    `quantity`        INT          DEFAULT 0 COMMENT '库存数量',
+    `unit`            VARCHAR(20)  DEFAULT NULL COMMENT '单位',
+    `alert_threshold` INT          DEFAULT 0 COMMENT '预警阈值',
+    `create_by`       VARCHAR(64)  DEFAULT '' COMMENT '创建人',
+    `create_time`     DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_by`       VARCHAR(64)  DEFAULT '' COMMENT '更新人',
+    `update_time`     DATETIME     DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`),
+    KEY `idx_goods_name` (`goods_name`),
+    KEY `idx_warehouse` (`warehouse`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表';
+
+-- 2. 调拨记录表
+CREATE TABLE IF NOT EXISTS `sc_transfer_record` (
+                                                    `id`               BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+                                                    `company_id`       BIGINT       DEFAULT NULL COMMENT '租户ID',
+                                                    `goods_id`         BIGINT       DEFAULT NULL COMMENT '商品ID',
+                                                    `goods_name`       VARCHAR(200) NOT NULL COMMENT '商品名称',
+    `from_warehouse`   VARCHAR(100) NOT NULL COMMENT '调出仓库',
+    `to_warehouse`     VARCHAR(100) NOT NULL COMMENT '调入仓库',
+    `quantity`         INT          NOT NULL COMMENT '调拨数量',
+    `status`           VARCHAR(20)  DEFAULT 'processing' COMMENT '状态: processing-调拨中 done-已完成',
+    `operator`         VARCHAR(64)  DEFAULT NULL COMMENT '操作人',
+    `remark`           VARCHAR(500) DEFAULT NULL COMMENT '备注',
+    `create_by`        VARCHAR(64)  DEFAULT '' COMMENT '创建人',
+    `create_time`      DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_by`        VARCHAR(64)  DEFAULT '' COMMENT '更新人',
+    `update_time`      DATETIME     DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`),
+    KEY `idx_goods_name` (`goods_name`),
+    KEY `idx_status` (`status`),
+    KEY `idx_create_time` (`create_time`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='调拨记录表';
+CREATE TABLE fs_store_favorite_scrm (
+    favorite_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '收藏ID',
+    user_id     BIGINT NOT NULL COMMENT '用户ID',
+    store_id    BIGINT NOT NULL COMMENT '店铺ID',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    KEY idx_user_id (user_id),
+    KEY idx_store_id (store_id),
+    UNIQUE KEY uk_user_store (user_id, store_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户店铺收藏表';
+-- 1. 代理信息表
+CREATE TABLE `agent` (
+    `id`                BIGINT(20)   NOT NULL AUTO_INCREMENT   COMMENT '代理ID',
+    `agent_name`        VARCHAR(100) NOT NULL                  COMMENT '代理名称',
+    `contact_person`    VARCHAR(50)  DEFAULT NULL              COMMENT '联系人',
+    `phone`             VARCHAR(20)  DEFAULT NULL              COMMENT '联系电话',
+    `email`             VARCHAR(100) DEFAULT NULL              COMMENT '邮箱',
+    `address`           VARCHAR(255) DEFAULT NULL              COMMENT '地址',
+    `agent_level`       INT(11)      DEFAULT 1                COMMENT '代理等级(1=一级,2=二级...)',
+    `max_tenant_count`  INT(11)      DEFAULT 10               COMMENT '可创建租户数量上限',
+    `current_tenant_count` INT(11)   DEFAULT 0                COMMENT '当前已创建租户数',
+    `balance`           DECIMAL(12,2) DEFAULT 0.00            COMMENT '账户余额(元)',
+    `total_recharge`    DECIMAL(12,2) DEFAULT 0.00            COMMENT '累计充值(元)',
+    `total_cost`        DECIMAL(12,2) DEFAULT 0.00            COMMENT '累计消费(元)',
+    `status`            TINYINT(1)   DEFAULT 1                COMMENT '状态(1=正常, 0=禁用)',
+    `remark`            VARCHAR(500) DEFAULT NULL              COMMENT '备注',
+    `create_by`         VARCHAR(64)  DEFAULT NULL              COMMENT '创建者',
+    `create_time`       DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_by`         VARCHAR(64)  DEFAULT NULL              COMMENT '更新者',
+    `update_time`       DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_agent_name` (`agent_name`),
+    KEY `idx_phone`      (`phone`),
+    KEY `idx_status`     (`status`),
+    KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代理信息表';
+
+
+-- 2. 代理账户流水表(充值/扣款/分账/提现)
+CREATE TABLE `agent_wallet_txn` (
+    `id`              BIGINT(20)    NOT NULL AUTO_INCREMENT   COMMENT '流水ID',
+    `agent_id`        BIGINT(20)    NOT NULL                  COMMENT '代理ID',
+    `txn_type`        VARCHAR(32)   NOT NULL                  COMMENT '交易类型(RECHARGE=充值, DEDUCT=扣款, PROFIT=分账收入, WITHDRAW=提现)',
+    `amount`          DECIMAL(12,2) NOT NULL                  COMMENT '交易金额(正=收入, 负=支出)',
+    `balance_before`  DECIMAL(12,2) DEFAULT 0.00             COMMENT '交易前余额',
+    `balance_after`   DECIMAL(12,2) DEFAULT 0.00             COMMENT '交易后余额',
+    `biz_no`          VARCHAR(64)   DEFAULT NULL              COMMENT '业务单号(关联订单/结算批次)',
+    `remark`          VARCHAR(255)  DEFAULT NULL              COMMENT '备注',
+    `create_time`     DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_agent_id`    (`agent_id`),
+    KEY `idx_txn_type`    (`txn_type`),
+    KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代理账户流水表';

+ 54 - 44
java/fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java

@@ -66,7 +66,7 @@ public class ProductScrmController extends AppBaseController {
 
     @Autowired
     private IFsStoreProductPurchaseLimitScrmService purchaseLimitService;
-    
+
     @Autowired
     private IFsStoreOrderItemScrmService orderItemService;
 
@@ -321,7 +321,7 @@ public class ProductScrmController extends AppBaseController {
     @Login
     @ApiOperation("检查商品限购")
     @GetMapping("/checkPurchaseLimit")
-    public R checkPurchaseLimit(@RequestParam(value="productId") Long productId, 
+    public R checkPurchaseLimit(@RequestParam(value="productId") Long productId,
                                 @RequestParam(value="num", required = false, defaultValue = "1") Integer num){
         try {
             // 查询商品信息
@@ -329,13 +329,13 @@ public class ProductScrmController extends AppBaseController {
             if(product == null){
                 return R.error("商品不存在");
             }
-            
+
             // 检查是否限购
             if(product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0){
                 // 商品不限购,直接返回成功
                 return R.ok();
             }
-            
+
             // 商品有限购,查询用户已购买数量
             String userId = getUserId();
             if(userId == null){
@@ -347,14 +347,14 @@ public class ProductScrmController extends AppBaseController {
             if (purchaseLimit != null) {
                 purchasedNum = purchaseLimit.getNum();
             }
-            
+
             // 计算剩余可购买数量
             int remainingPurchaseLimit = product.getPurchaseLimit() - purchasedNum;
             if (remainingPurchaseLimit < num) {
                 // 剩余可购买数量不足
                 return R.error("该商品限购" + product.getPurchaseLimit() + "件,您已购买" + purchasedNum + "件,本次购买" + num + "件,超出限购数量");
             }
-            
+
             // 剩余可购买数量充足
             return R.ok();
         } catch (Exception e){
@@ -388,6 +388,14 @@ public class ProductScrmController extends AppBaseController {
         return result;
     }
     @Login
+    @ApiOperation("多店铺添加购物车(数量覆盖,不累加)")
+    @PostMapping("/addCartByStoreOverride")
+    public R addCartByStoreOverride(@Validated @RequestBody FsStoreCartParam cartParam, HttpServletRequest request){
+        R result = cartService.addCartByStoreOverride(Long.parseLong(getUserId()), cartParam);
+        clearCartCacheFull(Long.parseLong(getUserId()), cartParam.getStoreId());
+        return result;
+    }
+    @Login
     @ApiOperation("获取购物车列表")
     @GetMapping("/getCarts")
     public R getCarts(HttpServletRequest request){
@@ -468,11 +476,13 @@ public class ProductScrmController extends AppBaseController {
     @Login
     @ApiOperation("多店铺删除购物车")
     @PostMapping("/delCartByStore")
-    public R delCartByStore(@Validated @RequestBody FsStoreCartDelParam cartParam, HttpServletRequest request){
-        R result = cartService.delCartByStore(Long.parseLong(getUserId()), cartParam);
-        // 使用请求参数中的 storeId 清理缓存
-        clearCartCacheFull(Long.parseLong(getUserId()), cartParam.getStoreId());
-        return result;
+    public R delCartByStore(@RequestBody List<FsStoreCartDelParam> cartParams, HttpServletRequest request){
+        Long userId = Long.parseLong(getUserId());
+        for (FsStoreCartDelParam cartParam : cartParams) {
+            cartService.delCartByStore(userId, cartParam);
+            clearCartCacheFull(userId, cartParam.getStoreId());
+        }
+        return R.ok();
     }
 
 
@@ -534,29 +544,29 @@ public class ProductScrmController extends AppBaseController {
             if (userId == null) {
                 return R.error("用户未登录");
             }
-            
+
             if (param.getProducts() == null || param.getProducts().isEmpty()) {
                 return R.error("商品列表不能为空");
             }
-            
+
             Long userIdLong = Long.parseLong(userId);
             List<String> errorMessages = new ArrayList<>();
-            
+
             // 按productId分组,累加同一商品不同规格的数量
             Map<Long, Integer> productNumMap = new HashMap<>();
             Map<Long, String> productNameMap = new HashMap<>();
-            
+
             for (CartPurchaseLimitCheckParam.ProductItem item : param.getProducts()) {
                 Long productId = item.getProductId();
                 Integer num = item.getNum();
-                
+
                 if (productId == null || num == null || num <= 0) {
                     continue;
                 }
-                
+
                 // 累加同一商品的数量(不同规格算在一起)
                 productNumMap.put(productId, productNumMap.getOrDefault(productId, 0) + num);
-                
+
                 // 保存商品名称(用于错误提示)
                 if (!productNameMap.containsKey(productId)) {
                     FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
@@ -565,24 +575,24 @@ public class ProductScrmController extends AppBaseController {
                     }
                 }
             }
-            
+
             // 遍历分组后的商品,检查限购
             for (Map.Entry<Long, Integer> entry : productNumMap.entrySet()) {
                 Long productId = entry.getKey();
                 Integer totalNum = entry.getValue(); // 同一商品所有规格的总数量
-                
+
                 // 查询商品信息
                 FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
                 if (product == null) {
                     continue;
                 }
-                
+
                 // 检查是否限购
                 if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
                     // 商品不限购,跳过
                     continue;
                 }
-                
+
                 // 商品有限购,查询用户已购买数量
                 FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(
                         productId, userIdLong);
@@ -590,12 +600,12 @@ public class ProductScrmController extends AppBaseController {
                 if (purchaseLimit != null) {
                     purchasedNum = purchaseLimit.getNum();
                 }
-                
+
                 // 计算剩余可购买数量
                 int remainingPurchaseLimit = product.getPurchaseLimit() - purchasedNum;
                 if (remainingPurchaseLimit < totalNum) {
                     // 超过限购,添加错误信息
-                    String productName = productNameMap.getOrDefault(productId, 
+                    String productName = productNameMap.getOrDefault(productId,
                             product.getProductName() != null ? product.getProductName() : "商品ID:" + productId);
                     String errorMsg = String.format("商品【%s】限购%d件,您已购买%d件,本次购买%d件,超出限购数量",
                             productName,
@@ -605,13 +615,13 @@ public class ProductScrmController extends AppBaseController {
                     errorMessages.add(errorMsg);
                 }
             }
-            
+
             // 如果有错误信息,拼接并返回
             if (!errorMessages.isEmpty()) {
                 String errorMsg = String.join(";", errorMessages);
                 return R.error(errorMsg);
             }
-            
+
             // 所有商品都通过限购检查
             return R.ok();
         } catch (Exception e) {
@@ -628,39 +638,39 @@ public class ProductScrmController extends AppBaseController {
             if (userId == null) {
                 return R.error("用户未登录");
             }
-            
+
             if (orderCode == null || orderCode.trim().isEmpty()) {
                 return R.error("订单号不能为空");
             }
-            
+
             Long userIdLong = Long.parseLong(userId);
-            
+
             // 根据orderCode查询订单商品
             FsStoreOrderItemScrm queryParam = new FsStoreOrderItemScrm();
             queryParam.setOrderCode(orderCode);
             List<FsStoreOrderItemScrm> orderItems = orderItemService.selectFsStoreOrderItemList(queryParam);
-            
+
             if (orderItems == null || orderItems.isEmpty()) {
                 return R.error("订单不存在或订单中没有商品");
             }
-            
+
             List<String> errorMessages = new ArrayList<>();
-            
+
             // 按productId分组,累加同一商品不同规格的数量
             Map<Long, Integer> productNumMap = new HashMap<>();
             Map<Long, String> productNameMap = new HashMap<>();
-            
+
             for (FsStoreOrderItemScrm orderItem : orderItems) {
                 Long productId = orderItem.getProductId();
                 Integer num = orderItem.getNum();
-                
+
                 if (productId == null || num == null || num <= 0) {
                     continue;
                 }
-                
+
                 // 累加同一商品的数量(不同规格算在一起)
                 productNumMap.put(productId, productNumMap.getOrDefault(productId, 0) + num);
-                
+
                 // 保存商品名称(用于错误提示)
                 if (!productNameMap.containsKey(productId)) {
                     FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
@@ -669,24 +679,24 @@ public class ProductScrmController extends AppBaseController {
                     }
                 }
             }
-            
+
             // 遍历分组后的商品,检查限购
             for (Map.Entry<Long, Integer> entry : productNumMap.entrySet()) {
                 Long productId = entry.getKey();
                 Integer totalNum = entry.getValue(); // 同一商品所有规格的总数量
-                
+
                 // 查询商品信息
                 FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
                 if (product == null) {
                     continue;
                 }
-                
+
                 // 检查是否限购
                 if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
                     // 商品不限购,跳过
                     continue;
                 }
-                
+
                 // 商品有限购,查询用户已购买数量
                 FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(
                         productId, userIdLong);
@@ -694,12 +704,12 @@ public class ProductScrmController extends AppBaseController {
                 if (purchaseLimit != null) {
                     purchasedNum = purchaseLimit.getNum();
                 }
-                
+
                 // 计算剩余可购买数量(订单中的数量也要计算在内)
                 int remainingPurchaseLimit = product.getPurchaseLimit() - purchasedNum;
                 if (remainingPurchaseLimit < totalNum) {
                     // 超过限购,添加错误信息
-                    String productName = productNameMap.getOrDefault(productId, 
+                    String productName = productNameMap.getOrDefault(productId,
                             product.getProductName() != null ? product.getProductName() : "商品ID:" + productId);
                     String errorMsg = String.format("商品【%s】限购%d件,您已购买%d件,订单中购买%d件,超出限购数量",
                             productName,
@@ -709,13 +719,13 @@ public class ProductScrmController extends AppBaseController {
                     errorMessages.add(errorMsg);
                 }
             }
-            
+
             // 如果有错误信息,拼接并返回
             if (!errorMessages.isEmpty()) {
                 String errorMsg = String.join(";", errorMessages);
                 return R.error(errorMsg);
             }
-            
+
             // 所有商品都通过限购检查
             return R.ok();
         } catch (Exception e) {