puzhibing
2026-09-28 d4bde58ab669a434bb0c96f391153ec66eedd5b7
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TGroupCompanyServiceImpl.java
@@ -2,22 +2,41 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.api.feignClient.AppUserWhitelistClient;
import com.ruoyi.account.api.feignClient.ChargingCardClient;
import com.ruoyi.account.api.feignClient.VinManagementClient;
import com.ruoyi.account.api.model.TAppUserWhitelist;
import com.ruoyi.account.api.model.TChargingCard;
import com.ruoyi.account.api.model.TVinManagement;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.other.api.domain.TFleet;
import com.ruoyi.other.api.domain.TGroupCompany;
import com.ruoyi.other.api.model.TCompanyRechargeRecord;
import com.ruoyi.other.api.vo.CompanyChargingUserVO;
import com.ruoyi.other.mapper.TCompanyRechargeRecordMapper;
import com.ruoyi.other.mapper.TFleetMapper;
import com.ruoyi.other.mapper.TGroupCompanyMapper;
import com.ruoyi.other.service.TGroupCompanyService;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.feignClient.SysRoleClient;
import com.ruoyi.system.api.feignClient.SysUserClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * <p>
@@ -33,6 +52,33 @@
    /** 金额保留小数位(与 t_company_recharge_record.arrival_amount 一致) */
    private static final int AMOUNT_SCALE = 2;
    /** 公司账号角色标识(内置角色 role_key=company) */
    private static final String COMPANY_ROLE_KEY = "company";
    /** 公司账号角色类型(sys_user.roleType=5) */
    private static final int COMPANY_ROLE_TYPE = 5;
    /** 账户模式:共享模式(新增企业未指定时按此建档) */
    private static final int ACCOUNT_MODE_SHARED = 1;
    /** 账户模式:分配模式 */
    private static final int ACCOUNT_MODE_ALLOCATION = 2;
    /** 充电用户类型:VIN码 */
    private static final int USER_TYPE_VIN = 1;
    /** 充电用户类型:刷卡 */
    private static final int USER_TYPE_CARD = 2;
    /** 充电用户类型:白名单 */
    private static final int USER_TYPE_WHITELIST = 3;
    /** 账户状态:正常 */
    private static final int ACCOUNT_STATUS_NORMAL = 1;
    /** 账户状态:欠款 */
    private static final int ACCOUNT_STATUS_OVERDUE = 2;
    /**
     * 车队 Mapper。
     * 此处注入 Mapper 而非 TFleetService:TFleetServiceImpl 已依赖 TGroupCompanyService,
@@ -44,6 +90,26 @@
    /** 公司充值记录 Mapper */
    @Resource
    private TCompanyRechargeRecordMapper companyRechargeRecordMapper;
    /** 系统用户远程客户端(公司登录账号同步) */
    @Resource
    private SysUserClient sysUserClient;
    /** 系统角色远程客户端(公司默认角色查询) */
    @Resource
    private SysRoleClient sysRoleClient;
    /** VIN管理远程客户端(公司详情-充电用户信息聚合使用) */
    @Resource
    private VinManagementClient vinManagementClient;
    /** 充电卡远程客户端(公司详情-充电用户信息聚合使用) */
    @Resource
    private ChargingCardClient chargingCardClient;
    /** 白名单远程客户端(公司详情-充电用户信息聚合使用) */
    @Resource
    private AppUserWhitelistClient appUserWhitelistClient;
    @Override
    public TGroupCompany getDetail(Integer id) {
@@ -61,6 +127,168 @@
        // 累计充值到账金额:作为公司总账户余额展示(额度分配弹窗使用),仅覆盖响应对象,不落库
        company.setTotalRechargeAmount(sumRechargeAmount(id));
        return company;
    }
    @Override
    public TGroupCompany getDetailWithChargingUsers(Integer id) {
        TGroupCompany company = getDetail(id);
        if (company == null) {
            return null;
        }
        company.setChargingUsers(buildChargingUsers(company));
        return company;
    }
    /**
     * 聚合公司充电用户信息:VIN码、刷卡、白名单三类数据按统一结构返回。
     * 仅公司详情使用;remoteGet 远程调用保持 getDetail(不聚合),避免跨服务循环调用。
     */
    private List<CompanyChargingUserVO> buildChargingUsers(TGroupCompany company) {
        Integer companyId = company.getId();
        Integer accountMode = company.getAccountMode();
        List<CompanyChargingUserVO> result = new ArrayList<>();
        List<TVinManagement> vins = fetchVins(companyId);
        List<TChargingCard> cards = fetchCards(companyId);
        List<TAppUserWhitelist> whitelists = fetchWhitelists(companyId);
        List<Integer> fleetIds = new ArrayList<>();
        for (TVinManagement vin : vins) {
            if (vin.getFleetId() != null) {
                fleetIds.add(vin.getFleetId());
            }
        }
        for (TChargingCard card : cards) {
            if (card.getFleetId() != null) {
                fleetIds.add(card.getFleetId());
            }
        }
        for (TAppUserWhitelist whitelist : whitelists) {
            if (whitelist.getFleetId() != null) {
                fleetIds.add(whitelist.getFleetId());
            }
        }
        Map<Integer, String> fleetNameMap = loadFleetNameMap(fleetIds);
        String companyName = company.getName();
        BigDecimal companyBalance = company.getAvailableBalance();
        for (TVinManagement vin : vins) {
            result.add(buildRow(USER_TYPE_VIN + "-" + vin.getId(), USER_TYPE_VIN, vin.getVin(),
                    companyName, fleetNameMap.get(vin.getFleetId()), accountMode,
                    vin.getBalance(), companyBalance, vin.getStatus()));
        }
        for (TChargingCard card : cards) {
            result.add(buildRow(USER_TYPE_CARD + "-" + card.getId(), USER_TYPE_CARD, card.getCardNo(),
                    companyName, fleetNameMap.get(card.getFleetId()), accountMode,
                    card.getBalance(), companyBalance, card.getStatus()));
        }
        for (TAppUserWhitelist whitelist : whitelists) {
            result.add(buildRow(USER_TYPE_WHITELIST + "-" + whitelist.getId(), USER_TYPE_WHITELIST, whitelist.getPhone(),
                    companyName, fleetNameMap.get(whitelist.getFleetId()), accountMode,
                    whitelist.getBalance(), companyBalance, whitelist.getStatus()));
        }
        return result;
    }
    /**
     * 组装单行充电用户信息。可用余额:分配模式取子账户余额/初始分配额度,共享模式取公司可用余额。
     * 超额/账户状态按余额正负推算:余额为负时超额=|余额|、账户状态=欠款;否则无超额、状态正常。
     */
    private CompanyChargingUserVO buildRow(String rowKey, Integer userType, String userInfo,
                                           String companyName, String fleetName, Integer accountMode,
                                           BigDecimal accountBalance, BigDecimal companyBalance, Integer status) {
        CompanyChargingUserVO vo = new CompanyChargingUserVO();
        vo.setRowKey(rowKey);
        vo.setUserType(userType);
        vo.setUserInfo(userInfo);
        vo.setCompanyName(companyName);
        vo.setFleetName(fleetName);
        vo.setAccountMode(accountMode);
        vo.setBalance(resolveBalance(accountMode, accountBalance, companyBalance));
        vo.setOverrun(null);
        vo.setAccountStatus(ACCOUNT_STATUS_NORMAL);
        BigDecimal balance = vo.getBalance();
        if (balance != null && balance.compareTo(BigDecimal.ZERO) < 0) {
            vo.setOverrun(balance.abs().setScale(AMOUNT_SCALE, RoundingMode.HALF_UP));
            vo.setAccountStatus(ACCOUNT_STATUS_OVERDUE);
        }
        vo.setStatus(status);
        return vo;
    }
    /**
     * 计算可用余额:分配模式=子账户余额/初始分配额度,共享模式=公司可用余额
     */
    private BigDecimal resolveBalance(Integer accountMode, BigDecimal accountBalance, BigDecimal companyBalance) {
        if (Integer.valueOf(ACCOUNT_MODE_ALLOCATION).equals(accountMode)) {
            return accountBalance;
        }
        if (Integer.valueOf(ACCOUNT_MODE_SHARED).equals(accountMode)) {
            return companyBalance;
        }
        return null;
    }
    /**
     * 远程查询公司VIN码列表(降级返回空列表,保证详情主流程不受影响)
     */
    private List<TVinManagement> fetchVins(Integer companyId) {
        try {
            R<List<TVinManagement>> response = vinManagementClient.listByCompany(companyId);
            return (response != null && R.isSuccess(response) && response.getData() != null)
                    ? response.getData() : new ArrayList<>();
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }
    /**
     * 远程查询公司卡片列表(降级返回空列表)
     */
    private List<TChargingCard> fetchCards(Integer companyId) {
        try {
            R<List<TChargingCard>> response = chargingCardClient.listByCompany(companyId);
            return (response != null && R.isSuccess(response) && response.getData() != null)
                    ? response.getData() : new ArrayList<>();
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }
    /**
     * 远程查询公司白名单列表(降级返回空列表)
     */
    private List<TAppUserWhitelist> fetchWhitelists(Integer companyId) {
        try {
            R<List<TAppUserWhitelist>> response = appUserWhitelistClient.listByCompany(companyId);
            return (response != null && R.isSuccess(response) && response.getData() != null)
                    ? response.getData() : new ArrayList<>();
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }
    /**
     * 批量查询车队名称(本地库 t_fleet,逻辑删除由 @TableLogic 自动过滤)
     */
    private Map<Integer, String> loadFleetNameMap(List<Integer> fleetIds) {
        Map<Integer, String> map = new HashMap<>();
        if (fleetIds == null || fleetIds.isEmpty()) {
            return map;
        }
        List<Integer> distinctIds = fleetIds.stream()
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (distinctIds.isEmpty()) {
            return map;
        }
        List<TFleet> fleets = fleetMapper.selectList(
                Wrappers.<TFleet>lambdaQuery().in(TFleet::getId, distinctIds));
        for (TFleet fleet : fleets) {
            map.put(fleet.getId(), fleet.getFleetName());
        }
        return map;
    }
    /**
@@ -116,4 +344,173 @@
                .setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
    }
    /**
     * 查询公司默认角色ID(内置角色 role_key=company)
     */
    private Long resolveCompanyRoleId() {
        R<Long> roleR = sysRoleClient.getRoleIdByRoleKey(COMPANY_ROLE_KEY);
        if (roleR == null || 200 != roleR.getCode() || roleR.getData() == null) {
            throw new RuntimeException("系统内置角色【" + COMPANY_ROLE_KEY + "】不存在或已停用,请先配置角色");
        }
        return roleR.getData();
    }
    /**
     * 检查登录账号在 t_group_company 中是否已存在(编辑时排除自身)
     */
    private boolean accountExists(String account, Integer excludeId) {
        Long count = count(Wrappers.<TGroupCompany>lambdaQuery()
                .eq(TGroupCompany::getAccount, account)
                .ne(excludeId != null, TGroupCompany::getId, excludeId));
        return count != null && count > 0;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult addCompany(TGroupCompany company) {
        if (!StringUtils.hasText(company.getAccount())) {
            return AjaxResult.error("登录账号不能为空");
        }
        // 添加企业弹窗不再配置账户模式:未指定时按共享模式建档,后续可在“账户模式”弹窗切换
        if (company.getAccountMode() == null) {
            company.setAccountMode(ACCOUNT_MODE_SHARED);
        }
        if (!StringUtils.hasText(company.getPassword())) {
            return AjaxResult.error("登录密码不能为空");
        }
        if (accountExists(company.getAccount(), null)) {
            return AjaxResult.error("登录账号不能重复");
        }
        R<SysUser> existR = sysUserClient.queryUserByUserName(company.getAccount());
        SysUser existUser = existR == null ? null : existR.getData();
        if (existUser != null) {
            return AjaxResult.error("登录账号不能重复");
        }
        Long roleId = resolveCompanyRoleId();
        SysUser user = new SysUser();
        user.setUserName(company.getAccount());
        user.setNickName(company.getName());
        user.setPhonenumber(company.getContactPhone());
        user.setPassword(company.getPassword());
        user.setStatus("0");
        user.setDelFlag("0");
        user.setRoleId(roleId);
        user.setRoleType(COMPANY_ROLE_TYPE);
        user.setPassWordUpdate(new Date());
        R<Long> r = sysUserClient.addSysUser(user);
        if (r == null || 200 != r.getCode()) {
            throw new RuntimeException(r == null ? "创建登录账号失败" : r.getMsg());
        }
        save(company);
        // 回写 objectId=公司ID(账号创建时公司尚未保存)
        R<SysUser> createdR = sysUserClient.getSysUser(r.getData());
        if (createdR != null && 200 == createdR.getCode() && createdR.getData() != null) {
            SysUser created = createdR.getData();
            created.setObjectId(company.getId());
            sysUserClient.updateUser(created);
        }
        return AjaxResult.success();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult editCompany(TGroupCompany company) {
        TGroupCompany old = getById(company.getId());
        if (old == null) {
            return AjaxResult.error("公司不存在");
        }
        // 编辑时账号只读(前端禁用),此处兼容外部调用方传入账号的场景:唯一性校验(排除自身)
        if (StringUtils.hasText(company.getAccount())) {
            if (accountExists(company.getAccount(), company.getId())) {
                return AjaxResult.error("登录账号不能重复");
            }
        }
        String account = StringUtils.hasText(company.getAccount()) ? company.getAccount() : old.getAccount();
        if (StringUtils.hasText(account)) {
            syncCompanyUserOnEdit(company, account);
        }
        updateById(company);
        return AjaxResult.success();
    }
    /**
     * 编辑公司时同步登录账号:存在则仅更新账号资料(不修改密码),不存在则补建
     */
    private void syncCompanyUserOnEdit(TGroupCompany company, String account) {
        R<SysUser> userR = sysUserClient.queryUserByUserName(account);
        SysUser user = userR == null ? null : userR.getData();
        if (user == null) {
            if (!StringUtils.hasText(company.getPassword())) {
                throw new RuntimeException("补建登录账号时登录密码不能为空");
            }
            Long roleId = resolveCompanyRoleId();
            SysUser newUser = new SysUser();
            newUser.setUserName(account);
            newUser.setNickName(company.getName());
            newUser.setPhonenumber(company.getContactPhone());
            newUser.setPassword(company.getPassword());
            newUser.setStatus("0");
            newUser.setDelFlag("0");
            newUser.setRoleId(roleId);
            newUser.setRoleType(COMPANY_ROLE_TYPE);
            newUser.setObjectId(company.getId());
            newUser.setPassWordUpdate(new Date());
            R<Long> r = sysUserClient.addSysUser(newUser);
            if (r == null || 200 != r.getCode()) {
                throw new RuntimeException(r == null ? "创建登录账号失败" : r.getMsg());
            }
            return;
        }
        // 远程查询返回的密码字段可能是哈希或脱敏值,显式清空避免再次写回
        user.setPassword(null);
        user.setPassWordUpdate(null);
        user.setUserName(account);
        user.setNickName(company.getName());
        user.setPhonenumber(company.getContactPhone());
        R<Boolean> r = sysUserClient.updateSysUser(user);
        if (r == null || 200 != r.getCode()) {
            throw new RuntimeException(r == null ? "更新登录账号失败" : r.getMsg());
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult delCompany(Integer id) {
        TGroupCompany company = getById(id);
        if (company == null) {
            return AjaxResult.error("公司不存在");
        }
        removeById(id);
        if (StringUtils.hasText(company.getAccount())) {
            R<SysUser> userR = sysUserClient.queryUserByUserName(company.getAccount());
            SysUser user = userR == null ? null : userR.getData();
            if (user != null) {
                user.setDelFlag("2");
                sysUserClient.updateUser(user);
            }
        }
        return AjaxResult.success();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult updateAccountMode(Integer id, Integer accountMode) {
        if (id == null) {
            return AjaxResult.error("公司ID不能为空");
        }
        if (accountMode == null
                || (accountMode != ACCOUNT_MODE_SHARED && accountMode != ACCOUNT_MODE_ALLOCATION)) {
            return AjaxResult.error("账户模式不合法");
        }
        TGroupCompany company = getById(id);
        if (company == null) {
            return AjaxResult.error("公司不存在");
        }
        TGroupCompany update = new TGroupCompany();
        update.setId(id);
        update.setAccountMode(accountMode);
        updateById(update);
        return AjaxResult.success();
    }
}