|
|
@@ -0,0 +1,234 @@
|
|
|
+# 夜市摊位抽成实现计划
|
|
|
+
|
|
|
+> **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
|
|
|
+<result property="storeId" column="store_id" />
|
|
|
+```
|
|
|
+
|
|
|
+- [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<UserWallet> 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<UserBilling>().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 支持摊位订单抽成逻辑"
|
|
|
+```
|