ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardAllocateDTO.java
@@ -21,7 +21,4 @@ @ApiModelProperty(value = "备注(可选,写入卡片余额变更流水)") private String remark; @ApiModelProperty(value = "操作密码", required = true) private String operationPassword; } ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardBalanceDTO.java
File was deleted ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TChargingCardController.java
@@ -3,7 +3,6 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.ruoyi.account.api.dto.ChargingCardAllocateDTO; import com.ruoyi.account.api.dto.ChargingCardAuthDTO; import com.ruoyi.account.api.dto.ChargingCardBalanceDTO; import com.ruoyi.account.api.dto.ChargingCardDeductDTO; import com.ruoyi.account.api.dto.ChargingCardExcelDTO; import com.ruoyi.account.api.dto.ChargingCardQueryDTO; @@ -76,13 +75,6 @@ @PutMapping("/status") public R<Boolean> status(@RequestBody ChargingCardStatusDTO dto) { return R.ok(chargingCardService.updateStatus(dto)); } @ApiOperation(value = "批量更新卡片余额") @RequiresPermissions(value = {"/chargingCard/balance"}, logical = Logical.OR) @PutMapping("/balance") public R<Boolean> balance(@RequestBody ChargingCardBalanceDTO dto) { return R.ok(chargingCardService.updateBalance(dto)); } @ApiOperation(value = "额度分配概览(公司总账户余额、可分配余额)") ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TChargingCardService.java
@@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.account.api.dto.ChargingCardAllocateDTO; import com.ruoyi.account.api.dto.ChargingCardAuthDTO; import com.ruoyi.account.api.dto.ChargingCardBalanceDTO; import com.ruoyi.account.api.dto.ChargingCardDeductDTO; import com.ruoyi.account.api.dto.ChargingCardExcelDTO; import com.ruoyi.account.api.dto.ChargingCardQueryDTO; @@ -28,8 +27,6 @@ boolean updateStatus(ChargingCardStatusDTO dto); boolean updateBalance(ChargingCardBalanceDTO dto); /** * 额度分配概览:公司可分配余额(available_balance)与总账户余额(累计充值到账)。 * 仅分配模式(account_mode=2)的公司可执行额度分配。 @@ -44,7 +41,7 @@ * 校验公司为启用且分配模式、卡片归属该公司且未在充电,禁止透支; * 每张卡片写入余额变更流水,失败时回滚并退还公司已扣金额。 * * @param dto 分配参数(公司、卡片明细、备注、操作密码) * @param dto 分配参数(公司、卡片明细、备注) * @return 分配后的公司概览(可分配余额为最新值) */ ChargingCardAllocateVO allocateQuota(ChargingCardAllocateDTO dto); ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java
@@ -8,7 +8,6 @@ import com.ruoyi.account.api.dto.ChargingCardAllocateDTO; import com.ruoyi.account.api.dto.ChargingCardAllocateItemDTO; import com.ruoyi.account.api.dto.ChargingCardAuthDTO; import com.ruoyi.account.api.dto.ChargingCardBalanceDTO; import com.ruoyi.account.api.dto.ChargingCardDeductDTO; import com.ruoyi.account.api.dto.ChargingCardExcelDTO; import com.ruoyi.account.api.dto.ChargingCardQueryDTO; @@ -22,14 +21,10 @@ import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.exception.ServiceException; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.security.service.TokenService; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.other.api.domain.TFleet; import com.ruoyi.other.api.domain.TGroupCompany; import com.ruoyi.other.api.feignClient.GroupCompanyClient; import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.feignClient.SysUserClient; import com.ruoyi.system.api.model.LoginUser; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.slf4j.Logger; @@ -64,7 +59,6 @@ private static final int ENABLED = 1; private static final int DISABLED = 2; private static final int BALANCE_ADD = 1; private static final int BALANCE_SUBTRACT = 2; /** 余额变动类型:3=刷卡订单充电结算扣款(V0.2 定稿,订单扣款专用) */ private static final int BALANCE_TYPE_ORDER_DEDUCT = 3; /** 充电锁定标记:1=充电中(业务锁定,管理操作互斥) */ @@ -87,12 +81,6 @@ private static final long LOCK_WAIT_SECONDS = 5L; /** 锁自动释放时间(秒),兜住异常未解锁的场景 */ private static final long LOCK_LEASE_SECONDS = 30L; @Autowired private TokenService tokenService; @Autowired private SysUserClient sysUserClient; @Autowired private TransactionTemplate transactionTemplate; @@ -262,31 +250,6 @@ } @Override @Transactional(rollbackFor = Exception.class) public boolean updateBalance(ChargingCardBalanceDTO dto) { validateBalanceRequest(dto); verifyOperationPassword(dto.getOperationPassword()); List<TChargingCard> cards = listByIds(dto.getIds()); if (cards.size() != new HashSet<>(dto.getIds()).size()) { throw new ServiceException("部分卡片不存在或已删除"); } assertNotChargingLocked(cards, "调整余额"); int updated = baseMapper.adjustBalance(dto.getIds(), dto.getAmount(), dto.getType(), MAX_AMOUNT, SecurityUtils.getUserId()); if (updated != cards.size()) { throw new ServiceException(dto.getType() == BALANCE_SUBTRACT ? "部分卡片正在充电中或余额不足,无法完成扣减" : "部分卡片正在充电中或余额超过上限,无法完成增加"); } for (TChargingCard card : cards) { baseMapper.insertBalanceRecord(card.getId(), dto.getType(), dto.getAmount(), StringUtils.isEmpty(dto.getRemark()) ? null : dto.getRemark().trim(), SecurityUtils.getUserId()); } return true; } @Override public ChargingCardAllocateVO getAllocateOverview(Integer companyId) { if (companyId == null) { throw new ServiceException("请选择公司"); @@ -302,7 +265,6 @@ @Transactional(rollbackFor = Exception.class) public ChargingCardAllocateVO allocateQuota(ChargingCardAllocateDTO dto) { Map<Long, BigDecimal> amounts = validateAllocateRequest(dto); verifyOperationPassword(dto.getOperationPassword()); Integer companyId = dto.getCompanyId(); // 公司必须存在、启用且为分配模式(共享模式不支持额度分配) TGroupCompany company = loadAllocationCompany(companyId); @@ -629,9 +591,6 @@ if (dto.getItems().size() > MAX_ALLOCATE_CARD_COUNT) { throw new ServiceException("单次最多分配" + MAX_ALLOCATE_CARD_COUNT + "张卡片"); } if (StringUtils.isEmpty(dto.getOperationPassword())) { throw new ServiceException("请输入操作密码"); } if (dto.getRemark() != null && dto.getRemark().trim().length() > 200) { throw new ServiceException("备注不能超过200个字符"); } @@ -921,22 +880,6 @@ return baseMapper.countByCardNo(cardNo, excludeId) > 0; } private void validateBalanceRequest(ChargingCardBalanceDTO dto) { if (dto == null || dto.getIds() == null || dto.getIds().isEmpty()) { throw new ServiceException("请选择需要更新余额的卡片"); } if (dto.getType() == null || (dto.getType() != BALANCE_ADD && dto.getType() != BALANCE_SUBTRACT)) { throw new ServiceException("余额调整类型错误"); } validateAmount(dto.getAmount(), false, "调整金额"); if (dto.getRemark() != null && dto.getRemark().trim().length() > 255) { throw new ServiceException("备注不能超过255个字符"); } if (StringUtils.isEmpty(dto.getOperationPassword())) { throw new ServiceException("请输入操作密码"); } } private void validateDeductRequest(ChargingCardDeductDTO dto) { if (dto == null) { throw new ServiceException("扣款参数不能为空"); @@ -978,21 +921,6 @@ } String prefix = row > 0 ? "第" + row + "行" : ""; throw new ServiceException(prefix + "启用状态错误"); } private void verifyOperationPassword(String operationPassword) { LoginUser loginUser = tokenService.getLoginUser(); if (loginUser == null || loginUser.getUserid() == null) { throw new ServiceException("登录状态已失效,请重新登录"); } R<SysUser> response = sysUserClient.getSysUser(loginUser.getUserid()); SysUser user = response == null ? null : response.getData(); if (user == null || StringUtils.isEmpty(user.getOperationPassword())) { throw new ServiceException("当前账号未设置操作密码"); } if (!SecurityUtils.matchesPassword(operationPassword, user.getOperationPassword())) { throw new ServiceException("操作密码错误"); } } private boolean isValidStatus(Integer status) { ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/controller/TChargingCardControllerTest.java
@@ -2,7 +2,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.account.api.dto.ChargingCardAuthDTO; import com.ruoyi.account.api.dto.ChargingCardBalanceDTO; import com.ruoyi.account.api.dto.ChargingCardDeductDTO; import com.ruoyi.account.api.dto.ChargingCardExcelDTO; import com.ruoyi.account.api.dto.ChargingCardQueryDTO; @@ -57,27 +56,23 @@ } @Test void addEditDeleteStatusAndBalanceDelegateToService() { void addEditDeleteAndStatusDelegateToService() { ChargingCardSaveDTO save = new ChargingCardSaveDTO(); ChargingCardStatusDTO status = new ChargingCardStatusDTO(); ChargingCardBalanceDTO balance = new ChargingCardBalanceDTO(); when(service.saveCard(save)).thenReturn(true); when(service.updateCard(save)).thenReturn(true); when(service.deleteCards(Collections.singletonList(1L))).thenReturn(true); when(service.updateStatus(status)).thenReturn(true); when(service.updateBalance(balance)).thenReturn(true); assertTrue(controller.add(save).getData()); assertTrue(controller.edit(save).getData()); assertTrue(controller.delete(Collections.singletonList(1L)).getData()); assertTrue(controller.status(status).getData()); assertTrue(controller.balance(balance).getData()); verify(service).saveCard(save); verify(service).updateCard(save); verify(service).deleteCards(Collections.singletonList(1L)); verify(service).updateStatus(status); verify(service).updateBalance(balance); } @Test ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TChargingCardServiceImplTest.java
@@ -6,7 +6,6 @@ import com.ruoyi.account.api.dto.ChargingCardAllocateDTO; import com.ruoyi.account.api.dto.ChargingCardAllocateItemDTO; import com.ruoyi.account.api.dto.ChargingCardAuthDTO; import com.ruoyi.account.api.dto.ChargingCardBalanceDTO; import com.ruoyi.account.api.dto.ChargingCardDeductDTO; import com.ruoyi.account.api.dto.ChargingCardExcelDTO; import com.ruoyi.account.api.dto.ChargingCardSaveDTO; @@ -18,14 +17,10 @@ import com.ruoyi.common.core.context.SecurityContextHolder; import com.ruoyi.common.core.exception.ServiceException; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.security.service.TokenService; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.other.api.domain.TFleet; import com.ruoyi.other.api.domain.TGroupCompany; import com.ruoyi.other.api.feignClient.GroupCompanyClient; import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.feignClient.SysUserClient; import com.ruoyi.system.api.model.LoginUser; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -67,8 +62,6 @@ class TChargingCardServiceImplTest { @Mock private TChargingCardMapper mapper; @Mock private TokenService tokenService; @Mock private SysUserClient sysUserClient; @Mock private TransactionTemplate transactionTemplate; @Mock private GroupCompanyClient groupCompanyClient; @Mock private RedissonClient redissonClient; @@ -79,8 +72,6 @@ void setUp() throws InterruptedException { service = spy(new TChargingCardServiceImpl()); ReflectionTestUtils.setField(service, "baseMapper", mapper); ReflectionTestUtils.setField(service, "tokenService", tokenService); ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient); ReflectionTestUtils.setField(service, "transactionTemplate", transactionTemplate); ReflectionTestUtils.setField(service, "groupCompanyClient", groupCompanyClient); ReflectionTestUtils.setField(service, "redissonClient", redissonClient); @@ -183,47 +174,6 @@ ServiceException exception = assertThrows(ServiceException.class, () -> service.updateStatus(dto)); assertEquals("卡片状态参数错误", exception.getMessage()); } @Test void updateBalanceAdjustsAndRecordsForAllCards() { LoginUser loginUser = new LoginUser(); loginUser.setUserid(123L); SysUser user = new SysUser(); user.setOperationPassword(com.ruoyi.common.security.utils.SecurityUtils.encryptPassword("pwd")); when(tokenService.getLoginUser()).thenReturn(loginUser); when(sysUserClient.getSysUser(123L)).thenReturn(R.ok(user)); TChargingCard first = new TChargingCard(); first.setId(1L); TChargingCard second = new TChargingCard(); second.setId(2L); when(mapper.selectBatchIds(anyList())).thenReturn(Arrays.asList(first, second)); when(mapper.adjustBalance(anyList(), any(BigDecimal.class), any(Integer.class), any(BigDecimal.class), any(Long.class))) .thenReturn(2); ChargingCardBalanceDTO dto = new ChargingCardBalanceDTO(); dto.setIds(Arrays.asList(1L, 2L)); dto.setType(1); dto.setAmount(new BigDecimal("10.00")); dto.setOperationPassword("pwd"); dto.setRemark(" test "); assertTrue(service.updateBalance(dto)); verify(mapper).insertBalanceRecord(1L, 1, new BigDecimal("10.00"), "test", 123L); verify(mapper).insertBalanceRecord(2L, 1, new BigDecimal("10.00"), "test", 123L); } @Test void updateBalanceRejectsMissingPasswordBeforeDatabaseUpdate() { ChargingCardBalanceDTO dto = new ChargingCardBalanceDTO(); dto.setIds(Collections.singletonList(1L)); dto.setType(1); dto.setAmount(BigDecimal.ONE); ServiceException exception = assertThrows(ServiceException.class, () -> service.updateBalance(dto)); assertEquals("请输入操作密码", exception.getMessage()); verify(mapper, never()).adjustBalance(anyList(), any(), any(), any(), any()); } @Test @@ -372,31 +322,6 @@ assertTrue(exception.getMessage().contains("正在充电中")); verify(mapper, never()).updateById(any()); } @Test void updateBalanceRejectsCardInChargingLock() { LoginUser loginUser = new LoginUser(); loginUser.setUserid(123L); SysUser user = new SysUser(); user.setOperationPassword(SecurityUtils.encryptPassword("pwd")); when(tokenService.getLoginUser()).thenReturn(loginUser); when(sysUserClient.getSysUser(123L)).thenReturn(R.ok(user)); TChargingCard locked = new TChargingCard(); locked.setId(1L); locked.setCardNo("CARD-1"); locked.setChargingFlag(1); when(mapper.selectBatchIds(anyList())).thenReturn(Collections.singletonList(locked)); ChargingCardBalanceDTO dto = new ChargingCardBalanceDTO(); dto.setIds(Collections.singletonList(1L)); dto.setType(2); dto.setAmount(new BigDecimal("10.00")); dto.setOperationPassword("pwd"); ServiceException exception = assertThrows(ServiceException.class, () -> service.updateBalance(dto)); assertTrue(exception.getMessage().contains("正在充电中")); verify(mapper, never()).adjustBalance(anyList(), any(), any(), any(), any()); } // ==================== 卡片所属公司/车队 + 初始分配额度 ==================== @@ -853,7 +778,6 @@ @Test void allocateQuotaAddsCardBalanceAndDeductsCompanyBalance() { mockOperationPassword(); when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司"))); TChargingCard first = allocateCard(1L, 9, "CARD-1", "10.00"); TChargingCard second = allocateCard(2L, 9, "CARD-2", "20.00"); @@ -864,7 +788,7 @@ .thenReturn(1); when(groupCompanyClient.deductBalance(9, new BigDecimal("30.00"))).thenReturn(R.ok(new BigDecimal("970.00"))); ChargingCardAllocateVO vo = service.allocateQuota(allocateDto(9, "pwd", ChargingCardAllocateVO vo = service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00"), allocateItem(2L, "20.00"))); assertEquals(new BigDecimal("970.00"), vo.getAvailableBalance()); @@ -877,13 +801,12 @@ @Test void allocateQuotaRejectsSharedModeCompany() { mockOperationPassword(); TGroupCompany shared = allocationCompany(9, "共享公司"); shared.setAccountMode(1); when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(shared)); ServiceException exception = assertThrows(ServiceException.class, () -> service.allocateQuota(allocateDto(9, "pwd", allocateItem(1L, "10.00")))); () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00")))); assertEquals("该公司为共享模式,不支持额度分配", exception.getMessage()); verify(groupCompanyClient, never()).deductBalance(any(Integer.class), any(BigDecimal.class)); @@ -891,13 +814,12 @@ @Test void allocateQuotaRejectsCardOfAnotherCompany() { mockOperationPassword(); when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司"))); TChargingCard foreign = allocateCard(1L, 8, "CARD-1", "10.00"); doReturn(Collections.singletonList(foreign)).when(service).listByIds(anyCollection()); ServiceException exception = assertThrows(ServiceException.class, () -> service.allocateQuota(allocateDto(9, "pwd", allocateItem(1L, "10.00")))); () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00")))); assertTrue(exception.getMessage().contains("不属于所选公司")); verify(groupCompanyClient, never()).deductBalance(any(Integer.class), any(BigDecimal.class)); @@ -905,7 +827,6 @@ @Test void allocateQuotaRefundsCompanyBalanceWhenCardUpdateFails() { mockOperationPassword(); when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司"))); TChargingCard card = allocateCard(1L, 9, "CARD-1", "10.00"); doReturn(Collections.singletonList(card)).when(service).listByIds(anyCollection()); @@ -915,7 +836,7 @@ any(Long.class))).thenReturn(0); ServiceException exception = assertThrows(ServiceException.class, () -> service.allocateQuota(allocateDto(9, "pwd", allocateItem(1L, "10.00")))); () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00")))); assertTrue(exception.getMessage().contains("余额分配失败")); verify(groupCompanyClient).refundBalance(9, new BigDecimal("10.00")); @@ -923,14 +844,13 @@ @Test void allocateQuotaReportsInsufficientCompanyBalance() { mockOperationPassword(); when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司"))); TChargingCard card = allocateCard(1L, 9, "CARD-1", "0.00"); doReturn(Collections.singletonList(card)).when(service).listByIds(anyCollection()); when(groupCompanyClient.deductBalance(9, new BigDecimal("500.00"))).thenReturn(R.fail("公司可用余额不足")); ServiceException exception = assertThrows(ServiceException.class, () -> service.allocateQuota(allocateDto(9, "pwd", allocateItem(1L, "500.00")))); () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "500.00")))); assertEquals("公司可分配余额不足,无法完成本次分配", exception.getMessage()); verify(mapper, never()).adjustBalance(anyList(), any(BigDecimal.class), any(Integer.class), @@ -940,7 +860,7 @@ @Test void allocateQuotaRejectsInvalidAmount() { ServiceException exception = assertThrows(ServiceException.class, () -> service.allocateQuota(allocateDto(9, "pwd", allocateItem(1L, "0")))); () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "0")))); assertEquals("分配金额必须大于0", exception.getMessage()); verify(groupCompanyClient, never()).getCompanyById(any(Integer.class)); @@ -949,43 +869,23 @@ @Test void allocateQuotaRejectsDuplicateCards() { ServiceException exception = assertThrows(ServiceException.class, () -> service.allocateQuota(allocateDto(9, "pwd", allocateItem(1L, "10.00"), allocateItem(1L, "5.00")))); () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00"), allocateItem(1L, "5.00")))); assertEquals("分配明细中存在重复卡片", exception.getMessage()); } @Test void allocateQuotaRejectsMissingPassword() { ServiceException exception = assertThrows(ServiceException.class, () -> service.allocateQuota(allocateDto(9, null, allocateItem(1L, "10.00")))); assertEquals("请输入操作密码", exception.getMessage()); verify(mapper, never()).adjustBalance(anyList(), any(BigDecimal.class), any(Integer.class), any(BigDecimal.class), any(Long.class)); } @Test void allocateQuotaRejectsChargingLockedCard() { mockOperationPassword(); when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司"))); TChargingCard card = allocateCard(1L, 9, "CARD-1", "10.00"); card.setChargingFlag(1); doReturn(Collections.singletonList(card)).when(service).listByIds(anyCollection()); ServiceException exception = assertThrows(ServiceException.class, () -> service.allocateQuota(allocateDto(9, "pwd", allocateItem(1L, "10.00")))); () -> service.allocateQuota(allocateDto(9, allocateItem(1L, "10.00")))); assertTrue(exception.getMessage().contains("充电中")); verify(groupCompanyClient, never()).deductBalance(any(Integer.class), any(BigDecimal.class)); } private void mockOperationPassword() { LoginUser loginUser = new LoginUser(); loginUser.setUserid(123L); SysUser user = new SysUser(); user.setOperationPassword(SecurityUtils.encryptPassword("pwd")); when(tokenService.getLoginUser()).thenReturn(loginUser); when(sysUserClient.getSysUser(123L)).thenReturn(R.ok(user)); } private TChargingCard allocateCard(Long id, Integer companyId, String cardNo, String balance) { @@ -1006,11 +906,10 @@ return item; } private ChargingCardAllocateDTO allocateDto(Integer companyId, String password, ChargingCardAllocateItemDTO... items) { private ChargingCardAllocateDTO allocateDto(Integer companyId, ChargingCardAllocateItemDTO... items) { ChargingCardAllocateDTO dto = new ChargingCardAllocateDTO(); dto.setCompanyId(companyId); dto.setItems(Arrays.asList(items)); dto.setOperationPassword(password); return dto; } } sql/20260926_统一执行清单.md
@@ -97,3 +97,21 @@ - 幂等性:`ALTER TABLE ... MODIFY COLUMN` 语句本身可重复执行,本次连续执行两次均 EXIT=0、结构不变 - 代码侧配套(同批改动):后端 save 在共享模式忽略该字段并落库 NULL、分配模式仍必填;detail 在共享模式不回显;前端弹窗共享模式隐藏该输入项且提交 null - 注意:`TCompanyBalanceWarning.subWarnAmount` 已加 `@TableField(updateStrategy = FieldStrategy.IGNORED)`,否则 MyBatis-Plus 默认策略会忽略 null,共享模式切换后旧子账户预警线不会被清空(该列可空是此策略生效的前提) ## 九、追加执行:公司登录账号/密码字段 + 内置公司角色(2026-09-28 11:06 ~ 11:16,精确) > 背景:`TGroupCompany` 实体(ruoyi-api-other)新增 `account`/`password` 字段后,公司列表报 `Unknown column 'account' in 'field list'`(`TGroupCompanyMapper` 全字段 SELECT)。 > > 脚本 `20260928_company_account_role.sql` 此前未生效,两层原因:① 原脚本用 `SET @变量 := (SELECT ...)` + `PREPARE/EXECUTE` 条件化 DDL,Mycat 2.0 不支持用户变量 → 静默失效(与 20260901 VIN 脚本同坑);② 脚本缺 `USE` 选库语句,`-e "source ..."` 直接报 `ERROR 1105 (HY000): please use schema`。 | 脚本 | 结果 | |---|---| | `20260928_company_account_role.sql`(本次改造为 Mycat 兼容) | **已执行生效**:`t_group_company` 补 `account`/`password` 列;`sys_role` 新增 `role_id=74`「公司/company」(data_scope=5);`sys_user.roleType` 注释更新为含 `5=公司` | - 改造内容:第 1 部分删除 `SET @变量`/`PREPARE`,改为两条直接 `ALTER TABLE ... ADD COLUMN ... AFTER`(非幂等,重复执行报 `Duplicate column name` 属预期,脚本头已注明);文件开头补 `USE charging_pile_other;` 语句;第 2、3 部分(`sys_role` 幂等 `INSERT ... WHERE NOT EXISTS` / `UPDATE`、`sys_user` MODIFY 注释)写法保持,均无用户变量。 - 执行环境:`221.182.45.100:8066`(Mycat 2.0,`--default-character-set=utf8mb4`,客户端指定默认库 `charging_pile_other`,脚本复制为 `C:\...\Temp\company_account_role.sql` 后 `-e "source ..."`,EXIT=0) - 回查证据:`information_schema.COLUMNS` 显示 `account varchar(255)` / `password varchar(255)` 位于 `contact_phone` 之后、中文注释无乱码;`sys_role` 新增行 `role_id=74, role_name=公司, role_key=company, data_scope=5, status=0, del_flag=0`;`sys_user.roleType` 注释=「角色类型 1=平台 2=合作商 3=门店 4=修理厂 5=公司」;**原报错 SQL 原样重放成功**(`SELECT ... account,password ... FROM t_group_company WHERE del_flag=0 ORDER BY id DESC LIMIT 5` 返回 1 行公司数据) - 幂等性:`sys_role`/`sys_user` 段抽取为独立脚本连续执行两次,均 EXIT=0 且 `role_key='company'` 行数恒为 1(全表角色 21 行,无重复插入);`ALTER` 段以 `--force` 重跑报 `Duplicate column name 'account'`(Mycat 遇错即停后续语句,未产生副作用) - 回归测试:后端 `mvn -B -pl ruoyi-service/ruoyi-other -am test-compile` SUCCESS(18 个测试源文件编译通过);surefire 2.22.2 定向 `TGroupCompanyServiceImplTest` **6/6 通过**(0 失败 0 错误) - 代码侧配套(无需改动):`TGroupCompanyServiceImpl` 新增/编辑/删除公司与 `sys_user`(roleType=5、role_key=company)的同步逻辑、前端公司页「登录账号」必填项此前已实现,本次仅补齐数据库结构。 - 待办提醒:现有 1 行公司(喜望公司)`account`/`password` 为 NULL;前端编辑公司时「登录账号」为只读禁用态,补录账号需业务侧确认放开前端或后台补数据;`sys_role` 新角色的菜单权限需在「角色管理」中配置(脚本不涉及)。 sql/20260928_company_account_role.sql
@@ -3,47 +3,20 @@ -- 创建日期: 2026-09-28 -- 说明: -- 1. t_group_company 新增 account/password(存储前端 HMAC-MD5 加密后的登录密码) -- 注意:Mycat 2.0 不支持 SET @变量/PREPARE 条件化 DDL(会静默失效),故此处直接 ALTER; -- 执行前请先只读确认列不存在(information_schema.COLUMNS),重复执行报 Duplicate column name 属预期 -- 2. sys_role 幂等补充内置角色 role_key=company(所有公司账号共享,菜单在角色管理配置) -- 3. sys_user.roleType 注释统一为 1=平台 2=合作商 3=门店 4=修理厂 5=公司 -- 库: charging_pile_other -- ------------------------------------------ USE `charging_pile_other`; -- 1. t_group_company 增加登录账号/密码字段(幂等) SET @gc_account_exists := ( SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_group_company' AND COLUMN_NAME = 'account' ); -- 1. t_group_company 增加登录账号/密码字段(已只读确认当前库缺列;Mycat 2.0 不支持条件化 DDL) ALTER TABLE `t_group_company` ADD COLUMN `account` varchar(255) NULL DEFAULT NULL COMMENT '登录账号' AFTER `contact_phone`; SET @gc_account_ddl := IF( @gc_account_exists = 0, 'ALTER TABLE `t_group_company` ADD COLUMN `account` varchar(255) NULL DEFAULT NULL COMMENT ''登录账号'' AFTER `contact_phone`', 'SELECT 1' ); PREPARE gc_account_stmt FROM @gc_account_ddl; EXECUTE gc_account_stmt; DEALLOCATE PREPARE gc_account_stmt; SET @gc_password_exists := ( SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_group_company' AND COLUMN_NAME = 'password' ); SET @gc_password_ddl := IF( @gc_password_exists = 0, 'ALTER TABLE `t_group_company` ADD COLUMN `password` varchar(255) NULL DEFAULT NULL COMMENT ''登录密码(前端HMAC-MD5加密值)'' AFTER `account`', 'SELECT 1' ); PREPARE gc_password_stmt FROM @gc_password_ddl; EXECUTE gc_password_stmt; DEALLOCATE PREPARE gc_password_stmt; ALTER TABLE `t_group_company` ADD COLUMN `password` varchar(255) NULL DEFAULT NULL COMMENT '登录密码(前端HMAC-MD5加密值)' AFTER `account`; -- 2. 内置公司角色(幂等) INSERT INTO sys_role (role_name, role_key, data_scope, status, del_flag, create_by, create_time, remark) sql/20260928_delete_card_balance_menu.sql
New file @@ -0,0 +1,15 @@ -- ============================================================= -- 2026-09-28 删除卡片管理-批量修改余额按钮权限 -- 目标库:charging_pile_account -- 说明:批量修改余额功能下线(前端按钮 + 后端 /balance 接口已删除), -- 幂等删除对应菜单按钮及角色关联。 -- ============================================================= -- 1. 先删除角色与菜单的关联(仅限该按钮) DELETE srm FROM sys_role_menu srm JOIN sys_menu m ON srm.menu_id = m.menu_id WHERE m.perms = '/chargingCard/balance'; -- 2. 再删除菜单按钮本身(幂等:不存在则影响 0 行) DELETE FROM sys_menu WHERE perms = '/chargingCard/balance';