package com.tourguide.modules.finance.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.tourguide.common.exception.BusinessException;
|
import com.tourguide.common.huifu.biz.DouGongBasicDataBiz;
|
import com.tourguide.modules.finance.dto.GuideBankBranchResponse;
|
import com.tourguide.modules.finance.dto.GuideBankCardRequest;
|
import com.tourguide.modules.finance.dto.GuideBankCardResponse;
|
import com.tourguide.modules.finance.dto.GuideSettlementOnboardingRequest;
|
import com.tourguide.modules.finance.dto.GuideSettlementStatusResponse;
|
import com.tourguide.modules.finance.entity.GuideWithdrawalAccount;
|
import com.tourguide.modules.finance.entity.GuideBankBranch;
|
import com.tourguide.modules.finance.mapper.GuideBankBranchMapper;
|
import com.tourguide.modules.finance.mapper.GuideWithdrawalAccountMapper;
|
import com.tourguide.modules.user.entity.GuideUser;
|
import com.tourguide.modules.user.service.GuideUserService;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
|
import java.time.LocalDateTime;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.UUID;
|
import java.util.stream.Collectors;
|
|
/** 导游端进件、审核状态和当前结算卡能力。 */
|
@Service
|
public class GuideSettlementService {
|
static final int GUIDE_USER_TYPE = 2;
|
static final int HUIFU_TYPE = 1;
|
static final int ACTIVE_STATUS = 1;
|
private static final int AUDIT_PENDING = 1;
|
private static final int AUDIT_APPROVED = 2;
|
private static final int AUDIT_REJECTED = 3;
|
private static final String SUCCESS_CODE = "00000000";
|
|
private final GuideWithdrawalAccountMapper accountMapper;
|
private final GuideUserService guideUserService;
|
private final DouGongBasicDataBiz douGongBasicDataBiz;
|
private final GuideBankBranchMapper bankBranchMapper;
|
|
public GuideSettlementService(GuideWithdrawalAccountMapper accountMapper,
|
GuideUserService guideUserService,
|
DouGongBasicDataBiz douGongBasicDataBiz,
|
GuideBankBranchMapper bankBranchMapper) {
|
this.accountMapper = accountMapper;
|
this.guideUserService = guideUserService;
|
this.douGongBasicDataBiz = douGongBasicDataBiz;
|
this.bankBranchMapper = bankBranchMapper;
|
}
|
|
public GuideSettlementStatusResponse status(Long guideId) {
|
guideUserService.findEnabledById(guideId);
|
GuideWithdrawalAccount account = findCurrent(guideId);
|
if (account == null) return new GuideSettlementStatusResponse("unsubmitted", null, null, null);
|
refreshAuditState(account);
|
return toStatus(account);
|
}
|
|
@Transactional(noRollbackFor = BusinessException.class)
|
public GuideSettlementStatusResponse submit(Long guideId, GuideSettlementOnboardingRequest request) {
|
GuideUser guide = guideUserService.findEnabledById(guideId);
|
validateOnboarding(request);
|
GuideWithdrawalAccount account = findCurrent(guideId);
|
if (account != null && "approved".equals(statusValue(account))) {
|
throw new BusinessException(409, "当前导游已完成进件");
|
}
|
if (account != null && "pending".equals(statusValue(account))) {
|
throw new BusinessException(409, "进件资料正在审核中,请勿重复提交");
|
}
|
if (account != null && StringUtils.hasText(account.getHuifuId())
|
&& StringUtils.hasText(account.getSubjectType())
|
&& !account.getSubjectType().equals(request.getSubjectType())) {
|
throw new BusinessException(409, "已提交的进件主体类型不可变更,请按原类型重新提交");
|
}
|
if (account == null) account = new GuideWithdrawalAccount();
|
applyOnboarding(account, guideId, request, guide);
|
account.setApplicationStatus("processing");
|
account.setIsAudit(AUDIT_PENDING);
|
account.setFailureMsgsReason(null);
|
account.setOutRequestNo(randomRequestNo());
|
account.setCreateTime(account.getCreateTime() == null ? LocalDateTime.now() : account.getCreateTime());
|
save(account);
|
try {
|
boolean enterprise = "enterprise".equals(account.getSubjectType());
|
Map<String, Object> result = enterprise
|
? submitEnterprise(account)
|
: submitIndividual(account);
|
requireSuccess(result, "斗拱进件提交失败");
|
applyRemoteIds(account, result);
|
if (enterprise) {
|
applyRemoteAuditState(account, result, false);
|
} else {
|
applyIndividualSuccess(account);
|
}
|
save(account);
|
return toStatus(account);
|
} catch (BusinessException exception) {
|
markRejected(account, exception.getMessage());
|
throw exception;
|
} catch (Exception exception) {
|
markRejected(account, exception.getMessage());
|
throw new BusinessException(502, "斗拱进件服务暂不可用,请稍后重试");
|
}
|
}
|
|
public List<GuideBankCardResponse> bankCards(Long guideId) {
|
GuideWithdrawalAccount account = requireApproved(guideId);
|
if (!StringUtils.hasText(account.getBankCardCode())) return Collections.emptyList();
|
refreshBankCardState(account);
|
return Collections.singletonList(new GuideBankCardResponse(account));
|
}
|
|
@Transactional
|
public GuideBankCardResponse bindBankCard(Long guideId, GuideBankCardRequest request) {
|
guideUserService.findEnabledById(guideId);
|
GuideWithdrawalAccount account = accountMapper.selectCurrentForUpdate(toTableUserId(guideId));
|
requireApproved(account);
|
GuideBankBranch branch = findActiveBranch(request.getBranchCode(), request.getCityCode());
|
account.setBankName(branch.getBankName());
|
account.setBankCardCode(trim(request.getCardNumber()));
|
account.setBankBranchName(branch.getBranchName());
|
account.setBankBranchCode(branch.getBranchCode());
|
account.setBankCardProvinceCode(trim(request.getProvinceCode()));
|
account.setBankCardCityCode(branch.getCityCode());
|
account.setOpenAccountName(account.getCertificateName());
|
account.setBankCardStatus("local");
|
account.setBankCardReason(null);
|
account.setTokenNo(null);
|
account.setCusApplicationId(null);
|
save(account);
|
return new GuideBankCardResponse(account);
|
}
|
|
@Transactional(noRollbackFor = BusinessException.class)
|
public GuideBankCardResponse openBankCardBusiness(Long guideId, Integer cardId) {
|
guideUserService.findEnabledById(guideId);
|
GuideWithdrawalAccount account = accountMapper.selectCurrentForUpdate(toTableUserId(guideId));
|
requireApproved(account);
|
requireCurrentBankCard(account, cardId);
|
if (StringUtils.hasText(account.getTokenNo())) {
|
account.setBankCardStatus("approved");
|
account.setBankCardReason(null);
|
save(account);
|
return new GuideBankCardResponse(account);
|
}
|
if ("pending".equals(account.getBankCardStatus())
|
&& StringUtils.hasText(account.getCusApplicationId())) {
|
refreshBankCardState(account);
|
return new GuideBankCardResponse(account);
|
}
|
account.setBankCardStatus("pending");
|
account.setBankCardReason(null);
|
account.setCusApplicationId(null);
|
save(account);
|
try {
|
Map<String, Object> result = douGongBasicDataBiz.userBusinessOpen(account);
|
requireSuccess(result, "银行卡业务入驻失败");
|
account.setTokenNo(first(result, "token_no", "card_token_no"));
|
account.setCusApplicationId(first(result, "apply_no", "application_no", "cus_application_id"));
|
if (!StringUtils.hasText(account.getTokenNo())
|
&& !StringUtils.hasText(account.getCusApplicationId())) {
|
throw new BusinessException(502, "银行卡业务入驻响应缺少取现卡序列号");
|
}
|
account.setBankCardStatus(StringUtils.hasText(account.getTokenNo()) ? "approved" : "pending");
|
save(account);
|
return new GuideBankCardResponse(account);
|
} catch (BusinessException exception) {
|
account.setBankCardStatus("rejected");
|
account.setBankCardReason(exception.getMessage());
|
save(account);
|
throw exception;
|
} catch (Exception exception) {
|
account.setBankCardStatus("rejected");
|
account.setBankCardReason(exception.getMessage());
|
save(account);
|
throw new BusinessException(502, "银行卡业务入驻服务暂不可用,请稍后重试");
|
}
|
}
|
|
private void requireCurrentBankCard(GuideWithdrawalAccount account, Integer cardId) {
|
if (cardId == null || !cardId.equals(account.getId())
|
|| !StringUtils.hasText(account.getBankCardCode())) {
|
throw new BusinessException(404, "银行卡不存在,请重新选择");
|
}
|
}
|
|
private GuideBankBranch findActiveBranch(String branchCode, String cityCode) {
|
GuideBankBranch branch = bankBranchMapper.selectOne(new LambdaQueryWrapper<GuideBankBranch>()
|
.eq(GuideBankBranch::getBranchCode, trim(branchCode))
|
.eq(GuideBankBranch::getCityCode, trim(cityCode))
|
.eq(GuideBankBranch::getStatus, ACTIVE_STATUS)
|
.last("LIMIT 1"));
|
if (branch == null) {
|
throw new BusinessException(400, "开户行不存在或已停用,请重新选择");
|
}
|
return branch;
|
}
|
|
public List<GuideBankBranchResponse> branches(String cityCode, String keyword) {
|
String query = trim(keyword);
|
LambdaQueryWrapper<GuideBankBranch> wrapper = new LambdaQueryWrapper<GuideBankBranch>()
|
.eq(GuideBankBranch::getStatus, 1)
|
.eq(StringUtils.hasText(cityCode), GuideBankBranch::getCityCode,
|
StringUtils.hasText(cityCode) ? cityCode.trim() : null)
|
.and(StringUtils.hasText(query), item -> item.like(GuideBankBranch::getBankName, query)
|
.or().like(GuideBankBranch::getBranchName, query))
|
.orderByAsc(GuideBankBranch::getBankName)
|
.orderByAsc(GuideBankBranch::getBranchName)
|
.last("LIMIT 100");
|
return bankBranchMapper.selectList(wrapper).stream()
|
.map(GuideBankBranchResponse::new)
|
.collect(Collectors.toList());
|
}
|
|
public GuideWithdrawalAccount requireApproved(Long guideId) {
|
guideUserService.findEnabledById(guideId);
|
return requireApproved(findCurrent(guideId));
|
}
|
|
public GuideWithdrawalAccount requireApprovedCard(Long guideId, Integer cardId) {
|
GuideWithdrawalAccount account = requireApproved(guideId);
|
return requireApprovedCard(account, cardId);
|
}
|
|
/** 提现创建事务内锁定当前进件账户,串行校验余额和结算卡。 */
|
public GuideWithdrawalAccount requireApprovedCardForUpdate(Long guideId, Integer cardId) {
|
guideUserService.findEnabledById(guideId);
|
GuideWithdrawalAccount account = accountMapper.selectCurrentForUpdate(toTableUserId(guideId));
|
requireApproved(account);
|
return requireApprovedCard(account, cardId);
|
}
|
|
private GuideWithdrawalAccount requireApprovedCard(GuideWithdrawalAccount account, Integer cardId) {
|
if (!account.getId().equals(cardId) || !StringUtils.hasText(account.getTokenNo())
|
|| "rejected".equals(account.getBankCardStatus())) {
|
throw new BusinessException(409, "请选择审核通过的银行卡");
|
}
|
return account;
|
}
|
|
public GuideWithdrawalAccount findCurrent(Long guideId) {
|
return accountMapper.selectOne(new LambdaQueryWrapper<GuideWithdrawalAccount>()
|
.eq(GuideWithdrawalAccount::getUserId, toTableUserId(guideId))
|
.eq(GuideWithdrawalAccount::getUserType, GUIDE_USER_TYPE)
|
.eq(GuideWithdrawalAccount::getType, HUIFU_TYPE)
|
.eq(GuideWithdrawalAccount::getStatus, ACTIVE_STATUS)
|
.orderByDesc(GuideWithdrawalAccount::getId)
|
.last("LIMIT 1"));
|
}
|
|
private Map<String, Object> submitEnterprise(GuideWithdrawalAccount account) throws Exception {
|
return StringUtils.hasText(account.getHuifuId())
|
? douGongBasicDataBiz.userBasicDataEntModify(account)
|
: douGongBasicDataBiz.userBasicDataEnt(account);
|
}
|
|
private Map<String, Object> submitIndividual(GuideWithdrawalAccount account) throws Exception {
|
return StringUtils.hasText(account.getHuifuId())
|
? douGongBasicDataBiz.userBasicDataIndvModify(account)
|
: douGongBasicDataBiz.userBasicDataIndv(account);
|
}
|
|
private void refreshAuditState(GuideWithdrawalAccount account) {
|
if (!"pending".equals(statusValue(account)) || !StringUtils.hasText(account.getHuifuId())) return;
|
// 个人基础进件是同步接口:成功响应只有 huifu_id,不会返回申请单号,也无需查询审核状态。
|
if (!"enterprise".equals(account.getSubjectType())) {
|
applyIndividualSuccess(account);
|
save(account);
|
return;
|
}
|
if (!StringUtils.hasText(account.getCusApplicationId())) return;
|
try {
|
Map<String, Object> result = douGongBasicDataBiz.userApplyQuery(account, account.getCusApplicationId());
|
requireSuccess(result, "进件审核状态查询失败");
|
applyRemoteAuditState(account, result, true);
|
save(account);
|
} catch (BusinessException exception) {
|
throw exception;
|
} catch (Exception exception) {
|
throw new BusinessException(502, "进件审核状态查询失败,请稍后重试");
|
}
|
}
|
|
private void refreshBankCardState(GuideWithdrawalAccount account) {
|
if (!"pending".equals(account.getBankCardStatus())
|
|| !StringUtils.hasText(account.getHuifuId())
|
|| !StringUtils.hasText(account.getCusApplicationId())) return;
|
try {
|
Map<String, Object> result = douGongBasicDataBiz.userApplyQuery(account, account.getCusApplicationId());
|
requireSuccess(result, "银行卡审核状态查询失败");
|
String remoteStatus = remoteStatus(result);
|
String tokenNo = first(result, "token_no", "card_token_no");
|
if (StringUtils.hasText(tokenNo)) account.setTokenNo(tokenNo);
|
if (isApproved(remoteStatus) || StringUtils.hasText(account.getTokenNo())) {
|
account.setBankCardStatus("approved");
|
account.setBankCardReason(null);
|
} else if (isRejected(remoteStatus)) {
|
account.setBankCardStatus("rejected");
|
account.setBankCardReason(remoteReason(result));
|
}
|
save(account);
|
} catch (Exception ignored) {
|
// 备注:银行卡列表仍返回本地最后状态,避免第三方短暂不可用导致页面完全不可用。
|
}
|
}
|
|
private void applyRemoteAuditState(GuideWithdrawalAccount account, Map<String, Object> result,
|
boolean keepPendingWhenUnknown) {
|
String remoteStatus = remoteStatus(result);
|
if (isApproved(remoteStatus)) {
|
account.setIsAudit(AUDIT_APPROVED);
|
account.setApplicationStatus("succeeded");
|
account.setSucceededAt(account.getSucceededAt() == null ? LocalDateTime.now() : account.getSucceededAt());
|
account.setFailureMsgsReason(null);
|
} else if (isRejected(remoteStatus)) {
|
account.setIsAudit(AUDIT_REJECTED);
|
account.setApplicationStatus("failed");
|
account.setFailureMsgsReason(remoteReason(result));
|
} else if (keepPendingWhenUnknown || !StringUtils.hasText(remoteStatus)) {
|
account.setIsAudit(AUDIT_PENDING);
|
account.setApplicationStatus("submitted");
|
} else {
|
account.setIsAudit(AUDIT_PENDING);
|
account.setApplicationStatus(remoteStatus);
|
}
|
}
|
|
private void applyIndividualSuccess(GuideWithdrawalAccount account) {
|
if (!StringUtils.hasText(account.getHuifuId())) {
|
throw new BusinessException(502, "斗拱个人进件成功响应缺少汇付ID");
|
}
|
account.setIsAudit(AUDIT_APPROVED);
|
account.setApplicationStatus("succeeded");
|
account.setSucceededAt(account.getSucceededAt() == null ? LocalDateTime.now() : account.getSucceededAt());
|
account.setFailureMsgsReason(null);
|
account.setCusApplicationId(null);
|
}
|
|
private GuideWithdrawalAccount requireApproved(GuideWithdrawalAccount account) {
|
if (account == null) throw new BusinessException(409, "请先完成导游进件");
|
if (!"approved".equals(statusValue(account))) {
|
throw new BusinessException(409, "进件审核通过后才能继续操作");
|
}
|
if (!StringUtils.hasText(account.getHuifuId())) {
|
throw new BusinessException(409, "进件账户信息不完整,请联系平台处理");
|
}
|
return account;
|
}
|
|
private GuideSettlementStatusResponse toStatus(GuideWithdrawalAccount account) {
|
return new GuideSettlementStatusResponse(statusValue(account),
|
StringUtils.hasText(account.getSubjectType()) ? account.getSubjectType() : "individual",
|
account.getFailureMsgsReason(), account.getCreateTime());
|
}
|
|
private String statusValue(GuideWithdrawalAccount account) {
|
if (account == null) return "unsubmitted";
|
if (Integer.valueOf(AUDIT_APPROVED).equals(account.getIsAudit())) return "approved";
|
if (Integer.valueOf(AUDIT_REJECTED).equals(account.getIsAudit())) return "rejected";
|
String value = account.getApplicationStatus();
|
if (isApproved(value) && StringUtils.hasText(account.getHuifuId())) return "approved";
|
if (isRejected(value)) return "rejected";
|
return "pending";
|
}
|
|
private void validateOnboarding(GuideSettlementOnboardingRequest request) {
|
boolean enterprise = "enterprise".equals(request.getSubjectType());
|
if (enterprise) {
|
requireText(request.getEnterpriseName(), "企业名称不能为空");
|
if (!StringUtils.hasText(request.getCreditCode())
|
|| !request.getCreditCode().trim().matches("^[0-9A-Z]{18}$")) {
|
throw new BusinessException(400, "统一社会信用代码格式不正确");
|
}
|
requireValidity(request.getLicenseValidityType(), request.getLicenseBeginDate(),
|
request.getLicenseEndDate(), "营业执照");
|
requireText(request.getRegisteredProvinceCode(), "注册地址省编码不能为空");
|
requireText(request.getRegisteredCityCode(), "注册地址市编码不能为空");
|
requireText(request.getRegisteredDistrictCode(), "注册地址区县编码不能为空");
|
requireText(request.getRegisteredAddress(), "详细注册地址不能为空");
|
}
|
requireValidity(request.getSubjectCertificateValidityType(),
|
request.getSubjectCertificateBeginDate(), request.getSubjectCertificateEndDate(), "证件");
|
}
|
|
private void requireValidity(String type, String begin, String end, String label) {
|
if (!"fixed".equals(type) && !"long_term".equals(type)) {
|
throw new BusinessException(400, label + "有效期类型不支持");
|
}
|
requireText(begin, label + "有效期开始日期不能为空");
|
if ("fixed".equals(type)) requireText(end, label + "有效期结束日期不能为空");
|
}
|
|
private void requireText(String value, String message) {
|
if (!StringUtils.hasText(value)) throw new BusinessException(400, message);
|
}
|
|
private void applyOnboarding(GuideWithdrawalAccount account, Long guideId,
|
GuideSettlementOnboardingRequest request, GuideUser guide) {
|
account.setUserId(toTableUserId(guideId));
|
account.setUserType(GUIDE_USER_TYPE);
|
account.setType(HUIFU_TYPE);
|
account.setStatus(ACTIVE_STATUS);
|
account.setSubjectType(request.getSubjectType());
|
account.setEnterpriseName(trim(request.getEnterpriseName()));
|
account.setCreditCode(trim(request.getCreditCode()));
|
account.setLicenseValidityType(trim(request.getLicenseValidityType()));
|
account.setLicenseBeginDate(trim(request.getLicenseBeginDate()));
|
account.setLicenseEndDate(trim(request.getLicenseEndDate()));
|
account.setRegisteredProvinceCode(trim(request.getRegisteredProvinceCode()));
|
account.setRegisteredCityCode(trim(request.getRegisteredCityCode()));
|
account.setRegisteredDistrictCode(trim(request.getRegisteredDistrictCode()));
|
account.setRegisteredAddress(trim(request.getRegisteredAddress()));
|
account.setContactName(trim(request.getContactName()));
|
account.setCertificateName(trim(request.getSubjectName()));
|
account.setCertificateType(0);
|
account.setCertificateCode(trim(request.getSubjectIdNumber()).toUpperCase());
|
account.setCertificateExpireTime(dateRange(request.getSubjectCertificateValidityType(),
|
request.getSubjectCertificateBeginDate(), request.getSubjectCertificateEndDate()));
|
account.setOpenAccountName(trim(request.getSubjectName()));
|
account.setPhone(StringUtils.hasText(request.getContactMobile())
|
? request.getContactMobile().trim() : guide.getMobile());
|
}
|
|
private String dateRange(String type, String begin, String end) {
|
return trim(begin) + "," + ("long_term".equals(type) ? "长期" : trim(end));
|
}
|
|
private void applyRemoteIds(GuideWithdrawalAccount account, Map<String, Object> result) {
|
String huifuId = first(result, "huifu_id", "user_huifu_id");
|
String applicationId = first(result, "apply_no", "application_no", "cus_application_id");
|
if (StringUtils.hasText(huifuId)) account.setHuifuId(huifuId);
|
if (StringUtils.hasText(applicationId)) account.setCusApplicationId(applicationId);
|
}
|
|
private void requireSuccess(Map<String, Object> result, String fallback) {
|
String code = first(result, "resp_code", "code");
|
if (SUCCESS_CODE.equals(code)) return;
|
String message = first(result, "resp_desc", "message", "msg");
|
throw new BusinessException(502, StringUtils.hasText(message) ? message : fallback);
|
}
|
|
private String remoteStatus(Map<String, Object> result) {
|
return first(result, "apply_status", "application_status", "audit_status", "status", "trans_stat");
|
}
|
|
private String remoteReason(Map<String, Object> result) {
|
String reason = first(result, "failure_reason", "fail_reason", "audit_remark", "resp_desc");
|
return StringUtils.hasText(reason) ? reason : "进件审核未通过";
|
}
|
|
private boolean isApproved(String value) {
|
if (!StringUtils.hasText(value)) return false;
|
String normalized = value.trim().toLowerCase();
|
return Arrays.asList("2", "s", "success", "succeeded", "approved", "pass", "passed").contains(normalized);
|
}
|
|
private boolean isRejected(String value) {
|
if (!StringUtils.hasText(value)) return false;
|
String normalized = value.trim().toLowerCase();
|
return Arrays.asList("3", "f", "failed", "failure", "rejected", "reject").contains(normalized);
|
}
|
|
@SuppressWarnings("unchecked")
|
private String first(Map<String, Object> result, String... keys) {
|
if (result == null) return null;
|
for (String key : keys) {
|
Object value = result.get(key);
|
if (value != null && StringUtils.hasText(String.valueOf(value))) return String.valueOf(value);
|
}
|
Object data = result.get("data");
|
if (data instanceof Map) return first((Map<String, Object>) data, keys);
|
if (data instanceof String && StringUtils.hasText((String) data)) {
|
try {
|
Map<String, Object> nested = com.alibaba.fastjson.JSONObject.parseObject(
|
(String) data, LinkedHashMap.class);
|
return first(nested, keys);
|
} catch (Exception ignored) {}
|
}
|
return null;
|
}
|
|
private void markRejected(GuideWithdrawalAccount account, String reason) {
|
account.setIsAudit(AUDIT_REJECTED);
|
account.setApplicationStatus("failed");
|
account.setFailureMsgsReason(StringUtils.hasText(reason) ? reason : "斗拱进件失败");
|
save(account);
|
}
|
|
private void save(GuideWithdrawalAccount account) {
|
if (account.getId() == null) accountMapper.insert(account); else accountMapper.updateById(account);
|
}
|
|
private int toTableUserId(Long guideId) {
|
try {
|
return Math.toIntExact(guideId);
|
} catch (ArithmeticException exception) {
|
throw new BusinessException(400, "导游ID超出进件表支持范围");
|
}
|
}
|
|
private String randomRequestNo() {
|
return UUID.randomUUID().toString().replace("-", "");
|
}
|
|
private String trim(String value) {
|
return StringUtils.hasText(value) ? value.trim() : null;
|
}
|
}
|