| New file |
| | |
| | | # Partner Type Role Sync Implementation Plan |
| | | |
| | | > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| | | |
| | | **Goal:** Ensure partner accounts automatically receive the enabled system role matching their partner type whenever user permissions are loaded. |
| | | |
| | | **Architecture:** Add a focused system-service component that resolves the current partner type through the existing charging-pile Feign client and synchronizes only the three built-in type roles in `sys_user_role`. Call it before login/current-user permissions are calculated, then reload the user so its role collection and menu permissions reflect the synchronized database state. |
| | | |
| | | **Tech Stack:** Java 8, Spring Boot, MyBatis-Plus, OpenFeign, JUnit 5, Mockito |
| | | |
| | | ## Global Constraints |
| | | |
| | | - Partner type `1` maps to role key `independent_partner`. |
| | | - Partner type `2` maps to role key `site_owner`. |
| | | - Partner type `3` maps to role key `service_provider`. |
| | | - Preserve roles outside these three built-in type roles. |
| | | - Remove stale built-in type roles when the partner type changes. |
| | | - Do not modify unrelated existing workspace changes. |
| | | |
| | | --- |
| | | |
| | | ### Task 1: Partner type role synchronizer |
| | | |
| | | **Files:** |
| | | - Create: `ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/PartnerTypeRoleService.java` |
| | | - Test: `ruoyi-modules/ruoyi-system/src/test/java/com/ruoyi/system/service/PartnerTypeRoleServiceTest.java` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: `SiteClient.getPartnerR(Integer)`, `ISysRoleService`, and `ISysUserRoleService`. |
| | | - Produces: `void synchronize(SysUser user)`. |
| | | |
| | | - [x] **Step 1: Write failing tests for all three mappings, custom-role preservation, stale built-in-role removal, non-partner bypass, and missing-role failure.** |
| | | - [x] **Step 2: Run `mvn -pl ruoyi-modules/ruoyi-system -am -Dtest=PartnerTypeRoleServiceTest -Dsurefire.failIfNoSpecifiedTests=false test` and verify the new tests fail before implementation.** |
| | | - [x] **Step 3: Implement role-key mapping and idempotent `sys_user_role` synchronization.** |
| | | - [x] **Step 4: Run the focused test and verify it passes.** |
| | | |
| | | ### Task 2: Permission-loading integration |
| | | |
| | | **Files:** |
| | | - Modify: `ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java` |
| | | - Test: `ruoyi-modules/ruoyi-system/src/test/java/com/ruoyi/system/service/PartnerTypeRoleServiceTest.java` |
| | | |
| | | **Interfaces:** |
| | | - Consumes: `PartnerTypeRoleService.synchronize(SysUser)`. |
| | | - Produces: login and current-user responses whose roles and permissions include the matching partner-type role. |
| | | |
| | | - [x] **Step 1: Invoke synchronization in both `/user/info/{username}` and `/user/getInfo` before permission calculation.** |
| | | - [x] **Step 2: Reload `SysUser` after synchronization so `SysUser.roles` is current.** |
| | | - [x] **Step 3: Run the focused test and compile the system module.** |
| | | - [x] **Step 4: Review the diff to confirm unrelated working-tree changes remain untouched.** |
| | |
| | | package com.ruoyi.order.api.vo; |
| | | |
| | | import com.ruoyi.common.core.web.page.PageInfo; |
| | | import com.ruoyi.order.api.model.TChargingBill; |
| | | import com.ruoyi.order.api.model.TChargingOrder; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | |
| | | <groupId>com.ruoyi</groupId> |
| | | <artifactId>ruoyi-api-chargingPile</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-test</artifactId> |
| | | <scope>test</scope> |
| | | </dependency> |
| | | |
| | | </dependencies> |
| | | |
| | |
| | | </resources> |
| | | </build> |
| | | |
| | | </project> |
| | | </project> |
| | |
| | | @ApiOperation(value = "编辑角色", tags = {"管理后台-角色管理"}) |
| | | public AjaxResult roleUpdate(@Validated @RequestBody RoleUpdateDto dto) { |
| | | SysRole oldRole = roleService.selectRoleById(dto.getRoleId()); |
| | | if (isSystemBuiltinRole(oldRole)) { |
| | | return AjaxResult.error("系统内置角色不可编辑"); |
| | | if (oldRole != null && oldRole.isAdmin()) { |
| | | return AjaxResult.error("超级管理员角色不可编辑"); |
| | | } |
| | | SysRole role = new SysRole(); |
| | | role.setRoleName(dto.getRoleName()); |
| | |
| | | private ISysPermissionService permissionService; |
| | | |
| | | @Resource |
| | | private PartnerTypeRoleService partnerTypeRoleService; |
| | | |
| | | @Resource |
| | | private ISysConfigService configService; |
| | | |
| | | @Resource |
| | |
| | | if (StringUtils.isNull(sysUser)) { |
| | | return R.fail("用户名/密码错误"); |
| | | } |
| | | sysUser = synchronizePartnerTypeRole(sysUser); |
| | | // 角色集合 |
| | | Set<String> roles = permissionService.getRolePermission(sysUser); |
| | | // 权限集合 |
| | |
| | | @GetMapping("/getInfo") |
| | | public AjaxResult getInfo() { |
| | | SysUser user = userService.selectUserById(tokenService.getLoginUser().getUserid()); |
| | | user = synchronizePartnerTypeRole(user); |
| | | // 角色集合 |
| | | Set<String> roles = permissionService.getRolePermission(user); |
| | | // 权限集合 |
| | |
| | | return ajax; |
| | | } |
| | | |
| | | private SysUser synchronizePartnerTypeRole(SysUser user) { |
| | | partnerTypeRoleService.synchronize(user); |
| | | if (user != null && Integer.valueOf(2).equals(user.getRoleType()) && user.getObjectId() != null) { |
| | | return userService.selectUserById(user.getUserId()); |
| | | } |
| | | return user; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 发送重置密码验证码 |
| New file |
| | |
| | | package com.ruoyi.system.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.chargingPile.api.feignClient.SiteClient; |
| | | import com.ruoyi.chargingPile.api.model.Partner; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.exception.ServiceException; |
| | | import com.ruoyi.system.api.domain.SysRole; |
| | | import com.ruoyi.system.api.domain.SysUser; |
| | | import com.ruoyi.system.domain.SysUserRole; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * Synchronizes the built-in role granted to a partner account from its partner type. |
| | | */ |
| | | @Service |
| | | public class PartnerTypeRoleService { |
| | | |
| | | static final String INDEPENDENT_PARTNER_ROLE_KEY = "independent_partner"; |
| | | static final String SITE_OWNER_ROLE_KEY = "site_owner"; |
| | | static final String SERVICE_PROVIDER_ROLE_KEY = "service_provider"; |
| | | |
| | | private static final Set<String> PARTNER_ROLE_KEYS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( |
| | | INDEPENDENT_PARTNER_ROLE_KEY, |
| | | SITE_OWNER_ROLE_KEY, |
| | | SERVICE_PROVIDER_ROLE_KEY |
| | | ))); |
| | | |
| | | private static final Map<Integer, String> PARTNER_TYPE_ROLE_KEYS; |
| | | |
| | | static { |
| | | Map<Integer, String> roleKeys = new HashMap<>(); |
| | | roleKeys.put(1, INDEPENDENT_PARTNER_ROLE_KEY); |
| | | roleKeys.put(2, SITE_OWNER_ROLE_KEY); |
| | | roleKeys.put(3, SERVICE_PROVIDER_ROLE_KEY); |
| | | PARTNER_TYPE_ROLE_KEYS = Collections.unmodifiableMap(roleKeys); |
| | | } |
| | | |
| | | private final SiteClient siteClient; |
| | | private final ISysRoleService roleService; |
| | | private final ISysUserRoleService userRoleService; |
| | | |
| | | public PartnerTypeRoleService(SiteClient siteClient, |
| | | ISysRoleService roleService, |
| | | ISysUserRoleService userRoleService) { |
| | | this.siteClient = siteClient; |
| | | this.roleService = roleService; |
| | | this.userRoleService = userRoleService; |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void synchronize(SysUser user) { |
| | | if (!isPartnerAccount(user)) { |
| | | return; |
| | | } |
| | | |
| | | R<Partner> partnerResult = siteClient.getPartnerR(user.getObjectId()); |
| | | Partner partner = partnerResult == null ? null : partnerResult.getData(); |
| | | if (partner == null) { |
| | | throw new ServiceException("当前账号关联的合作商不存在"); |
| | | } |
| | | |
| | | String targetRoleKey = PARTNER_TYPE_ROLE_KEYS.get(partner.getPartnerType()); |
| | | if (targetRoleKey == null) { |
| | | throw new ServiceException("当前合作商类型无效"); |
| | | } |
| | | |
| | | List<SysRole> builtInRoles = roleService.list(Wrappers.lambdaQuery(SysRole.class) |
| | | .in(SysRole::getRoleKey, PARTNER_ROLE_KEYS) |
| | | .eq(SysRole::getDelFlag, "0") |
| | | .eq(SysRole::getStatus, "0")); |
| | | Map<String, SysRole> roleByKey = builtInRoles.stream() |
| | | .collect(Collectors.toMap(SysRole::getRoleKey, role -> role, (first, ignored) -> first)); |
| | | SysRole targetRole = roleByKey.get(targetRoleKey); |
| | | if (targetRole == null) { |
| | | throw new ServiceException("系统角色【" + targetRoleKey + "】不存在或已停用,请先配置角色"); |
| | | } |
| | | |
| | | Set<Long> builtInRoleIds = builtInRoles.stream() |
| | | .map(SysRole::getRoleId) |
| | | .collect(Collectors.toSet()); |
| | | List<SysUserRole> existingRoles = userRoleService.list(Wrappers.lambdaQuery(SysUserRole.class) |
| | | .eq(SysUserRole::getUserId, user.getUserId()) |
| | | .in(SysUserRole::getRoleId, builtInRoleIds)); |
| | | |
| | | boolean hasTargetRole = false; |
| | | for (SysUserRole existingRole : existingRoles) { |
| | | Long existingRoleId = existingRole.getRoleId(); |
| | | if (targetRole.getRoleId().equals(existingRoleId)) { |
| | | hasTargetRole = true; |
| | | } else if (builtInRoleIds.contains(existingRoleId)) { |
| | | userRoleService.remove(Wrappers.lambdaQuery(SysUserRole.class) |
| | | .eq(SysUserRole::getUserId, user.getUserId()) |
| | | .eq(SysUserRole::getRoleId, existingRoleId)); |
| | | } |
| | | } |
| | | |
| | | if (!hasTargetRole) { |
| | | SysUserRole userRole = new SysUserRole(); |
| | | userRole.setUserId(user.getUserId()); |
| | | userRole.setRoleId(targetRole.getRoleId()); |
| | | userRoleService.save(userRole); |
| | | } |
| | | } |
| | | |
| | | private boolean isPartnerAccount(SysUser user) { |
| | | return user != null |
| | | && user.getUserId() != null |
| | | && Integer.valueOf(2).equals(user.getRoleType()) |
| | | && user.getObjectId() != null; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.ruoyi.system.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import com.ruoyi.chargingPile.api.feignClient.SiteClient; |
| | | import com.ruoyi.chargingPile.api.model.Partner; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.exception.ServiceException; |
| | | import com.ruoyi.system.api.domain.SysRole; |
| | | import com.ruoyi.system.api.domain.SysUser; |
| | | import com.ruoyi.system.domain.SysUserRole; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.junit.jupiter.params.ParameterizedTest; |
| | | import org.junit.jupiter.params.provider.CsvSource; |
| | | import org.mockito.ArgumentCaptor; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.never; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class PartnerTypeRoleServiceTest { |
| | | |
| | | @ParameterizedTest |
| | | @CsvSource({ |
| | | "1, independent_partner, 11", |
| | | "2, site_owner, 12", |
| | | "3, service_provider, 13" |
| | | }) |
| | | void addsRoleMatchingPartnerType(Integer partnerType, String expectedRoleKey, Long expectedRoleId) { |
| | | Fixture fixture = new Fixture(partnerType); |
| | | fixture.stubBuiltInRoles(); |
| | | when(fixture.userRoleService.list(any(Wrapper.class))).thenReturn(Collections.emptyList()); |
| | | |
| | | fixture.service.synchronize(fixture.user); |
| | | |
| | | ArgumentCaptor<SysUserRole> captor = ArgumentCaptor.forClass(SysUserRole.class); |
| | | verify(fixture.userRoleService).save(captor.capture()); |
| | | assertEquals(expectedRoleId, captor.getValue().getRoleId()); |
| | | assertEquals(expectedRoleKey, fixture.roleKey(expectedRoleId)); |
| | | } |
| | | |
| | | @Test |
| | | void replacesStaleBuiltInRoleButKeepsUnrelatedCustomRole() { |
| | | Fixture fixture = new Fixture(2); |
| | | fixture.stubBuiltInRoles(); |
| | | SysUserRole oldTypeRole = userRole(100L, 11L); |
| | | SysUserRole customRole = userRole(100L, 99L); |
| | | when(fixture.userRoleService.list(any(Wrapper.class))) |
| | | .thenReturn(Arrays.asList(oldTypeRole, customRole)); |
| | | |
| | | fixture.service.synchronize(fixture.user); |
| | | |
| | | verify(fixture.userRoleService).remove(any(Wrapper.class)); |
| | | ArgumentCaptor<SysUserRole> captor = ArgumentCaptor.forClass(SysUserRole.class); |
| | | verify(fixture.userRoleService).save(captor.capture()); |
| | | assertEquals(Long.valueOf(12L), captor.getValue().getRoleId()); |
| | | } |
| | | |
| | | @Test |
| | | void doesNothingForPlatformUser() { |
| | | Fixture fixture = new Fixture(1); |
| | | fixture.user.setRoleType(1); |
| | | |
| | | fixture.service.synchronize(fixture.user); |
| | | |
| | | verify(fixture.siteClient, never()).getPartnerR(any()); |
| | | verify(fixture.roleService, never()).list(any(Wrapper.class)); |
| | | verify(fixture.userRoleService, never()).save(any(SysUserRole.class)); |
| | | } |
| | | |
| | | @Test |
| | | void failsClearlyWhenConfiguredRoleIsMissing() { |
| | | Fixture fixture = new Fixture(3); |
| | | when(fixture.roleService.list(any(Wrapper.class))) |
| | | .thenReturn(Arrays.asList(role(11L, "independent_partner"), role(12L, "site_owner"))); |
| | | |
| | | ServiceException exception = assertThrows(ServiceException.class, |
| | | () -> fixture.service.synchronize(fixture.user)); |
| | | |
| | | assertEquals("系统角色【service_provider】不存在或已停用,请先配置角色", exception.getMessage()); |
| | | verify(fixture.userRoleService, never()).save(any(SysUserRole.class)); |
| | | } |
| | | |
| | | private static SysRole role(Long roleId, String roleKey) { |
| | | SysRole role = new SysRole(); |
| | | role.setRoleId(roleId); |
| | | role.setRoleKey(roleKey); |
| | | return role; |
| | | } |
| | | |
| | | private static SysUserRole userRole(Long userId, Long roleId) { |
| | | SysUserRole userRole = new SysUserRole(); |
| | | userRole.setUserId(userId); |
| | | userRole.setRoleId(roleId); |
| | | return userRole; |
| | | } |
| | | |
| | | private static class Fixture { |
| | | private final SiteClient siteClient = mock(SiteClient.class); |
| | | private final ISysRoleService roleService = mock(ISysRoleService.class); |
| | | private final ISysUserRoleService userRoleService = mock(ISysUserRoleService.class); |
| | | private final PartnerTypeRoleService service = |
| | | new PartnerTypeRoleService(siteClient, roleService, userRoleService); |
| | | private final SysUser user = new SysUser(); |
| | | |
| | | private Fixture(Integer partnerType) { |
| | | user.setUserId(100L); |
| | | user.setRoleType(2); |
| | | user.setObjectId(715); |
| | | Partner partner = new Partner(); |
| | | partner.setId(715); |
| | | partner.setPartnerType(partnerType); |
| | | when(siteClient.getPartnerR(715)).thenReturn(R.ok(partner)); |
| | | } |
| | | |
| | | private void stubBuiltInRoles() { |
| | | when(roleService.list(any(Wrapper.class))).thenReturn(Arrays.asList( |
| | | role(11L, "independent_partner"), |
| | | role(12L, "site_owner"), |
| | | role(13L, "service_provider") |
| | | )); |
| | | } |
| | | |
| | | private String roleKey(Long roleId) { |
| | | if (Long.valueOf(11L).equals(roleId)) { |
| | | return "independent_partner"; |
| | | } |
| | | if (Long.valueOf(12L).equals(roleId)) { |
| | | return "site_owner"; |
| | | } |
| | | return "service_provider"; |
| | | } |
| | | } |
| | | } |
| | |
| | | List<GetSiteListDTO> getSiteList(PageInfo<GetSiteListDTO> pageInfo, @Param("item") GetSiteList siteList, @Param("ids") Set<Integer> ids); |
| | | |
| | | /** |
| | | * 获取下拉框使用的轻量站点列表。 |
| | | */ |
| | | List<GetSiteListDTO> getSiteOptions(@Param("ids") Set<Integer> ids, |
| | | @Param("partnerId") Integer partnerId); |
| | | |
| | | /** |
| | | * 获取站点分页列表 |
| | | * @return |
| | | */ |
| | |
| | | |
| | | @Override |
| | | public PageInfo<GetSiteListDTO> getSiteList1(Long userId) { |
| | | GetSiteList siteList = new GetSiteList(); |
| | | siteList.setPageCurr(1); |
| | | siteList.setPageSize(9999); |
| | | Set<Integer> ids = null; |
| | | //校验合作商权限 |
| | | SysUser sysUser = sysUserClient.getSysUser(userId).getData(); |
| | | Integer roleType = sysUser.getRoleType(); |
| | | Integer objectId = sysUser.getObjectId(); |
| | | R<Integer> admin = sysUserClient.isAdmin(tokenService.getLoginUser().getUserid()); |
| | | Integer partnerId = null; |
| | | |
| | | //合作商 |
| | | if(roleType == 2){ |
| | | ids = partnerService.authSite(objectId, SiteMenu.SITE_LIST); |
| | | partnerId = sysUser.getObjectId(); |
| | | }else{ |
| | | //非管理员需要根据角色和用户配置查询允许的站点数据 |
| | | R<Integer> admin = sysUserClient.isAdmin(userId); |
| | | if(admin.getData()!=1){ |
| | | ids = getAssignedSiteIds(sysUser.getUserId()); |
| | | if (ids.isEmpty()) { |
| | | ids.add(-1); |
| | | } |
| | | } |
| | | } |
| | | PageInfo<GetSiteListDTO> pageInfo = new PageInfo<>(siteList.getPageCurr(), siteList.getPageSize()); |
| | | List<GetSiteListDTO> list = this.baseMapper.getSiteList(pageInfo, siteList, ids); |
| | | for (GetSiteListDTO getSiteListDTO : list) { |
| | | Integer siteId = getSiteListDTO.getId(); |
| | | getSiteListDTO.setAuthAccountingStrategy(roleType == 1 ? true : partnerService.authMenu(objectId, siteId, SiteMenu.SITE_ACCOUNTING_STRATEGY)); |
| | | //平台账号才有此权限 |
| | | getSiteListDTO.setAuthPartner(roleType == 1 ? true : false); |
| | | getSiteListDTO.setAuthUpdate(roleType == 1 ? true : partnerService.authMenu(objectId, siteId, SiteMenu.SITE_UPDATE)); |
| | | getSiteListDTO.setAuthDelete(roleType == 1 ? true : partnerService.authMenu(objectId, siteId, SiteMenu.SITE_DELETE)); |
| | | } |
| | | return pageInfo.setRecords(list); |
| | | PageInfo<GetSiteListDTO> pageInfo = new PageInfo<>(1, 9999); |
| | | return pageInfo.setRecords(this.baseMapper.getSiteOptions(ids, partnerId)); |
| | | } |
| | | |
| | | private Set<Integer> getAssignedSiteIds(Long userId) { |
| | |
| | | order by a.sort desc, a.create_time desc |
| | | </select> |
| | | |
| | | <select id="getSiteOptions" resultType="com.ruoyi.chargingPile.api.dto.GetSiteListDTO"> |
| | | select a.id, |
| | | a.code, |
| | | a.name, |
| | | a.partner_id as partnerId, |
| | | b.partner_type as partnerType, |
| | | b.name as partnerName, |
| | | a.venue_partner_id as venuePartnerId, |
| | | vp.name as venuePartnerName, |
| | | a.site_type as siteType, |
| | | a.status |
| | | from t_site a |
| | | left join t_partner b on a.partner_id = b.id |
| | | left join t_partner vp on a.venue_partner_id = vp.id |
| | | where a.del_flag = 0 |
| | | <if test="null != partnerId"> |
| | | and (a.partner_id = #{partnerId} or a.venue_partner_id = #{partnerId}) |
| | | </if> |
| | | <if test="null == partnerId and null != ids and ids.size() > 0"> |
| | | and a.id in |
| | | <foreach collection="ids" item="siteId" open="(" separator="," close=")"> |
| | | #{siteId} |
| | | </foreach> |
| | | </if> |
| | | order by a.sort desc, a.create_time desc |
| | | </select> |
| | | |
| | | <select id="pageList" resultType="com.ruoyi.chargingPile.api.vo.SiteVO"> |
| | | select |
| | | ts.id, ts.partner_id, ts.code, ts.`name`, ts.site_type, ts.business_category, ts.member_discount_flag as memberDiscountFlag, ts.status, ts.construction_site, ts.img_url, |
| New file |
| | |
| | | package com.ruoyi.chargingPile.service.impl; |
| | | |
| | | import com.ruoyi.chargingPile.api.dto.GetSiteListDTO; |
| | | import com.ruoyi.chargingPile.mapper.SiteMapper; |
| | | import com.ruoyi.chargingPile.service.IPartnerService; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.system.api.domain.SysUser; |
| | | import com.ruoyi.system.api.feignClient.SysUserClient; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.util.Collections; |
| | | |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.ArgumentMatchers.isNull; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.never; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class SiteServiceImplTest { |
| | | |
| | | @Test |
| | | void partnerSiteListIncludesAllSitesRelatedToCurrentPartner() { |
| | | SiteServiceImpl service = new SiteServiceImpl(); |
| | | SiteMapper mapper = mock(SiteMapper.class); |
| | | IPartnerService partnerService = mock(IPartnerService.class); |
| | | SysUserClient sysUserClient = mock(SysUserClient.class); |
| | | ReflectionTestUtils.setField(service, "baseMapper", mapper); |
| | | ReflectionTestUtils.setField(service, "partnerService", partnerService); |
| | | ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient); |
| | | |
| | | SysUser sysUser = new SysUser(); |
| | | sysUser.setUserId(100L); |
| | | sysUser.setRoleType(2); |
| | | sysUser.setObjectId(715); |
| | | |
| | | when(sysUserClient.getSysUser(100L)).thenReturn(R.ok(sysUser)); |
| | | when(mapper.getSiteOptions(isNull(), eq(715))) |
| | | .thenReturn(Collections.<GetSiteListDTO>emptyList()); |
| | | |
| | | service.getSiteList1(100L); |
| | | |
| | | verify(mapper).getSiteOptions(isNull(), eq(715)); |
| | | verify(partnerService, never()).authSite(any(), any()); |
| | | verify(sysUserClient, never()).isAdmin(any()); |
| | | } |
| | | } |
| | |
| | | * @param startTime |
| | | * @param endTime |
| | | */ |
| | | List<Map<String, Object>> getAccountSettlementStatement(@Param("startTime") String startTime, @Param("endTime") String endTime, |
| | | @Param("rechargePaymentType") Integer rechargePaymentType); |
| | | List<Map<String, Object>> getAccountSettlementStatement(@Param("startTime") String startTime, |
| | | @Param("endTime") String endTime); |
| | | } |
| | |
| | | * @param endTime |
| | | * @return |
| | | */ |
| | | List<Map<String, Object>> getAccountSettlementStatement(String startTime, String endTime, Integer rechargePaymentType); |
| | | List<Map<String, Object>> getAccountSettlementStatement(String startTime, String endTime); |
| | | |
| | | /** |
| | | * 手动处理退款数据 |
| | |
| | | * @return |
| | | */ |
| | | @Override |
| | | public List<Map<String, Object>> getAccountSettlementStatement(String startTime, String endTime, Integer rechargePaymentType) { |
| | | return this.baseMapper.getAccountSettlementStatement(startTime, endTime, rechargePaymentType); |
| | | public List<Map<String, Object>> getAccountSettlementStatement(String startTime, String endTime) { |
| | | return this.baseMapper.getAccountSettlementStatement(startTime, endTime); |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | @Scheduled(fixedRate = 1000 * 60) |
| | | public void taskOneMinutes() { |
| | | if(null != port && port == 5400){ |
| | | if (isScheduledTaskPort(port)) { |
| | | List<TChargingOrder> upStatusChargingOrder = chargingOrderService.findUpStatusChargingOrder(); |
| | | log.info("定时任务执行,查询需要手动修改状态的订单数量:{}", upStatusChargingOrder.size()); |
| | | for (TChargingOrder order : upStatusChargingOrder) { |
| | |
| | | //十分钟循环执行的定时任务 |
| | | @Scheduled(fixedRate = 1000 * 60 * 10) |
| | | public void taskTenMinutes() { |
| | | if(null != port && port == 5400){ |
| | | if (isScheduledTaskPort(port)) { |
| | | List<TChargingOrder> failedStartupOrder = chargingOrderService.findFailedStartupOrder(); |
| | | log.info("定时任务执行,查询到启动失败的订单数量:{}", failedStartupOrder.size()); |
| | | for (TChargingOrder order : failedStartupOrder) { |
| | |
| | | //定时任务添加结算账单汇总数据 |
| | | @Scheduled(cron = "0 30 0 * * ?") |
| | | public void taskMonth() { |
| | | if(null != port && port == 5400){ |
| | | if (isScheduledTaskPort(port)) { |
| | | //推送三方引流平台数据 |
| | | pushOrderDrainagePlatform(); |
| | | //添加充电算账单汇总数据 |
| | |
| | | // 每个月最后一天23点执行的定时任务 |
| | | @Scheduled(cron = "0 0 23 * * ?") |
| | | public void taskLastDay() { |
| | | if(null != port && port == 5400){ |
| | | if (isScheduledTaskPort(port)) { |
| | | try { |
| | | int dayOfMonth = LocalDate.now().getDayOfMonth(); |
| | | int lastDayOfMonth = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth(); |
| | |
| | | public void onApplicationEvent(WebServerInitializedEvent event) { |
| | | port = event.getWebServer().getPort(); |
| | | System.out.println("端口号:" + port); |
| | | } |
| | | |
| | | static boolean isScheduledTaskPort(Integer serverPort) { |
| | | return Integer.valueOf(5400).equals(serverPort) || Integer.valueOf(9400).equals(serverPort); |
| | | } |
| | | |
| | | |
| | |
| | | accountSettlementStatement.setCode("JSD"+string+randomDigits); |
| | | |
| | | //根据业务订单查询汇总数据 |
| | | List<Map<String, Object>> mapList = chargingOrderService.getAccountSettlementStatement(startTime, endTime, i); |
| | | List<Map<String, Object>> mapList = chargingOrderService.getAccountSettlementStatement(startTime, endTime); |
| | | BigDecimal rechargeAmount = BigDecimal.ZERO; |
| | | BigDecimal refundAmount = BigDecimal.ZERO; |
| | | BigDecimal platformFee = BigDecimal.ZERO; |
| | |
| | | |
| | | |
| | | <select id="getAccountSettlementStatement" resultType="map"> |
| | | select |
| | | aa.site_id as siteId, |
| | | sum(aa.recharge_amount) as rechargeAmount, |
| | | sum(aa.refund_amount) as refundAmount, |
| | | sum(aa.serviceAmount) as serviceAmount |
| | | from ( |
| | | select |
| | | b.id as orderId, |
| | | b.site_id, |
| | | ifnull(b.payment_amount, 0) as recharge_amount, |
| | | ifnull(c.refund_amount, 0) as refund_amount, |
| | | sum(ifnull(a.period_service_price, 0)) as serviceAmount |
| | | from t_charging_order_accounting_strategy a |
| | | left join t_charging_order b on (a.charging_order_id = b.id) |
| | | left join ( |
| | | select charging_order_id, sum(ifnull(refund_amount, 0)) as refund_amount |
| | | from t_charging_order_refund |
| | | where refund_status = 2 |
| | | group by charging_order_id |
| | | ) c on b.id = c.charging_order_id |
| | | where b.`status` = 5 |
| | | and b.del_flag = 0 |
| | | and b.order_source = 1 |
| | | and b.source_name = 'ICBC' |
| | | and b.start_time between #{startTime} and #{endTime} |
| | | group by orderId, site_id, recharge_amount, refund_amount |
| | | ) as aa group by aa.site_id order by aa.site_id |
| | | SELECT |
| | | b.site_id AS siteId, |
| | | SUM(CASE |
| | | WHEN sr.charging_order_id IS NOT NULL THEN sr.paid_amount |
| | | ELSE IFNULL(b.payment_amount, 0) |
| | | END) AS rechargeAmount, |
| | | SUM(IFNULL(r.refund_amount, 0)) AS refundAmount, |
| | | SUM(CASE |
| | | WHEN sr.charging_order_id IS NOT NULL THEN sr.paid_service_amount |
| | | ELSE IFNULL(a.service_amount, 0) |
| | | END) AS serviceAmount |
| | | FROM t_charging_order b |
| | | LEFT JOIN ( |
| | | SELECT charging_order_id, |
| | | SUM(IFNULL(period_service_price, 0)) AS service_amount |
| | | FROM t_charging_order_accounting_strategy |
| | | GROUP BY charging_order_id |
| | | ) a ON a.charging_order_id = b.id |
| | | LEFT JOIN ( |
| | | SELECT charging_order_id, |
| | | SUM(IFNULL(channel_pay_amount, 0)) AS paid_amount, |
| | | SUM(IFNULL(channel_service_amount, 0)) AS paid_service_amount |
| | | FROM t_charging_order_share_record |
| | | WHERE pay_channel = 'ICBC' |
| | | AND del_flag = 0 |
| | | GROUP BY charging_order_id |
| | | ) sr ON sr.charging_order_id = b.id |
| | | LEFT JOIN ( |
| | | SELECT charging_order_id, |
| | | SUM(IFNULL(refund_amount, 0)) AS refund_amount |
| | | FROM t_charging_order_refund |
| | | WHERE refund_status = 2 |
| | | GROUP BY charging_order_id |
| | | ) r ON r.charging_order_id = b.id |
| | | WHERE b.status = 5 |
| | | AND b.del_flag = 0 |
| | | AND b.order_source = 1 |
| | | AND b.source_name = 'ICBC' |
| | | AND b.start_time BETWEEN #{startTime} AND #{endTime} |
| | | GROUP BY b.site_id |
| | | ORDER BY b.site_id |
| | | </select> |
| | | |
| | | <sql id="enterpriseChargingBaseWhere"> |
| New file |
| | |
| | | package com.ruoyi.order.mapper; |
| | | |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InputStreamReader; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | |
| | | class AccountSettlementStatementMapperTest { |
| | | |
| | | @Test |
| | | void usesIcbcPaidAmountsAndKeepsLegacyFieldsForCompatibility() throws IOException { |
| | | String statementMapper = resource("mapper/order/TAccountSettlementStatementMapper.xml"); |
| | | assertTrue(statementMapper.contains("AND account_type = 3")); |
| | | assertTrue(statementMapper.contains("SUM(platform_income)")); |
| | | assertTrue(statementMapper.contains("SUM(partner_income)")); |
| | | assertTrue(statementMapper.contains("platform_fee")); |
| | | assertTrue(statementMapper.contains("tripartite_commission")); |
| | | assertTrue(statementMapper.contains("recorded_amount")); |
| | | |
| | | String orderMapper = resource("mapper/order/TChargingOrderMapper.xml"); |
| | | String settlementQuery = orderMapper.substring( |
| | | orderMapper.indexOf("<select id=\"getAccountSettlementStatement\""), |
| | | orderMapper.indexOf("<sql id=\"enterpriseChargingBaseWhere\"")); |
| | | assertTrue(settlementQuery.contains("b.order_source = 1")); |
| | | assertTrue(settlementQuery.contains("b.source_name = 'ICBC'")); |
| | | assertTrue(settlementQuery.contains("pay_channel = 'ICBC'")); |
| | | assertTrue(settlementQuery.contains("channel_pay_amount")); |
| | | assertTrue(settlementQuery.contains("channel_service_amount")); |
| | | } |
| | | |
| | | private String resource(String path) throws IOException { |
| | | InputStream input = getClass().getClassLoader().getResourceAsStream(path); |
| | | assertNotNull(input); |
| | | try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) { |
| | | return reader.lines().collect(Collectors.joining("\n")); |
| | | } |
| | | } |
| | | } |
| | |
| | | class AccountSettlementIncomeTest { |
| | | |
| | | @Test |
| | | void enablesScheduledTasksOnLegacyAndOrderPorts() { |
| | | assertEquals(true, TaskUtil.isScheduledTaskPort(5400)); |
| | | assertEquals(true, TaskUtil.isScheduledTaskPort(9400)); |
| | | assertEquals(false, TaskUtil.isScheduledTaskPort(9500)); |
| | | assertEquals(false, TaskUtil.isScheduledTaskPort(null)); |
| | | } |
| | | |
| | | @Test |
| | | void calculatesPartnerAndPlatformIncomeForMiniProgramOrder() { |
| | | BigDecimal partnerIncome = TaskUtil.calculateAccountPartnerIncome( |
| | | new BigDecimal("10.00"), new BigDecimal("30"), false); |
| | |
| | | ALTER TABLE `t_account_settlement_statement` |
| | | ADD COLUMN `platform_income` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '平台收入' AFTER `recorded_amount`, |
| | | ADD COLUMN `partner_income` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '合作商收入' AFTER `platform_income`; |
| | | |