Browse Source

feat(摊位): 新增摊位主编辑接口

- POST /stall/editOwner:归属校验、用户名/手机号唯一(排除自身)
- 密码留空不修改;仅更新 userName/nickName/telPhone/password,防止篡改身份字段
- 新增 4 个单测,stall.owner.* i18n key 补齐 5 个语言文件
qmj 6 days ago
parent
commit
eb52dfb978

+ 56 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/stall/StallController.java

@@ -181,6 +181,62 @@ public class StallController extends BaseController {
         return success(MessageUtils.message("no.success"), stallOwner);
     }
 
+    /**
+     * 编辑摊位主(夜市主只能编辑自己摊位下的摊位主;密码留空表示不修改)
+     */
+    @Anonymous
+    @Auth
+    @PostMapping("/editOwner")
+    @Transactional
+    public AjaxResult editStallOwner(@RequestHeader String token, @RequestBody InfoUser stallOwner) {
+        JwtUtil jwtUtil = new JwtUtil();
+        String userId = jwtUtil.getusid(token);
+        if (stallOwner.getUserId() == null) {
+            return error(MessageUtils.message("stall.owner.not.found"));
+        }
+        InfoUser existing = infoUserService.selectInfoUserByUserId(stallOwner.getUserId());
+        if (existing == null || !"4".equals(existing.getUserType()) || !"0".equals(existing.getDelFlag())) {
+            return error(MessageUtils.message("stall.owner.not.found"));
+        }
+        // 校验摊位属于当前夜市用户
+        PosStore store = posStoreService.selectPosStoreById(existing.getStoreId());
+        if (store == null || !Integer.valueOf(1).equals(store.getIsStall())
+                || !Long.valueOf(userId).equals(store.getUserId())) {
+            return error(MessageUtils.message("merchant.store.access.denied"));
+        }
+        if (stallOwner.getUserName() == null || stallOwner.getUserName().isEmpty()) {
+            return error(MessageUtils.message("stall.owner.name.blank"));
+        }
+        String telPhone = stallOwner.getTelPhone();
+        if (telPhone == null || telPhone.isBlank()) {
+            telPhone = stallOwner.getPhone();
+        }
+        if (telPhone == null || telPhone.isBlank()) {
+            return error(MessageUtils.message("no.user.phone.blank"));
+        }
+        // 检查用户名是否已被其他账号使用
+        InfoUser existName = infoUserService.lambdaQuery()
+                .eq(InfoUser::getUserName, stallOwner.getUserName())
+                .eq(InfoUser::getDelFlag, "0")
+                .ne(InfoUser::getUserId, existing.getUserId())
+                .one();
+        if (existName != null) {
+            return error(MessageUtils.message("stall.owner.name.exist"));
+        }
+        businessPhoneService.ensureUnique(telPhone, existing.getUserId());
+        // 只更新可编辑字段,密码留空表示不修改
+        InfoUser update = new InfoUser();
+        update.setUserId(existing.getUserId());
+        update.setUserName(stallOwner.getUserName());
+        update.setNickName(stallOwner.getNickName());
+        update.setTelPhone(telPhone);
+        if (stallOwner.getPassword() != null && !stallOwner.getPassword().isEmpty()) {
+            update.setPassword(stallOwner.getPassword());
+        }
+        infoUserService.updateInfoUser(update);
+        return success(MessageUtils.message("no.success"));
+    }
+
     /**
      * 获取摊位下的摊位主列表
      */

+ 4 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -448,3 +448,7 @@ stall.qualification.materials.required=Vui lòng bổ sung liên hệ, giấy t
 stall.qualification.approval.required=Hồ sơ quầy chưa được duyệt. Không được mở bán hoặc tạo đơn mới.
 stall.qualification.file.failed=Không thể xử lý tệp hồ sơ. Vui lòng thử lại.
 stall.qualification.file.invalid=Ảnh hồ sơ không hợp lệ, vượt giới hạn hoặc thuộc quầy khác
+# 685 编辑摊位主
+stall.owner.not.found=Không tìm thấy chủ quầy
+stall.owner.name.blank=Vui lòng nhập tên đăng nhập
+stall.owner.name.exist=Tên đăng nhập đã tồn tại

+ 4 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -434,3 +434,7 @@ stall.qualification.materials.required=Complete contact details, identity, stall
 stall.qualification.approval.required=Stall materials are not approved. Opening and new orders are not allowed.
 stall.qualification.file.failed=Failed to process qualification file. Please try again.
 stall.qualification.file.invalid=Invalid qualification image, limit exceeded or image belongs to another stall
+# 685 edit stall owner
+stall.owner.not.found=Stall owner not found
+stall.owner.name.blank=Username is required
+stall.owner.name.exist=Username already exists

+ 4 - 0
ruoyi-admin/src/main/resources/i18n/messages_th_TH.properties

@@ -435,3 +435,7 @@ stall.qualification.materials.required=โปรดกรอกให้คร
 stall.qualification.approval.required=เอกสารแผงลอยยังไม่ผ่านการตรวจสอบ จึงยังเปิดดำเนินการหรือสร้างคำสั่งซื้อใหม่ไม่ได้
 stall.qualification.file.failed=ประมวลผลไฟล์เอกสารตรวจสอบไม่สำเร็จ โปรดลองอีกครั้งภายหลัง
 stall.qualification.file.invalid=รูปภาพเอกสารไม่ถูกต้อง มีขนาดเกินกำหนด หรือไม่ใช่ของแผงลอยปัจจุบัน
+# 685 edit stall owner
+stall.owner.not.found=ไม่พบเจ้าของแผง
+stall.owner.name.blank=กรุณาระบุชื่อผู้ใช้
+stall.owner.name.exist=ชื่อผู้ใช้นี้มีอยู่แล้ว

+ 4 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -436,3 +436,7 @@ stall.qualification.materials.required=请补齐负责人姓名电话、证件
 stall.qualification.approval.required=摊位材料尚未审核通过,暂不能营业或创建新订单
 stall.qualification.file.failed=审核材料文件处理失败,请稍后重试
 stall.qualification.file.invalid=审核图片无效、超出限制或不属于当前摊位
+# 685 编辑摊位主
+stall.owner.not.found=摊位主不存在
+stall.owner.name.blank=用户名不能为空
+stall.owner.name.exist=用户名已存在

+ 4 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_TW.properties

@@ -435,3 +435,7 @@ stall.qualification.materials.required=請補齊負責人姓名電話、證件
 stall.qualification.approval.required=攤位材料尚未審核通過,暫不能營業或建立新訂單
 stall.qualification.file.failed=審核材料檔案處理失敗,請稍後重試
 stall.qualification.file.invalid=審核圖片無效、超過限制或不屬於目前攤位
+# 685 編輯攤位主
+stall.owner.not.found=攤位主不存在
+stall.owner.name.blank=用戶名不能為空
+stall.owner.name.exist=用戶名已存在

+ 180 - 0
ruoyi-admin/src/test/java/com/ruoyi/app/stall/StallControllerTest.java

@@ -73,6 +73,186 @@ class StallControllerTest {
         assertEquals("4", inserted.getValue().getUserType());
     }
 
+    @Test
+    void editStallOwnerUpdatesProfileFieldsOnly() throws Exception {
+        IPosStoreService posStoreService = mock(IPosStoreService.class);
+        IInfoUserService infoUserService = mock(IInfoUserService.class);
+        BusinessPhoneService businessPhoneService = mock(BusinessPhoneService.class);
+        @SuppressWarnings("unchecked")
+        LambdaQueryChainWrapper<InfoUser> nameQuery = mock(LambdaQueryChainWrapper.class);
+        when(infoUserService.lambdaQuery()).thenReturn(nameQuery);
+        when(nameQuery.eq(any(SFunction.class), any())).thenReturn(nameQuery);
+        when(nameQuery.ne(any(SFunction.class), any())).thenReturn(nameQuery);
+        when(nameQuery.one()).thenReturn(null);
+
+        InfoUser existing = new InfoUser();
+        existing.setUserId(940L);
+        existing.setUserType("4");
+        existing.setDelFlag("0");
+        existing.setStoreId(8L);
+        when(infoUserService.selectInfoUserByUserId(940L)).thenReturn(existing);
+        PosStore store = new PosStore();
+        store.setId(8);
+        store.setIsStall(1);
+        store.setUserId(10L);
+        when(posStoreService.selectPosStoreById(8L)).thenReturn(store);
+
+        StallController controller = new StallController();
+        ReflectionTestUtils.setField(controller, "posStoreService", posStoreService);
+        ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
+        ReflectionTestUtils.setField(controller, "businessPhoneService", businessPhoneService);
+
+        InfoUser request = new InfoUser();
+        request.setUserId(940L);
+        request.setUserName("hujiaobing1");
+        request.setNickName("胡椒饼");
+        request.setPhone("0999999");
+        request.setStoreId(999L);
+
+        AjaxResult result;
+        try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
+            messages.when(() -> MessageUtils.message("no.success")).thenReturn("ok");
+            result = controller.editStallOwner(
+                    JwtUtil.setToken("10", "night-market"), request);
+        }
+
+        assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
+        verify(businessPhoneService).ensureUnique("0999999", 940L);
+        ArgumentCaptor<InfoUser> updated = ArgumentCaptor.forClass(InfoUser.class);
+        verify(infoUserService).updateInfoUser(updated.capture());
+        assertEquals(Long.valueOf(940L), updated.getValue().getUserId());
+        assertEquals("hujiaobing1", updated.getValue().getUserName());
+        assertEquals("胡椒饼", updated.getValue().getNickName());
+        assertEquals("0999999", updated.getValue().getTelPhone());
+        assertNull(updated.getValue().getPassword());
+        assertNull(updated.getValue().getStoreId());
+        assertNull(updated.getValue().getUserType());
+        assertNull(updated.getValue().getStatus());
+    }
+
+    @Test
+    void editStallOwnerResetsPasswordWhenProvided() throws Exception {
+        IPosStoreService posStoreService = mock(IPosStoreService.class);
+        IInfoUserService infoUserService = mock(IInfoUserService.class);
+        BusinessPhoneService businessPhoneService = mock(BusinessPhoneService.class);
+        @SuppressWarnings("unchecked")
+        LambdaQueryChainWrapper<InfoUser> nameQuery = mock(LambdaQueryChainWrapper.class);
+        when(infoUserService.lambdaQuery()).thenReturn(nameQuery);
+        when(nameQuery.eq(any(SFunction.class), any())).thenReturn(nameQuery);
+        when(nameQuery.ne(any(SFunction.class), any())).thenReturn(nameQuery);
+        when(nameQuery.one()).thenReturn(null);
+        InfoUser existing = new InfoUser();
+        existing.setUserId(940L);
+        existing.setUserType("4");
+        existing.setDelFlag("0");
+        existing.setStoreId(8L);
+        when(infoUserService.selectInfoUserByUserId(940L)).thenReturn(existing);
+        PosStore store = new PosStore();
+        store.setId(8);
+        store.setIsStall(1);
+        store.setUserId(10L);
+        when(posStoreService.selectPosStoreById(8L)).thenReturn(store);
+
+        StallController controller = new StallController();
+        ReflectionTestUtils.setField(controller, "posStoreService", posStoreService);
+        ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
+        ReflectionTestUtils.setField(controller, "businessPhoneService", businessPhoneService);
+
+        InfoUser request = new InfoUser();
+        request.setUserId(940L);
+        request.setUserName("hujiaobing1");
+        request.setTelPhone("0999999");
+        request.setPassword("rsa-cipher");
+
+        try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
+            messages.when(() -> MessageUtils.message("no.success")).thenReturn("ok");
+            controller.editStallOwner(JwtUtil.setToken("10", "night-market"), request);
+        }
+
+        ArgumentCaptor<InfoUser> updated = ArgumentCaptor.forClass(InfoUser.class);
+        verify(infoUserService).updateInfoUser(updated.capture());
+        assertEquals("rsa-cipher", updated.getValue().getPassword());
+    }
+
+    @Test
+    void editStallOwnerRejectsNonOwningNightMarket() throws Exception {
+        IPosStoreService posStoreService = mock(IPosStoreService.class);
+        IInfoUserService infoUserService = mock(IInfoUserService.class);
+        InfoUser existing = new InfoUser();
+        existing.setUserId(940L);
+        existing.setUserType("4");
+        existing.setDelFlag("0");
+        existing.setStoreId(8L);
+        when(infoUserService.selectInfoUserByUserId(940L)).thenReturn(existing);
+        PosStore store = new PosStore();
+        store.setId(8);
+        store.setIsStall(1);
+        store.setUserId(10L);
+        when(posStoreService.selectPosStoreById(8L)).thenReturn(store);
+
+        StallController controller = new StallController();
+        ReflectionTestUtils.setField(controller, "posStoreService", posStoreService);
+        ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
+
+        InfoUser request = new InfoUser();
+        request.setUserId(940L);
+        request.setUserName("hujiaobing1");
+        request.setTelPhone("0999999");
+
+        AjaxResult result;
+        try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
+            messages.when(() -> MessageUtils.message("merchant.store.access.denied")).thenReturn("无权限");
+            result = controller.editStallOwner(JwtUtil.setToken("20", "other"), request);
+        }
+
+        assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
+        verify(infoUserService, never()).updateInfoUser(any(InfoUser.class));
+    }
+
+    @Test
+    void editStallOwnerRejectsDuplicateUserName() throws Exception {
+        IPosStoreService posStoreService = mock(IPosStoreService.class);
+        IInfoUserService infoUserService = mock(IInfoUserService.class);
+        BusinessPhoneService businessPhoneService = mock(BusinessPhoneService.class);
+        @SuppressWarnings("unchecked")
+        LambdaQueryChainWrapper<InfoUser> nameQuery = mock(LambdaQueryChainWrapper.class);
+        when(infoUserService.lambdaQuery()).thenReturn(nameQuery);
+        when(nameQuery.eq(any(SFunction.class), any())).thenReturn(nameQuery);
+        when(nameQuery.ne(any(SFunction.class), any())).thenReturn(nameQuery);
+        when(nameQuery.one()).thenReturn(new InfoUser());
+        InfoUser existing = new InfoUser();
+        existing.setUserId(940L);
+        existing.setUserType("4");
+        existing.setDelFlag("0");
+        existing.setStoreId(8L);
+        when(infoUserService.selectInfoUserByUserId(940L)).thenReturn(existing);
+        PosStore store = new PosStore();
+        store.setId(8);
+        store.setIsStall(1);
+        store.setUserId(10L);
+        when(posStoreService.selectPosStoreById(8L)).thenReturn(store);
+
+        StallController controller = new StallController();
+        ReflectionTestUtils.setField(controller, "posStoreService", posStoreService);
+        ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
+        ReflectionTestUtils.setField(controller, "businessPhoneService", businessPhoneService);
+
+        InfoUser request = new InfoUser();
+        request.setUserId(940L);
+        request.setUserName("taken-name");
+        request.setTelPhone("0999999");
+
+        AjaxResult result;
+        try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
+            messages.when(() -> MessageUtils.message("stall.owner.name.exist")).thenReturn("用户名已存在");
+            result = controller.editStallOwner(JwtUtil.setToken("10", "night-market"), request);
+        }
+
+        assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
+        verify(businessPhoneService, never()).ensureUnique(any(), any());
+        verify(infoUserService, never()).updateInfoUser(any(InfoUser.class));
+    }
+
     @Test
     void anotherNightMarketCannotEditStallOrPaymentRoutingFields() {
         IPosStoreService stores = mock(IPosStoreService.class);