# 夜市摊位抽成实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking. **Goal:** 摊位订单完成后,使用所属夜市的抽成比例计算平台佣金,收入入摊位门店钱包。 **Architecture:** 在现有 `setSanghuBilling` 中增加摊位判断分支,通过 `PosStore.isStall` 判断是否为摊位订单,是则用夜市的 `commission` 计算抽成,并调用新增的 `addStallBalance` 方法更新摊位门店钱包。 **Tech Stack:** Java, Spring Boot, MyBatis-Plus, Redisson --- ### Task 1: UserBilling 新增 storeId 字段 **Files:** - Modify: `ruoyi-system/src/main/java/com/ruoyi/system/domain/UserBilling.java` - [x] **Step 1: 在 UserBilling 实体中添加 storeId 字段** 在 `private Long mdId;` 之后添加: ```java /** 关联摊位ID,非摊位账单为NULL */ private Long storeId; ``` - [x] **Step 2: 在 UserBillingMapper.xml 的 resultMap 中添加映射** 文件:`ruoyi-system/src/main/resources/mapper/system/UserBillingMapper.xml` 找到 `mdId` 的 result 映射,在其后添加: ```xml ``` - [x] **Step 3: 在数据库中执行 ALTER TABLE** ```sql ALTER TABLE user_billing ADD COLUMN store_id BIGINT DEFAULT NULL COMMENT '关联摊位ID,非摊位账单为NULL'; ``` - [x] **Step 4: Commit** ```bash git add ruoyi-system/src/main/java/com/ruoyi/system/domain/UserBilling.java ruoyi-system/src/main/resources/mapper/system/UserBillingMapper.xml git commit -m "feat: UserBilling 新增 storeId 字段关联摊位钱包" ``` --- ### Task 2: WalletService 新增 addStallBalance 方法 **Files:** - Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/service/WalletService.java` - [x] **Step 1: 添加 addStallBalance 方法** 在 `addBalance(Long userId, BigDecimal amount, UserBilling billing)` 方法之后添加新方法。逻辑与 `addBalance` 类似,但通过 `storeId` 查找摊位钱包并更新: ```java /** * 增加摊位钱包余额 * * @param storeId 摊位门店ID * @param amount 增加金额 * @param billing 账单信息 */ @Transactional(rollbackFor = Exception.class) public void addStallBalance(Long storeId, BigDecimal amount, UserBilling billing) { if (storeId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException(MessageUtils.message("no.wallet.common.cs.error")); } UserWallet stallWallet = userWalletService.selectByStoreId(storeId); if (stallWallet == null) { throw new ServiceException("摊位钱包不存在"); } String lockKey = "wallet:stall:lock:" + storeId; RLock lock = redissonClient.getLock(lockKey); try { if (lock.tryLock(LOCK_WAIT_TIME, LOCK_TIMEOUT, TimeUnit.SECONDS)) { boolean releaseInFinally = true; try { BigDecimal newBalance = stallWallet.getBalanceWallet().add(amount); LambdaUpdateWrapper updateQuery = new LambdaUpdateWrapper<>(); updateQuery.eq(UserWallet::getStoreId, storeId) .eq(UserWallet::getVersion, stallWallet.getVersion()); stallWallet.setBalanceWallet(newBalance); stallWallet.setVersion(stallWallet.getVersion() + 1); boolean updateSuccess = userWalletService.update(stallWallet, updateQuery); createUserBill(billing, stallWallet.getBalanceWallet()); if (!updateSuccess) { throw new ServiceException(MessageUtils.message("no.wallet.update.faile")); } releaseInFinally = false; TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }); } finally { if (releaseInFinally && lock.isHeldByCurrentThread()) { lock.unlock(); } } } else { throw new ServiceException(MessageUtils.message("no.system.busy.try.again")); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ServiceException(MessageUtils.message("no.operation.interrupted.try.again")); } } ``` - [x] **Step 2: Commit** ```bash git add ruoyi-admin/src/main/java/com/ruoyi/app/service/WalletService.java git commit -m "feat: WalletService 新增 addStallBalance 摊位钱包操作方法" ``` --- ### Task 3: 修改 setSanghuBilling 抽成逻辑 **Files:** - Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java` - [x] **Step 1: 注入 IPosStoreService 依赖** 在 `PosOrderController` 中已有的 `@Autowired` 字段区域添加: ```java @Autowired private IPosStoreService posStoreService; ``` 确认 import 存在: ```java import com.ruoyi.system.service.IPosStoreService; ``` - [x] **Step 2: 修改 setSanghuBilling 方法** 将现有方法(约 576-641 行)替换为: ```java public void setSanghuBilling(@NotNull PosOrder posOrder) { long count = userBillingService.count(new LambdaQueryWrapper().eq(UserBilling::getUserId, posOrder.getShId()).eq(UserBilling::getType, "0").eq(UserBilling::getDdId, posOrder.getDdId())); if (count <= 0) { UserBilling billing = new UserBilling(); DecimalFormat df = new DecimalFormat("#.00"); // 判断是否为摊位订单 PosStore store = posStoreService.getById(posOrder.getMdId()); boolean isStall = store != null && store.getIsStall() != null && store.getIsStall() == 1 && store.getNightMarketId() != null; Double fcbili; if (isStall) { InfoUser nightMarket = infoUserService.getById(store.getNightMarketId()); fcbili = nightMarket.getCommission() == null ? 0.00 : nightMarket.getCommission(); } else { InfoUser user = infoUserService.getById(posOrder.getShId()); fcbili = user.getCommission() == null ? 0.00 : user.getCommission(); } fcbili = getShCommissionRate(fcbili, posOrder.getShId()); JSONArray list = JSONArray.parseArray(posOrder.getFood()); int fenc = 0; for (int i = 0; i < list.size(); i++) { JSONObject foods = list.getJSONObject(i); int price = foods.getInteger("price"); int otherPrice = foods.getInteger("otherPrice"); int num = foods.getInteger("number"); int age = (price + otherPrice) * num; fenc += age; } String remark = ""; if (posOrder.getMdYhId() != null && posOrder.getMdDiscountAmount() != null) { VipUserQuanyi userQuanyi = userQuanyiService.getById(posOrder.getMdYhId()); if (userQuanyi != null && "1".equals(userQuanyi.getType())) { fenc = fenc - posOrder.getMdDiscountAmount(); String yhmcMessage = MessageUtils.message("no.posorder.md.yh.mc.messag"); yhmcMessage = StrUtil.format(yhmcMessage, posOrder.getMdYhName()); remark += yhmcMessage; String yhAmount = MessageUtils.message("no.posorder.md.yh.jiner.messag"); yhAmount = StrUtil.format(yhAmount, posOrder.getMdDiscountAmount()); remark += yhAmount; } } if (posOrder.getMdSalesName() != null && posOrder.getMdSalesReduction() != null) { fenc = fenc - posOrder.getMdSalesReduction(); String cxNameMessage = MessageUtils.message("no.posorder.md.cx.mc.messag"); cxNameMessage = StrUtil.format(cxNameMessage, posOrder.getMdSalesName()); remark += cxNameMessage; String cxAmount = MessageUtils.message("no.posorder.md.cx.jiner.messag"); cxAmount = StrUtil.format(cxAmount, posOrder.getMdSalesReduction()); remark += cxAmount; } billing.setIllustrate(remark); Double chouc = fenc * fcbili; Double shfc = fenc - chouc; billing.setUserId(posOrder.getShId()); billing.setDdId(String.valueOf(posOrder.getDdId())); billing.setType("0"); billing.setAmount(Double.valueOf(df.format(shfc))); billing.setDivvy(Double.valueOf(df.format(chouc))); billing.setState("0"); billing.setMdId(posOrder.getMdId()); billing.setUserType("1"); billing.setDivvyRate(fcbili); if (isStall) { billing.setStoreId(store.getId()); walletService.addStallBalance(store.getId(), BigDecimal.valueOf(billing.getAmount()), billing); } else { walletService.addBalance(billing.getUserId(), BigDecimal.valueOf(billing.getAmount()), billing); } } } ``` - [x] **Step 3: Commit** ```bash git add ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java git commit -m "feat: setSanghuBilling 支持摊位订单抽成逻辑" ```