Merge branch 'feature/payment-apply-bfi-sync' into feature/hcs-api-p0
# Conflicts: # blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java # blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java # blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java # blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java # blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java # blade-service/blade-transport/src/main/java/org/springblade/transport/task/CustomerKingdeeSyncTask.java # blade-service/blade-transport/src/main/java/org/springblade/transport/task/PaymentKingdeeResultTask.java # doc/nacos/blade.yaml # doc/nacos/third-party-api.yaml
This commit is contained in:
+82
@@ -0,0 +1,82 @@
|
||||
# TMS(运输业务中台)—— 业财集成语境
|
||||
|
||||
TMS 与集团财务系统(金蝶星瀚)、合同合规系统(HCS)集成,单据在三个系统间流转。
|
||||
本表只收录集成语境下易混淆的业务术语,不含实现细节。
|
||||
|
||||
## Language
|
||||
|
||||
**合同合规系统(HCS)**:
|
||||
集团合同合规管控系统(桂物数字合同合规),接收 TMS 推送的项目、合同、履约与付款数据并做合规审批。
|
||||
_Avoid_: 桂物系统、合同系统、数字合同系统
|
||||
|
||||
**相对方**:
|
||||
HCS 对签约主体(甲乙方)的称谓,与 TMS 的"客商"同指一个外部单位。
|
||||
_Avoid_: 在 HCS 集成语境用"客户/供应商"
|
||||
|
||||
**来源单据键**:
|
||||
TMS 单据在 HCS 侧的对账主键(接口字段 `fd_kingdee_id`,字段名沿用金蝶),统一存 TMS 业务单据主键。
|
||||
_Avoid_: 外部ID、kingdeeId(泛指金蝶单据号时易混淆)
|
||||
|
||||
**来源标识**:
|
||||
TMS 推送给 HCS 时声明自身身份的常量(接口字段 `fd_sources`,约定值 "TMS")。
|
||||
_Avoid_: XH(那是星瀚侧的来源标识)
|
||||
|
||||
**履约规则**:
|
||||
合同维度的资金计划阶段(付款/收款、款项性质、计划金额、计划日期、开票规则),由 TMS 维护并同步到 HCS。
|
||||
_Avoid_: 履约计划(旧版已废弃接口的叫法)、付款计划
|
||||
|
||||
**履约阶段**:
|
||||
履约规则表中的一行;HCS 为其生成阶段ID,后续更新/删除必须凭该ID。
|
||||
_Avoid_: 阶段项、plan 行
|
||||
|
||||
**履约记录**:
|
||||
付款/收款执行事实在 HCS 的登记(一笔付款申请一条记录,随实际付款推进更新),与履约规则是"计划 vs 执行"的关系。
|
||||
_Avoid_: 履约流水
|
||||
|
||||
**额度校验**:
|
||||
付款提交时由 HCS 判定申请金额是否超出合同/项目剩余额度的同步检查;超额不阻断提交,转入特批链路。
|
||||
_Avoid_: 余额检查
|
||||
|
||||
**特批流程**:
|
||||
额度超额(或风险不通过)时在 HCS 发起的审批流程;TMS 持有其流程ID,流程结束由 HCS 经 B103 回传结果。
|
||||
_Avoid_: 特殊审批、绿色通道
|
||||
|
||||
**特批放行**:
|
||||
特批结果为"通过"后对**本次**付款申请解除推送拦截的动作;只改特批状态,不触发推送,单次有效。
|
||||
_Avoid_: 特批通过自动付款、放行推送
|
||||
|
||||
**锁单(B901)**:
|
||||
金蝶确认接收 TMS 推送的单据后,经 B901 通知 TMS 将本地源单据置为不可编辑、不可作废、不可重推的状态;金蝶反审核及删除后经 B901 解锁。
|
||||
_Avoid_: 冻结、金蝶锁定(那是金蝶侧单据自己的状态)
|
||||
|
||||
**解锁**:
|
||||
B901 通知金蝶侧单据已反审核及删除后,TMS 解除锁单、同步状态置失败、清金蝶单号留痕;不自动重推,人工核实后重新推送。
|
||||
_Avoid_: 反审核(那是金蝶侧的动作)、自动重推
|
||||
|
||||
**合规附件ID**:
|
||||
TMS 附件先上传 HCS 附件服务换取的 fdId;同一附件以 TMS 附件元数据为键缓存映射,不重复上传。
|
||||
_Avoid_: 附件fdId(指代不明)
|
||||
|
||||
**同步状态**:
|
||||
TMS 单据对 HCS 的推送生命周期(待同步/已同步/失败),权威状态在 TMS 侧字段,HCS 不回写业务状态。
|
||||
_Avoid_: 推送标志
|
||||
|
||||
**二次认领**:
|
||||
金蝶司库完成第一轮自动认领(客户维度)后,TMS 把收款流水认领到合同/结算单维度的动作;只在 TMS 本地登记,本身不产生金蝶收款单。
|
||||
_Avoid_: 收款认领(泛指整条链路时易与金蝶第一轮自动认领混淆)
|
||||
|
||||
**公告未认领金额**:
|
||||
金蝶认领公告(A301-2)上尚未被认领的金额(字段 unclaimamount),是 TMS 二次认领的上限和"认领完成"的判定基准;以金蝶侧为准,推送前须重查最新值。
|
||||
_Avoid_: 剩余可认领金额(那是本地展示口径)、未认领金额(不指明公告侧/本地侧时指代不明)
|
||||
|
||||
**认领完成**:
|
||||
一条收款流水的累计有效认领金额等于公告未认领金额的状态;只有认领完成的流水才满足推送金蝶的条件,不认领完就不推。
|
||||
_Avoid_: 认领完毕、全部认领(未指明对照基准)
|
||||
|
||||
**认领状态**:
|
||||
收款流水本地认领进度(未认领/部分认领/认领完成),对照公告未认领金额计算;作废的认领分摊不计入累计。
|
||||
_Avoid_: 与推送状态混用
|
||||
|
||||
**推送状态**:
|
||||
收款流水对金蝶 A301-3 的推送生命周期(未推送/已推送/推送失败),权威在流水级,认领主单仅冗余展示;已推送的流水不可二次推送。
|
||||
_Avoid_: 金蝶状态(那是 A301-1 回写的单据生命周期:已推送/审核通过/已收款)
|
||||
@@ -51,6 +51,7 @@ import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.system.feign.IAuthLockClient;
|
||||
import org.springblade.system.feign.IAuthLogClient;
|
||||
import org.springblade.core.tenant.BladeTenantProperties;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@@ -117,8 +118,8 @@ public class BladeAuthConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2UserService oAuth2UserService(IUserClient userClient) {
|
||||
return new BladeUserDetailService(userClient);
|
||||
public OAuth2UserService oAuth2UserService(IUserClient userClient, IMpUserClient mpUserClient) {
|
||||
return new BladeUserDetailService(userClient, mpUserClient);
|
||||
}
|
||||
|
||||
@Primary
|
||||
|
||||
+8
-1
@@ -33,6 +33,8 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.feign.ISysClient;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springblade.system.utils.MpUserAuth;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
@@ -72,7 +74,12 @@ public class OAuth2UserInfoResponseAdvice implements ResponseBodyAdvice<Object>
|
||||
if (userInfo == null) {
|
||||
return body;
|
||||
}
|
||||
userInfo.setPermission(loadPermission(bladeUser.getRoleId()));
|
||||
if (MpUserAuth.isMpUser(bladeUser)) {
|
||||
// 小程序用户:按身份直接生成权限,不走系统角色菜单
|
||||
userInfo.setPermission(MpUserIdentity.toPermissions(bladeUser.getRoleName()));
|
||||
} else {
|
||||
userInfo.setPermission(loadPermission(bladeUser.getRoleId()));
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@ public class Oauth2SmsEndpoint {
|
||||
return R.fail(USER_PHONE_NOT_FOUND);
|
||||
}
|
||||
// 校验手机是否已注册,防止恶意发送验证码
|
||||
OAuth2Request request = OAuth2Request.create();
|
||||
// buildArgs:带上 User-Type 等请求头,小程序传 User-Type=mp 时查 blade_mp_user
|
||||
OAuth2Request request = OAuth2Request.create().buildArgs();
|
||||
request.setTenantId(tenantId);
|
||||
OAuth2User oAuth2User = userService.loadByPhone(decryptedPhone, request);
|
||||
if (oAuth2User == null) {
|
||||
|
||||
@@ -2,10 +2,12 @@ package org.springblade.auth.granter;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.auth.utils.TokenUtil;
|
||||
import org.springblade.core.oauth2.exception.UserInvalidException;
|
||||
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
@@ -13,7 +15,8 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.core.tool.utils.WebUtil;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.thirdparty.wechat.constant.WechatMiniConstant;
|
||||
import org.springblade.thirdparty.wechat.exception.WechatMiniException;
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO;
|
||||
@@ -25,7 +28,8 @@ import org.springframework.stereotype.Component;
|
||||
* 微信小程序手机号一键登录。
|
||||
* <p>
|
||||
* grant_type=wechat_applet,参数:loginCode(wx.login)、phoneCode(getPhoneNumber)。
|
||||
* 流程:换 openid + 手机号 → 按手机号查用户(不存在则拒绝)→ 记录 openid → 发令牌(与密码登录一致)。
|
||||
* 流程:换 openid + 手机号 → 按手机号查小程序用户(blade_mp_user,不存在则拒绝)
|
||||
* → 记录 openid → 按身份发令牌(不依赖系统角色)。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -33,17 +37,17 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
||||
|
||||
private final OAuth2UserService userService;
|
||||
private final IWechatMiniService wechatMiniService;
|
||||
private final IUserClient userClient;
|
||||
private final IMpUserClient mpUserClient;
|
||||
|
||||
public WechatMiniTokenGranter(OAuth2ClientService clientService,
|
||||
OAuth2UserService userService,
|
||||
PasswordHandler passwordHandler,
|
||||
IWechatMiniService wechatMiniService,
|
||||
IUserClient userClient) {
|
||||
IMpUserClient mpUserClient) {
|
||||
super(clientService, userService, passwordHandler);
|
||||
this.userService = userService;
|
||||
this.wechatMiniService = wechatMiniService;
|
||||
this.userClient = userClient;
|
||||
this.mpUserClient = mpUserClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,7 +58,7 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
||||
@Override
|
||||
public OAuth2User user(OAuth2Request request) {
|
||||
// 先校验客户端是否允许该授权类型,避免先调微信再因客户端配置失败
|
||||
var oauthClient = client(request);
|
||||
OAuth2Client oauthClient = client(request);
|
||||
|
||||
HttpServletRequest httpRequest = WebUtil.getRequest();
|
||||
String loginCode = httpRequest.getParameter("loginCode");
|
||||
@@ -78,16 +82,21 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
||||
throw new UserInvalidException("未获取到微信手机号");
|
||||
}
|
||||
|
||||
OAuth2User user = userService.loadByPhone(phone, request);
|
||||
if (!userService.validateUser(user)) {
|
||||
R<MpUserInfo> mpUserResult = mpUserClient.userInfoByPhone(request.getTenantId(), phone);
|
||||
if (mpUserResult == null || !mpUserResult.isSuccess() || mpUserResult.getData() == null
|
||||
|| mpUserResult.getData().getUser() == null) {
|
||||
throw new UserInvalidException("用户不存在,无法登录");
|
||||
}
|
||||
|
||||
R<Boolean> bindResult = userClient.bindWxMiniOpenId(
|
||||
OAuth2User user = TokenUtil.convertMpUser(mpUserResult.getData());
|
||||
if (!userService.validateUser(user)) {
|
||||
throw new UserInvalidException("用户身份无效,无法登录");
|
||||
}
|
||||
|
||||
R<Boolean> bindResult = mpUserClient.bindOpenId(
|
||||
request.getTenantId(),
|
||||
Func.toLong(user.getUserId()),
|
||||
session.getOpenid(),
|
||||
phone
|
||||
session.getOpenid()
|
||||
);
|
||||
if (bindResult == null || !bindResult.isSuccess()) {
|
||||
String msg = bindResult != null ? bindResult.getMsg() : "绑定 openid 失败";
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.auth.service;
|
||||
@@ -35,8 +32,11 @@ import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.entity.UserInfo;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springblade.system.pojo.enums.UserType;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -49,63 +49,121 @@ import java.util.Optional;
|
||||
@RequiredArgsConstructor
|
||||
public class BladeUserDetailService implements OAuth2UserService {
|
||||
private final IUserClient userClient;
|
||||
private final IMpUserClient mpUserClient;
|
||||
|
||||
@Override
|
||||
public OAuth2User loadByUserId(String userId, OAuth2Request request) {
|
||||
// 获取用户参数
|
||||
String userType = Optional.ofNullable(request.getUserType())
|
||||
.filter(s -> !StringUtil.isBlank(s))
|
||||
.orElse(UserType.WEB.getName());
|
||||
|
||||
// 获取用户信息
|
||||
R<UserInfo> result = userClient.userInfo(Func.toLong(userId), userType);
|
||||
if (!result.isSuccess()) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
// 小程序刷新/鉴权:User-Type=mp 时只查 blade_mp_user
|
||||
if (isMpLoginRequest(request)) {
|
||||
OAuth2User mpUser = loadMpUserById(userId);
|
||||
if (mpUser == null) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
}
|
||||
return mpUser;
|
||||
}
|
||||
// 构建oauth2用户信息
|
||||
return TokenUtil.convertUser(result.getData(), request);
|
||||
|
||||
String userType = resolveUserType(request);
|
||||
R<UserInfo> result = userClient.userInfo(Func.toLong(userId), userType);
|
||||
if (result != null && result.isSuccess() && result.getData() != null
|
||||
&& result.getData().getUser() != null) {
|
||||
return TokenUtil.convertUser(result.getData(), request);
|
||||
}
|
||||
|
||||
// 兼容:刷新令牌未带 User-Type=mp 时,回落查询小程序用户
|
||||
OAuth2User mpUser = loadMpUserById(userId);
|
||||
if (mpUser != null) {
|
||||
return mpUser;
|
||||
}
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User loadByUsername(String username, OAuth2Request request) {
|
||||
// 获取用户参数
|
||||
String userType = Optional.ofNullable(request.getUserType())
|
||||
.filter(s -> !StringUtil.isBlank(s))
|
||||
.orElse(UserType.WEB.getName());
|
||||
String tenantId = request.getTenantId();
|
||||
|
||||
// 获取用户信息
|
||||
// 小程序账号密码 / 图形验证码登录:仅查 blade_mp_user
|
||||
if (isMpLoginRequest(request)) {
|
||||
R<MpUserInfo> mpResult = mpUserClient.userInfoByAccount(tenantId, username);
|
||||
if (mpResult == null || !mpResult.isSuccess() || mpResult.getData() == null) {
|
||||
return null;
|
||||
}
|
||||
return TokenUtil.convertMpUser(mpResult.getData());
|
||||
}
|
||||
|
||||
String userType = resolveUserType(request);
|
||||
R<UserInfo> result = userClient.userInfo(tenantId, username, userType);
|
||||
if (!result.isSuccess()) {
|
||||
if (result == null || !result.isSuccess()) {
|
||||
return null;
|
||||
}
|
||||
// 构建oauth2用户信息
|
||||
return TokenUtil.convertUser(result.getData(), request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User loadByPhone(String phone, OAuth2Request request) {
|
||||
// 获取用户参数
|
||||
String userType = Optional.ofNullable(request.getUserType())
|
||||
.filter(s -> !StringUtil.isBlank(s))
|
||||
.orElse(UserType.WEB.getName());
|
||||
String tenantId = request.getTenantId();
|
||||
|
||||
// 获取用户信息
|
||||
R<UserInfo> result = userClient.userInfoByPhone(tenantId, phone, userType);
|
||||
if (!result.isSuccess()) {
|
||||
return null;
|
||||
// 小程序短信登录:仅查 blade_mp_user
|
||||
if (isMpLoginRequest(request)) {
|
||||
return loadMpUserByPhone(tenantId, phone);
|
||||
}
|
||||
// 构建oauth2用户信息
|
||||
return TokenUtil.convertUser(result.getData(), request);
|
||||
|
||||
String userType = resolveUserType(request);
|
||||
R<UserInfo> result = userClient.userInfoByPhone(tenantId, phone, userType);
|
||||
if (result != null && result.isSuccess() && result.getData() != null) {
|
||||
return TokenUtil.convertUser(result.getData(), request);
|
||||
}
|
||||
// 发短信校验等未带 User-Type 时,回落小程序用户,避免 mp 账号收不到验证码
|
||||
return loadMpUserByPhone(tenantId, phone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateUser(OAuth2User user) {
|
||||
return Optional.ofNullable(user)
|
||||
.filter(u -> u.getUserId() != null && !u.getUserId().isEmpty()) // 检查userId不为空
|
||||
.filter(u -> u.getAuthorities() != null && !u.getAuthorities().isEmpty()) // 检查authorities不为空
|
||||
.isPresent(); // 如果上述条件都满足,则返回true,否则返回false
|
||||
.filter(u -> u.getUserId() != null && !u.getUserId().isEmpty())
|
||||
.filter(u -> u.getAuthorities() != null && !u.getAuthorities().isEmpty())
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否小程序登录:请求头 User-Type=mp(Web 与小程序共用 saber3 客户端,不能按 clientId 分流)
|
||||
*/
|
||||
private boolean isMpLoginRequest(OAuth2Request request) {
|
||||
return request != null && isMpUserType(request.getUserType());
|
||||
}
|
||||
|
||||
private boolean isMpUserType(String userType) {
|
||||
return MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(Func.toStr(userType));
|
||||
}
|
||||
|
||||
private String resolveUserType(OAuth2Request request) {
|
||||
return Optional.ofNullable(request.getUserType())
|
||||
.filter(s -> !StringUtil.isBlank(s))
|
||||
.orElse(UserType.WEB.getName());
|
||||
}
|
||||
|
||||
private OAuth2User loadMpUserById(String userId) {
|
||||
if (Func.isBlank(userId)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
R<MpUserInfo> result = mpUserClient.userInfoById(Func.toLong(userId));
|
||||
if (result != null && result.isSuccess() && result.getData() != null
|
||||
&& result.getData().getUser() != null) {
|
||||
return TokenUtil.convertMpUser(result.getData());
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
// 非小程序用户场景忽略
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OAuth2User loadMpUserByPhone(String tenantId, String phone) {
|
||||
R<MpUserInfo> result = mpUserClient.userInfoByPhone(tenantId, phone);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
||||
return null;
|
||||
}
|
||||
return TokenUtil.convertMpUser(result.getData());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,12 +28,15 @@ package org.springblade.auth.utils;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.pojo.entity.UserInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springblade.system.utils.MpUserAuth;
|
||||
|
||||
/**
|
||||
* 认证工具类
|
||||
@@ -75,6 +78,7 @@ public class TokenUtil {
|
||||
userDetail.setRealName(user.getRealName());
|
||||
userDetail.setAccount(user.getAccount());
|
||||
userDetail.setPassword(user.getPassword());
|
||||
userDetail.setPhone(user.getPhone());
|
||||
userDetail.setDeptId(user.getDeptId());
|
||||
userDetail.setPostId(user.getPostId());
|
||||
userDetail.setRoleId(user.getRoleId());
|
||||
@@ -85,4 +89,40 @@ public class TokenUtil {
|
||||
return userDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序用户转换为OAuth2标准用户(身份直接写入 authorities,不走系统角色)
|
||||
*
|
||||
* @param userInfo 小程序用户信息
|
||||
* @return OAuth2User
|
||||
*/
|
||||
public static OAuth2User convertMpUser(MpUserInfo userInfo) {
|
||||
if (userInfo == null || userInfo.getUser() == null) {
|
||||
return null;
|
||||
}
|
||||
MpUser user = userInfo.getUser();
|
||||
OAuth2UserDetail userDetail = new OAuth2UserDetail();
|
||||
userDetail.setUserId(String.valueOf(user.getId()));
|
||||
userDetail.setTenantId(user.getTenantId());
|
||||
userDetail.setName(user.getName());
|
||||
userDetail.setRealName(user.getRealName());
|
||||
userDetail.setAccount(user.getAccount());
|
||||
userDetail.setPassword(user.getPassword());
|
||||
userDetail.setPhone(user.getPhone());
|
||||
// 严格令牌模式要求 deptId/roleId 非空且 >=0;小程序用户用占位值,身份写在 roleName/authorities
|
||||
userDetail.setDeptId(Func.toStr(user.getDeptId(), MpUserAuth.PLACEHOLDER_ID));
|
||||
if (Func.isBlank(userDetail.getDeptId()) || "-1".equals(userDetail.getDeptId())) {
|
||||
userDetail.setDeptId(MpUserAuth.PLACEHOLDER_ID);
|
||||
}
|
||||
userDetail.setPostId(Func.toStr(user.getPostId(), MpUserAuth.PLACEHOLDER_ID));
|
||||
userDetail.setRoleId(MpUserAuth.PLACEHOLDER_ID);
|
||||
userDetail.setRoleName(Func.join(userInfo.getIdentities()));
|
||||
userDetail.setAvatar(user.getAvatar());
|
||||
userDetail.setAuthorities(userInfo.getIdentities());
|
||||
userDetail.setPermissions(userInfo.getPermissions());
|
||||
userDetail.setDetail(userInfo.getDetail() == null
|
||||
? Kv.create().set("type", MpUserIdentity.DETAIL_TYPE)
|
||||
: userInfo.getDetail());
|
||||
return userDetail;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.common.excel;
|
||||
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 单元格读取失败异常 —— 用于「异常不是 fastexcel 抛出、但同样发生在读取阶段」的场景。
|
||||
* <p>
|
||||
* fastexcel 自身解析失败时抛 {@code ExcelDataConvertException},位置信息齐全,
|
||||
* {@link ImportFailureExcelUtil#exportReadFailure} 可以直接定位到行与列。但有些模块
|
||||
* 在读取阶段有自己的解析逻辑(例如运输对账的动态费用列:模板列不固定,由
|
||||
* {@code VehicleReconciliationFeeReader} 自行按表头解析),这些代码如果只是
|
||||
* {@code catch (NumberFormatException)} 后返回 {@code BigDecimal.ZERO},
|
||||
* 脏数据就会静默入库(费用 0.00 还会参与结算金额与自动匹配)。
|
||||
* <p>
|
||||
* 正确做法是改抛本异常,把位置信息补齐,让导出工具能把原因挂到正确的行、标红正确的列。
|
||||
* <p>
|
||||
* 本类位于 blade-common,供 blade-system 与 blade-transport 等模块共用。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class ImportCellReadException extends ServiceException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 出错的单元格所在的 Excel 行号,<b>绝对 0 基</b>(表头占 0,第一个数据行为 1)。
|
||||
* 与 {@code ExcelDataConvertException.getRowIndex()} 保持同一口径,
|
||||
* 便于导出工具统一换算。
|
||||
*/
|
||||
private final transient Integer rowIndex;
|
||||
|
||||
/**
|
||||
* 出错的列名(导入模板表头原文),会写进原因文案里,导出工具靠它标红对应列。
|
||||
*/
|
||||
private final transient String columnName;
|
||||
|
||||
public ImportCellReadException(Integer rowIndex, String columnName, String cellValue) {
|
||||
super(buildMessage(rowIndex, columnName, cellValue));
|
||||
this.rowIndex = rowIndex;
|
||||
this.columnName = columnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文案格式与 {@link ImportFailureExcelUtil#describeReadFailure} 保持一致:
|
||||
* 「第N行「列名」格式不正确:<原值>」。列名必须包在「」里,导出工具才能标红该列。
|
||||
*/
|
||||
private static String buildMessage(Integer rowIndex, String columnName, String cellValue) {
|
||||
String rowText = rowIndex == null ? "第?行" : "第" + (rowIndex + 1) + "行";
|
||||
String columnText = columnName == null || columnName.isBlank() ? "该列" : "「" + columnName + "」";
|
||||
StringBuilder message = new StringBuilder(rowText).append(columnText).append("格式不正确");
|
||||
if (cellValue != null && !cellValue.isBlank()) {
|
||||
message.append(":").append(cellValue);
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
public Integer getRowIndex() {
|
||||
return rowIndex;
|
||||
}
|
||||
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,7 +45,9 @@ import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -80,6 +82,443 @@ public class ImportFailureExcelUtil {
|
||||
addValidationError(validationErrors, value != null && value.length() > maxLength, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把「读取 Excel 阶段」的转换异常转成可读的失败原因。
|
||||
* <p>
|
||||
* 背景:单元格里填了无法转换的内容(如金额列填「一万二千元」)时,EasyExcel 会在
|
||||
* {@code ExcelUtil.read(...)} 阶段就抛 {@link cn.idev.excel.exception.ExcelDataConvertException},
|
||||
* 根本走不到行级校验;若不兜底,用户看到的是
|
||||
* 「Convert data ... to class java.math.BigDecimal error」这类技术文案。
|
||||
* <p>
|
||||
* 本方法据异常里的行列位置与目标类型,生成「第N行「列名」格式不正确:<原值>」这样
|
||||
* 用户能看懂、能定位的文案;列名靠表头反射拿 {@code @ExcelProperty},拿不到则退回列序号。
|
||||
*
|
||||
* @param exception 读取阶段抛出的异常
|
||||
* @param excelClass 导入模型类(用于按列序号反查表头名)
|
||||
* @return 可读的失败原因;非转换类异常返回 null(交由上层按原样处理)
|
||||
*/
|
||||
public static String describeReadFailure(Throwable exception, Class<?> excelClass) {
|
||||
cn.idev.excel.exception.ExcelDataConvertException convertException = findCause(exception,
|
||||
cn.idev.excel.exception.ExcelDataConvertException.class);
|
||||
if (convertException == null) {
|
||||
// 不是 fastexcel 抛的转换异常:模块自带的读取期解析逻辑(如运输对账的动态费用列)
|
||||
// 用 ImportCellReadException 补齐了位置信息,直接透传它已经拼好的原因文案。
|
||||
ImportCellReadException cellReadException = findCause(exception, ImportCellReadException.class);
|
||||
return cellReadException == null ? null : cellReadException.getMessage();
|
||||
}
|
||||
Integer rowIndex = convertException.getRowIndex();
|
||||
Integer columnIndex = convertException.getColumnIndex();
|
||||
String columnName = columnName(excelClass, columnIndex);
|
||||
// Excel 行号:getRowIndex() 是绝对 0 基行号(表头占 0),故 Excel 行号 = rowIndex + 1
|
||||
String rowText = rowIndex == null ? "第?行" : "第" + (rowIndex + 1) + "行";
|
||||
String columnText = columnName == null ? ("第" + (columnIndex == null ? "?" : columnIndex + 1) + "列") : "「" + columnName + "」";
|
||||
String value = cellValue(convertException);
|
||||
StringBuilder message = new StringBuilder(rowText).append(columnText).append("格式不正确");
|
||||
if (value != null && !value.isBlank()) {
|
||||
message.append(":").append(value);
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在异常链里查找指定类型的 cause
|
||||
*/
|
||||
private static <T extends Throwable> T findCause(Throwable throwable, Class<T> type) {
|
||||
for (Throwable current = throwable; current != null; current = current.getCause()) {
|
||||
if (type.isInstance(current)) {
|
||||
return type.cast(current);
|
||||
}
|
||||
if (current.getCause() == current) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按列序号反查表头名(@ExcelProperty 的 value,取不到则用字段名)
|
||||
*/
|
||||
private static String columnName(Class<?> excelClass, Integer columnIndex) {
|
||||
if (excelClass == null || columnIndex == null) {
|
||||
return null;
|
||||
}
|
||||
List<Field> fields = excelFields(excelClass);
|
||||
if (columnIndex < 0 || columnIndex >= fields.size()) {
|
||||
return null;
|
||||
}
|
||||
Field field = fields.get(columnIndex);
|
||||
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
|
||||
if (excelProperty == null) {
|
||||
return field.getName();
|
||||
}
|
||||
String[] value = excelProperty.value();
|
||||
String columnName = value.length == 0 ? field.getName() : value[0];
|
||||
return columnName.replace("*", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 取触发转换失败的单元格原值
|
||||
*/
|
||||
private static String cellValue(cn.idev.excel.exception.ExcelDataConvertException exception) {
|
||||
try {
|
||||
cn.idev.excel.metadata.data.CellData<?> cellData = exception.getCellData();
|
||||
if (cellData == null) {
|
||||
return null;
|
||||
}
|
||||
Object data = cellData.getData();
|
||||
if (data == null) {
|
||||
data = cellData.getStringValue();
|
||||
}
|
||||
return data == null ? null : String.valueOf(data).trim();
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 以「原始单元格文本」方式重读导入文件,得到的行用于构造失败明细。
|
||||
* <p>
|
||||
* 优先使用 Excel 文件真实表头(含动态费用列等模型类未声明的列),保证失败明细
|
||||
* 与原导入模板列一致,用户修正后可直接用该文件重新导入。
|
||||
* <p>
|
||||
* 用途:当 {@code ExcelUtil.read(file, XxxExcel.class)} 因单元格无法转换而失败时,
|
||||
* 已解析的实体列表是不完整的(异常中断了整批读取)。此时用本方法重读一次:
|
||||
* 不做类型转换(直接读成 Map),因此绝不会再抛转换异常,可拿到**原表全部行**,
|
||||
* 供导出「原表全量 + 仅出错行带原因」的失败明细。
|
||||
*
|
||||
* @param file 上传的 Excel 文件
|
||||
* @param excelClass 导入模型类(表头缺失时按列序号反查表头名)
|
||||
* @return 按行组织的明细数据:每行是「表头名 → 单元格文本」的有序 Map;
|
||||
* 读不出内容时返回空列表(调用方按空明细处理)
|
||||
*/
|
||||
public static List<Map<String, String>> readRawRows(java.io.InputStream file, Class<?> excelClass) {
|
||||
List<Map<Integer, String>> rawRows = new ArrayList<>();
|
||||
Map<Integer, String> headerByIndex = new LinkedHashMap<>();
|
||||
try {
|
||||
cn.idev.excel.read.builder.ExcelReaderBuilder builder = cn.idev.excel.FastExcel
|
||||
.read(file, new cn.idev.excel.event.AnalysisEventListener<Map<Integer, String>>() {
|
||||
@Override
|
||||
public void invokeHeadMap(Map<Integer, String> headMap, cn.idev.excel.context.AnalysisContext context) {
|
||||
if (headMap == null) {
|
||||
return;
|
||||
}
|
||||
headMap.forEach((index, header) -> {
|
||||
if (index == null) {
|
||||
return;
|
||||
}
|
||||
String name = header == null ? "" : header.trim();
|
||||
// 历史失败明细再导入时忽略旧的失败原因列,导出时重新追加
|
||||
if (!name.isEmpty() && !FAILURE_REASON.equals(name)) {
|
||||
headerByIndex.put(index, name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Map<Integer, String> data, cn.idev.excel.context.AnalysisContext context) {
|
||||
rawRows.add(data == null ? new LinkedHashMap<>() : new LinkedHashMap<>(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(cn.idev.excel.context.AnalysisContext context) {
|
||||
// 无需处理
|
||||
}
|
||||
});
|
||||
builder.headRowNumber(1).sheet().doRead();
|
||||
} catch (Exception ignored) {
|
||||
// 重读本身失败时退回空明细,由调用方按「无法读取」提示
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<Map<String, String>> rows = new ArrayList<>();
|
||||
for (Map<Integer, String> rawRow : rawRows) {
|
||||
Map<String, String> row = new LinkedHashMap<>();
|
||||
Set<Integer> columnIndexes = new LinkedHashSet<>(headerByIndex.keySet());
|
||||
if (rawRow != null) {
|
||||
columnIndexes.addAll(rawRow.keySet());
|
||||
}
|
||||
List<Integer> sortedIndexes = new ArrayList<>(columnIndexes);
|
||||
Collections.sort(sortedIndexes);
|
||||
for (Integer columnIndex : sortedIndexes) {
|
||||
if (columnIndex == null) {
|
||||
continue;
|
||||
}
|
||||
String columnName = headerByIndex.get(columnIndex);
|
||||
if (columnName == null || columnName.isBlank()) {
|
||||
columnName = columnName(excelClass, columnIndex);
|
||||
}
|
||||
if (columnName == null || columnName.isBlank()) {
|
||||
columnName = "第" + (columnIndex + 1) + "列";
|
||||
}
|
||||
if (FAILURE_REASON.equals(columnName)) {
|
||||
continue;
|
||||
}
|
||||
row.put(columnName, rawRow == null ? null : rawRow.get(columnIndex));
|
||||
}
|
||||
rows.add(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按原上传文件真实表头导出失败明细(含动态列),并追加「导入失败原因」。
|
||||
* <p>
|
||||
* 适用于运输对账等「模板列 = 固定列 + 动态费用列」场景:按实体字段反射导出
|
||||
* 会丢掉动态列,用户无法用失败文件修正后重新导入。本方法重读原文件全量列,
|
||||
* 再按 failureList 行序挂上失败原因。
|
||||
*
|
||||
* @param response 响应
|
||||
* @param fileName 文件名
|
||||
* @param sheetName 工作表名
|
||||
* @param file 原导入文件
|
||||
* @param failureList 校验失败列表(与原表行序一致,成功行 errorMessage 可为空)
|
||||
* @param excelClass 导入模型类(原文件重读失败时回退到按实体导出)
|
||||
*/
|
||||
public static void exportOriginalWithFailures(HttpServletResponse response, String fileName, String sheetName,
|
||||
org.springframework.web.multipart.MultipartFile file,
|
||||
List<?> failureList, Class<?> excelClass) {
|
||||
List<Map<String, String>> rawRows = new ArrayList<>();
|
||||
if (file != null && !file.isEmpty()) {
|
||||
try (java.io.InputStream inputStream = file.getInputStream()) {
|
||||
rawRows = readRawRows(inputStream, excelClass);
|
||||
} catch (IOException ignored) {
|
||||
rawRows = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
if (rawRows.isEmpty()) {
|
||||
// 原文件读不出时回退实体导出,至少保证失败原因可下载
|
||||
if (failureList != null && !failureList.isEmpty()) {
|
||||
export(response, fileName, sheetName, failureList, excelClass);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Map<Integer, String> failureMessageByRow = new HashMap<>();
|
||||
if (failureList != null) {
|
||||
for (int index = 0; index < failureList.size(); index++) {
|
||||
Object message = errorMessage(failureList.get(index));
|
||||
if (message != null && !String.valueOf(message).isBlank()) {
|
||||
failureMessageByRow.put(index, String.valueOf(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
exportRawFailure(response, fileName, sheetName, rawRows, failureMessageByRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出「读取阶段失败」明细:原表全量行 + 末尾「导入失败原因」列。
|
||||
* <p>
|
||||
* 与 {@link #export} 的区别:本方法直接按重读得到的「表头名 → 单元格文本」写出,
|
||||
* 不依赖导入模型的字段反射,因此适用于「单元格无法转换为实体字段」的场景
|
||||
* (此时实体列表不完整,无法走常规导出)。
|
||||
*
|
||||
* @param response 响应
|
||||
* @param fileName 文件名
|
||||
* @param sheetName 工作表名
|
||||
* @param rawRows {@link #readRawRows} 得到的原表全部行
|
||||
* @param failureMessageByRow 行号(0 基,对应 rawRows 下标)→ 该行失败原因;为 null 表示该行无错
|
||||
*/
|
||||
public static void exportRawFailure(HttpServletResponse response, String fileName, String sheetName,
|
||||
List<Map<String, String>> rawRows, Map<Integer, String> failureMessageByRow) {
|
||||
response.setContentType("application/vnd.ms-excel");
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
||||
response.setHeader("Content-disposition", "attachment;filename=" + encodeFileName + ".xlsx");
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Map<String, String> row : rawRows) {
|
||||
for (String key : row.keySet()) {
|
||||
if (!headers.contains(key)) {
|
||||
headers.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
headers.add(FAILURE_REASON);
|
||||
List<List<String>> head = new ArrayList<>();
|
||||
for (String header : headers) {
|
||||
head.add(List.of(header));
|
||||
}
|
||||
List<List<Object>> rows = new ArrayList<>();
|
||||
Map<Integer, Set<Integer>> redColumnsByRow = new HashMap<>();
|
||||
List<List<String>> headerKeywords = new ArrayList<>();
|
||||
for (int columnIndex = 0; columnIndex < headers.size() - 1; columnIndex++) {
|
||||
headerKeywords.add(headerKeywords(headers.get(columnIndex)));
|
||||
}
|
||||
for (int rowIndex = 0; rowIndex < rawRows.size(); rowIndex++) {
|
||||
Map<String, String> rawRow = rawRows.get(rowIndex);
|
||||
List<Object> row = new ArrayList<>();
|
||||
String failureMessage = failureMessageByRow == null ? null : failureMessageByRow.get(rowIndex);
|
||||
for (int columnIndex = 0; columnIndex < headers.size() - 1; columnIndex++) {
|
||||
row.add(rawRow.get(headers.get(columnIndex)));
|
||||
}
|
||||
row.add(failureMessage == null ? "" : failureMessage);
|
||||
rows.add(row);
|
||||
redColumnsByRow.put(rowIndex, resolveRawRedColumns(failureMessage, headerKeywords, headers.size() - 1));
|
||||
}
|
||||
try {
|
||||
FastExcel.write(response.getOutputStream())
|
||||
.registerWriteHandler(new RawFailureCellStyleHandler(rows, redColumnsByRow))
|
||||
.head(head)
|
||||
.sheet(sheetName)
|
||||
.doWrite(rows);
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("导出导入失败明细失败", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取阶段失败明细的单元格样式处理器:统一列宽 + 标红失败原因列与出错列
|
||||
*/
|
||||
private static class RawFailureCellStyleHandler implements CellWriteHandler {
|
||||
|
||||
private final int failureReasonColumnIndex;
|
||||
private final Map<Integer, Set<Integer>> redColumnsByRow;
|
||||
private final Map<Short, CellStyle> redStyleCache = new HashMap<>();
|
||||
private final Map<Short, CellStyle> noWrapStyleCache = new HashMap<>();
|
||||
private final Map<Integer, Integer> columnWidthCache = new HashMap<>();
|
||||
|
||||
private RawFailureCellStyleHandler(List<List<Object>> rows, Map<Integer, Set<Integer>> redColumnsByRow) {
|
||||
this.failureReasonColumnIndex = rows.isEmpty() ? 0 : rows.get(0).size() - 1;
|
||||
this.redColumnsByRow = redColumnsByRow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
|
||||
List<WriteCellData<?>> cellDataList, Cell cell, cn.idev.excel.metadata.Head head,
|
||||
Integer relativeRowIndex, Boolean isHead) {
|
||||
if (Boolean.TRUE.equals(isHead)) {
|
||||
adjustColumnWidth(cell);
|
||||
markNoWrap(cell);
|
||||
return;
|
||||
}
|
||||
adjustColumnWidth(cell);
|
||||
if (relativeRowIndex == null || relativeRowIndex < 0) {
|
||||
return;
|
||||
}
|
||||
Set<Integer> redColumns = redColumnsByRow.get(relativeRowIndex);
|
||||
if (redColumns != null && redColumns.contains(cell.getColumnIndex())) {
|
||||
markRed(cell);
|
||||
}
|
||||
}
|
||||
|
||||
private void markRed(Cell cell) {
|
||||
CellStyle currentStyle = cell.getCellStyle();
|
||||
CellStyle redStyle = redStyleCache.computeIfAbsent(currentStyle.getIndex(), styleIndex -> {
|
||||
Workbook workbook = cell.getSheet().getWorkbook();
|
||||
CellStyle newStyle = workbook.createCellStyle();
|
||||
newStyle.cloneStyleFrom(currentStyle);
|
||||
Font font = workbook.createFont();
|
||||
font.setColor(IndexedColors.RED.getIndex());
|
||||
newStyle.setFont(font);
|
||||
newStyle.setWrapText(true);
|
||||
return newStyle;
|
||||
});
|
||||
cell.setCellStyle(redStyle);
|
||||
}
|
||||
|
||||
private void markNoWrap(Cell cell) {
|
||||
CellStyle currentStyle = cell.getCellStyle();
|
||||
CellStyle noWrapStyle = noWrapStyleCache.computeIfAbsent(currentStyle.getIndex(), styleIndex -> {
|
||||
CellStyle newStyle = cell.getSheet().getWorkbook().createCellStyle();
|
||||
newStyle.cloneStyleFrom(currentStyle);
|
||||
newStyle.setWrapText(false);
|
||||
return newStyle;
|
||||
});
|
||||
cell.setCellStyle(noWrapStyle);
|
||||
}
|
||||
|
||||
private void adjustColumnWidth(Cell cell) {
|
||||
int columnIndex = cell.getColumnIndex();
|
||||
int columnWidth = Math.min(Math.max(displayWidth(cell.toString()) + 4, 12), 80) * 256;
|
||||
Integer currentWidth = columnWidthCache.get(columnIndex);
|
||||
if (currentWidth == null || columnWidth > currentWidth) {
|
||||
columnWidthCache.put(columnIndex, columnWidth);
|
||||
cell.getSheet().setColumnWidth(columnIndex, columnWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private int displayWidth(String value) {
|
||||
int width = 0;
|
||||
for (int index = 0; index < value.length(); index++) {
|
||||
width += value.charAt(index) > 255 ? 2 : 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取阶段异常的一站式兜底:重读原表 → 导出「全量行 + 出错行原因」失败明细。
|
||||
* <p>
|
||||
* 控制器用法:在导入的 try 块末尾追加
|
||||
* <pre>
|
||||
* } catch (Exception exception) {
|
||||
* if (ImportFailureExcelUtil.exportReadFailure(response, fileName, sheetName, file, XxxExcel.class, exception)) {
|
||||
* return null;
|
||||
* }
|
||||
* throw exception;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return true 表示已识别为「单元格转换失败」并已写出明细(调用方直接 return null);
|
||||
* false 表示不是转换类异常(调用方应继续抛出,保持原有处理)
|
||||
*/
|
||||
public static boolean exportReadFailure(HttpServletResponse response, String fileName, String sheetName,
|
||||
org.springframework.web.multipart.MultipartFile file,
|
||||
Class<?> excelClass, Throwable exception) {
|
||||
String failureMessage = describeReadFailure(exception, excelClass);
|
||||
if (failureMessage == null) {
|
||||
return false;
|
||||
}
|
||||
List<Map<String, String>> rawRows;
|
||||
try (java.io.InputStream inputStream = file.getInputStream()) {
|
||||
rawRows = readRawRows(inputStream, excelClass);
|
||||
} catch (IOException ioException) {
|
||||
rawRows = new ArrayList<>();
|
||||
}
|
||||
Map<Integer, String> failureMessageByRow = new HashMap<>();
|
||||
if (rawRows.isEmpty()) {
|
||||
// 重读也拿不到内容时,退化为「仅失败原因」一行,至少让用户看到可读原因
|
||||
Map<String, String> single = new java.util.LinkedHashMap<>();
|
||||
single.put(FAILURE_REASON, failureMessage);
|
||||
rawRows.add(single);
|
||||
failureMessageByRow.put(0, failureMessage);
|
||||
exportRawFailure(response, fileName, sheetName, rawRows, failureMessageByRow);
|
||||
return true;
|
||||
}
|
||||
// 异常里的数据行下标(0 基)用于把原因挂到对应行
|
||||
Integer rowIndex = readFailureRowIndex(exception);
|
||||
int targetRow = rowIndex == null ? 0 : Math.max(0, Math.min(rowIndex, rawRows.size() - 1));
|
||||
failureMessageByRow.put(targetRow, failureMessage);
|
||||
exportRawFailure(response, fileName, sheetName, rawRows, failureMessageByRow);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取读取阶段异常中「失败数据行」的下标(0 基,只对数据行计数),供挂失败原因使用。
|
||||
* <p>
|
||||
* 无论哪种异常,源信息都是<b>绝对 0 基行号</b>(表头占 0,第一个数据行为 1),
|
||||
* 而这里要的下标是相对数据行的,故统一减 1。
|
||||
* 不减的话原因会整体下移一行:错误恰好落在最后一行时会因调用处的 clamping
|
||||
* 侥幸正确,落在首行/中间行就会标到不该标的行上,误导用户去改正确的那一行。
|
||||
*/
|
||||
private static Integer readFailureRowIndex(Throwable exception) {
|
||||
Integer excelRowIndex = readFailureExcelRowIndex(exception);
|
||||
return excelRowIndex == null ? null : Math.max(0, excelRowIndex - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取读取阶段异常的绝对 0 基行号(表头 = 0)。
|
||||
* <p>
|
||||
* 两条来源统一到这里:fastexcel 自带的 {@code ExcelDataConvertException},
|
||||
* 以及模块自行解析时抛出的 {@link ImportCellReadException}。
|
||||
*/
|
||||
private static Integer readFailureExcelRowIndex(Throwable exception) {
|
||||
cn.idev.excel.exception.ExcelDataConvertException convertException = findCause(exception,
|
||||
cn.idev.excel.exception.ExcelDataConvertException.class);
|
||||
if (convertException != null) {
|
||||
return convertException.getRowIndex();
|
||||
}
|
||||
ImportCellReadException cellReadException = findCause(exception, ImportCellReadException.class);
|
||||
return cellReadException == null ? null : cellReadException.getRowIndex();
|
||||
}
|
||||
|
||||
private static final String FAILURE_REASON = "导入失败原因";
|
||||
private static final String ERROR_MESSAGE_FIELD = "errorMessage";
|
||||
private static final String FAILURE_REASON_FIELD = "failureReason";
|
||||
@@ -247,6 +686,69 @@ public class ImportFailureExcelUtil {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按真实表头生成定位关键词(用于原表失败明细标红)
|
||||
*/
|
||||
private static List<String> headerKeywords(String header) {
|
||||
if (header == null || header.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
List<String> keywords = new ArrayList<>();
|
||||
keywords.add(header);
|
||||
keywords.addAll(Arrays.asList(header.replace("*", "").split("[//、()()\\s]+")));
|
||||
return keywords.stream()
|
||||
.map(ImportFailureExcelUtil::normalize)
|
||||
.filter(keyword -> keyword.length() >= 2)
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按失败原因命中表头关键词,计算需要标红的列(含失败原因列)
|
||||
*/
|
||||
private static Set<Integer> resolveRawRedColumns(String failureMessage, List<List<String>> headerKeywords,
|
||||
int failureReasonColumnIndex) {
|
||||
Set<Integer> redColumns = new LinkedHashSet<>();
|
||||
redColumns.add(failureReasonColumnIndex);
|
||||
String normalizedReason = normalize(failureMessage);
|
||||
if (normalizedReason.isEmpty()) {
|
||||
return redColumns;
|
||||
}
|
||||
List<int[]> matches = new ArrayList<>();
|
||||
for (int column = 0; column < headerKeywords.size(); column++) {
|
||||
for (String keyword : headerKeywords.get(column)) {
|
||||
int fromIndex = 0;
|
||||
while (true) {
|
||||
int index = normalizedReason.indexOf(keyword, fromIndex);
|
||||
if (index < 0) {
|
||||
break;
|
||||
}
|
||||
matches.add(new int[]{index, index + keyword.length(), column});
|
||||
fromIndex = index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int[] match : matches) {
|
||||
if (!isRawMatchSubsumed(match, matches)) {
|
||||
redColumns.add(match[2]);
|
||||
}
|
||||
}
|
||||
return redColumns;
|
||||
}
|
||||
|
||||
private static boolean isRawMatchSubsumed(int[] match, List<int[]> matches) {
|
||||
int matchLength = match[1] - match[0];
|
||||
for (int[] other : matches) {
|
||||
if (other[2] == match[2]) {
|
||||
continue;
|
||||
}
|
||||
if ((other[1] - other[0]) > matchLength && other[0] <= match[0] && other[1] >= match[1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class ImportFailureCellStyleHandler implements CellWriteHandler {
|
||||
|
||||
private final int failureReasonColumnIndex;
|
||||
|
||||
+9
-3
@@ -23,7 +23,7 @@
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.excel;
|
||||
package org.springblade.common.excel;
|
||||
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
|
||||
@@ -33,8 +33,14 @@ import java.util.List;
|
||||
/**
|
||||
* 导入失败异常,携带失败明细用于导出原表并标注错误。
|
||||
* <p>
|
||||
* 批量导入采用「全失败即整批回滚」语义:任一行校验失败都会抛出本异常触发事务回滚,
|
||||
* 失败明细在抛异常前已收集完毕,因此回滚不影响明细的内容。
|
||||
* 批量导入采用「任一行失败即整批回滚」语义:循环内先收集完全部错误明细,
|
||||
* 再抛出本异常触发事务回滚。明细在抛异常前已收集完毕,因此回滚不影响明细内容。
|
||||
* <p>
|
||||
* 注意:仅标注 {@code @Transactional} 并不足够。若循环内把异常吞掉(catch 后
|
||||
* 只往 errorList 塞数据并正常 return),方法会正常返回,Spring 会照常提交事务。
|
||||
* 必须抛出异常才能让事务真正回滚。
|
||||
* <p>
|
||||
* 本类位于 blade-common,供 blade-system 与 blade-transport 等模块共用。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@@ -34,9 +34,13 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 自定义Feign日志记录器,只记录出入参信息
|
||||
@@ -50,6 +54,7 @@ public class CustomFeignLogger extends Logger {
|
||||
private static final String IMAGE_CONTENT_TYPE_PREFIX = "image/";
|
||||
private static final String AUDIO_CONTENT_TYPE_PREFIX = "audio/";
|
||||
private static final String VIDEO_CONTENT_TYPE_PREFIX = "video/";
|
||||
private static final String MULTIPART_CONTENT_TYPE_PREFIX = "multipart/";
|
||||
|
||||
@Override
|
||||
protected void log(String configKey, String format, Object... args) {
|
||||
@@ -63,10 +68,13 @@ public class CustomFeignLogger extends Logger {
|
||||
Map<String, Collection<String>> headers = request.headers();
|
||||
byte[] body = request.body();
|
||||
|
||||
String contentType = getContentType(headers);
|
||||
String bodyStr;
|
||||
if (body == null || body.length == 0) {
|
||||
bodyStr = "[无]";
|
||||
} else if (isBinaryContent(getContentType(headers))) {
|
||||
} else if (isMultipartContent(contentType)) {
|
||||
bodyStr = parseMultipartBody(body, contentType);
|
||||
} else if (isBinaryContent(contentType)) {
|
||||
bodyStr = "[二进制数据,长度=" + body.length + "字节]";
|
||||
} else {
|
||||
bodyStr = new String(body, StandardCharsets.UTF_8);
|
||||
@@ -160,4 +168,75 @@ public class CustomFeignLogger extends Logger {
|
||||
|| lowerContentType.startsWith(AUDIO_CONTENT_TYPE_PREFIX)
|
||||
|| lowerContentType.startsWith(VIDEO_CONTENT_TYPE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为multipart内容
|
||||
*/
|
||||
private boolean isMultipartContent(String contentType) {
|
||||
return contentType != null && contentType.toLowerCase().startsWith(MULTIPART_CONTENT_TYPE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析multipart请求体,文件部分用占位符替代,文本字段保留打印
|
||||
*/
|
||||
private String parseMultipartBody(byte[] body, String contentType) {
|
||||
String boundary = extractBoundary(contentType);
|
||||
if (boundary == null) {
|
||||
return "[multipart数据,长度=" + body.length + "字节]";
|
||||
}
|
||||
String bodyStr = new String(body, StandardCharsets.UTF_8);
|
||||
String delimiter = "--" + boundary;
|
||||
String[] parts = bodyStr.split(delimiter);
|
||||
|
||||
List<String> fields = new ArrayList<>();
|
||||
for (String part : parts) {
|
||||
if (part.isEmpty() || part.startsWith("--") || part.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int headerEnd = part.indexOf("\r\n\r\n");
|
||||
if (headerEnd < 0) {
|
||||
continue;
|
||||
}
|
||||
String headers = part.substring(0, headerEnd);
|
||||
String value = part.substring(headerEnd + 4);
|
||||
if (value.endsWith("\r\n")) {
|
||||
value = value.substring(0, value.length() - 2);
|
||||
}
|
||||
|
||||
String name = extractParam(headers, "name");
|
||||
|
||||
if (headers.contains("filename=")) {
|
||||
String filename = extractParam(headers, "filename");
|
||||
fields.add(name + "=[文件: " + filename + "]");
|
||||
} else {
|
||||
fields.add(name + "=" + value);
|
||||
}
|
||||
}
|
||||
return fields.isEmpty() ? "[multipart数据]" : "{" + String.join(", ", fields) + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Content-Type中提取boundary
|
||||
*/
|
||||
private String extractBoundary(String contentType) {
|
||||
for (String param : contentType.split(";")) {
|
||||
param = param.trim();
|
||||
if (param.toLowerCase().startsWith("boundary=")) {
|
||||
return param.substring("boundary=".length()).trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从headers中提取参数值(如name、filename)
|
||||
*/
|
||||
private String extractParam(String headers, String paramName) {
|
||||
Pattern pattern = Pattern.compile(paramName + "=\"([^\"]*)\"");
|
||||
Matcher matcher = pattern.matcher(headers);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return paramName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ server:
|
||||
port: 80
|
||||
|
||||
spring:
|
||||
codec:
|
||||
# 运单批量导入等大 JSON / 失败明细导出场景,默认 256KB 会触发网关 500
|
||||
max-in-memory-size: 20MB
|
||||
cloud:
|
||||
gateway:
|
||||
server:
|
||||
|
||||
+23
@@ -7,6 +7,7 @@ import org.springblade.process.pojo.dto.BusinessProcessDeleteDTO;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
|
||||
import org.springblade.process.pojo.vo.BusinessProcessVO;
|
||||
import org.springblade.process.pojo.vo.MkFormBindVO;
|
||||
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
|
||||
import org.springblade.process.pojo.vo.ProcessTodoVO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
@@ -40,6 +41,8 @@ public interface IBusinessProcessClient {
|
||||
String QUERY_APPROVED_RECORD_LIST = API_PREFIX + "/queryApprovedRecordsNoAttachments";
|
||||
String GET_CURRENT_NODES = API_PREFIX + "/getCurrentNodes";
|
||||
String GET_PROCESS_INFO = API_PREFIX + "/getProcessInfo";
|
||||
String RESOLVE_BY_FORM_INSTANCE_ID = API_PREFIX + "/resolveByFormInstanceId";
|
||||
String MARK_PROCESS_FINISHED = API_PREFIX + "/markProcessFinished";
|
||||
|
||||
/**
|
||||
* 提交业务流程
|
||||
@@ -126,4 +129,24 @@ public interface IBusinessProcessClient {
|
||||
@GetMapping(GET_PROCESS_INFO)
|
||||
FR<Object> getProcessInfo(@RequestParam("processInstanceId") String processInstanceId,
|
||||
@RequestParam(value = "loginName", required = false) String loginName);
|
||||
|
||||
/**
|
||||
* 按表单实例id解析业务绑定
|
||||
*
|
||||
* @param formInstanceId 表单实例id
|
||||
* @return 绑定信息(含业务主键)
|
||||
*/
|
||||
@GetMapping(RESOLVE_BY_FORM_INSTANCE_ID)
|
||||
FR<MkFormBindVO> resolveByFormInstanceId(@RequestParam("formInstanceId") Long formInstanceId);
|
||||
|
||||
/**
|
||||
* 公开页流程结束时标记业务流程通过/驳回并完结
|
||||
*
|
||||
* @param processInstanceId 流程实例id
|
||||
* @param approved true=通过 false=驳回
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
@PostMapping(MARK_PROCESS_FINISHED)
|
||||
FR<Boolean> markProcessFinished(@RequestParam("processInstanceId") String processInstanceId,
|
||||
@RequestParam("approved") boolean approved);
|
||||
}
|
||||
|
||||
+6
@@ -65,6 +65,12 @@ public class BusinessProcess implements Serializable {
|
||||
*/
|
||||
@Schema(description = "业务id")
|
||||
private Long bizId;
|
||||
/**
|
||||
* MK表单实例id
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "MK表单实例id")
|
||||
private Long formInstanceId;
|
||||
/**
|
||||
* 流程实例id
|
||||
*/
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.process.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MK表单实例绑定表 实体类
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@TableName("blade_mk_form_bind")
|
||||
@Schema(description = "MkFormBind对象")
|
||||
public class MkFormBind implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "主键")
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private String tenantId;
|
||||
|
||||
@Schema(description = "业务类型")
|
||||
private String bizType;
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "业务主键")
|
||||
private Long bizId;
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "MK表单实例id")
|
||||
private Long formInstanceId;
|
||||
|
||||
@Schema(description = "MK流程实例id")
|
||||
private String processInstanceId;
|
||||
|
||||
@Schema(description = "是否当前生效(1是0否)")
|
||||
private Integer isCurrent;
|
||||
|
||||
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||
@Schema(description = "创建时间", hidden = true)
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
|
||||
@Schema(description = "更新时间", hidden = true)
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.process.pojo.vo;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* MK表单实例绑定 VO
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "MkFormBindVO")
|
||||
public class MkFormBindVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "业务主键")
|
||||
private Long bizId;
|
||||
|
||||
@Schema(description = "业务类型")
|
||||
private String bizType;
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "MK表单实例id")
|
||||
private Long formInstanceId;
|
||||
|
||||
@Schema(description = "MK流程实例id")
|
||||
private String processInstanceId;
|
||||
}
|
||||
+20
@@ -138,6 +138,18 @@ public class Dept extends TenantEntity {
|
||||
@Schema(description = "部门编码")
|
||||
private String deptCode;
|
||||
|
||||
/**
|
||||
* 金蝶部门代码
|
||||
*/
|
||||
@Schema(description = "金蝶部门代码")
|
||||
private String kingdeeDeptCode;
|
||||
|
||||
/**
|
||||
* 合同编码前缀(合同编号:前缀(年份)三位流水号)
|
||||
*/
|
||||
@Schema(description = "合同编码前缀")
|
||||
private String contractCodePrefix;
|
||||
|
||||
/**
|
||||
* 组织简称
|
||||
*/
|
||||
@@ -204,4 +216,12 @@ public class Dept extends TenantEntity {
|
||||
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
|
||||
private String oaSupSubComId;
|
||||
|
||||
/**
|
||||
* OA上次更新时间(OA 的 modified 字段),仅同步比对使用
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Schema(description = "OA上次更新时间", hidden = true)
|
||||
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
|
||||
private String oaModified;
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.springblade.system.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -63,6 +64,7 @@ public class RailwayStation extends BaseEntity {
|
||||
* 电报略码
|
||||
*/
|
||||
@Schema(description = "电报略码")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String telegraphCode;
|
||||
/**
|
||||
* 车站名称
|
||||
|
||||
+23
-18
@@ -4,29 +4,34 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 部门类型枚举
|
||||
* 组织类型枚举,与业务字典 organization_type 对齐
|
||||
*
|
||||
* @author bfhuange
|
||||
* @date 2024/8/26
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum DeptCategory {
|
||||
/**
|
||||
* 公司
|
||||
*/
|
||||
COMPANY(1, "公司"),
|
||||
/**
|
||||
* 部门
|
||||
*/
|
||||
DEPT(2, "部门"),
|
||||
;
|
||||
/**
|
||||
* 集团
|
||||
*/
|
||||
GROUP(1, "集团"),
|
||||
/**
|
||||
* 公司
|
||||
*/
|
||||
COMPANY(2, "公司"),
|
||||
/**
|
||||
* 部门
|
||||
*/
|
||||
DEPT(3, "部门"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 部门类型编码
|
||||
*/
|
||||
private final Integer code;
|
||||
/**
|
||||
* 部门类型名称
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* 组织类型编码
|
||||
*/
|
||||
private final Integer code;
|
||||
/**
|
||||
* 组织类型名称
|
||||
*/
|
||||
private final String name;
|
||||
}
|
||||
|
||||
+5
-1
@@ -35,8 +35,12 @@ public enum UserSex {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String sexName = name.trim();
|
||||
if ("-1".equals(sexName) || "0".equals(sexName) || "未知".equals(sexName)) {
|
||||
return null;
|
||||
}
|
||||
return Stream.of(values())
|
||||
.filter(e -> e.getName().equals(name))
|
||||
.filter(e -> e.getName().equals(sexName) || String.valueOf(e.getCode()).equals(sexName))
|
||||
.map(UserSex::getCode)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
+7
@@ -52,6 +52,13 @@ public class AirportMasterVO extends AirportMaster {
|
||||
@Schema(description = "导入错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+4
@@ -49,6 +49,10 @@ public class FeeItemVO extends FeeItem {
|
||||
@Schema(description = "组织")
|
||||
private String createDeptName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "更新人")
|
||||
private String updateUserName;
|
||||
|
||||
+3
@@ -66,4 +66,7 @@ public class OaOrgSyncPageVO implements Serializable {
|
||||
|
||||
@Schema(description = "是否已到最后一页")
|
||||
private Boolean finished;
|
||||
|
||||
@Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传")
|
||||
private String modifiedSince;
|
||||
}
|
||||
|
||||
+3
@@ -63,4 +63,7 @@ public class OaPersonSyncPageVO implements Serializable {
|
||||
|
||||
@Schema(description = "是否已到最后一页")
|
||||
private Boolean finished;
|
||||
|
||||
@Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传")
|
||||
private String modifiedSince;
|
||||
}
|
||||
|
||||
+7
@@ -52,6 +52,13 @@ public class PortTerminalVO extends PortTerminal {
|
||||
@Schema(description = "导入错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+7
@@ -52,6 +52,13 @@ public class RailwayStationVO extends RailwayStation {
|
||||
@Schema(description = "导入错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.jackson;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* LocalDate 宽松反序列化,对齐 OCR / 司机侧日期归一化规则。
|
||||
* <p>
|
||||
* 兼容写法:
|
||||
* <ul>
|
||||
* <li>ISO:{@code yyyy-MM-dd}</li>
|
||||
* <li>紧凑:{@code yyyyMMdd}</li>
|
||||
* <li>点分 / 斜杠 / 年月日:{@code 2018.3.9}、{@code 2026/03/17}、{@code 2030年05月20日}</li>
|
||||
* <li>任意非数字分隔:年与月、月与日之间只要不是数字即可</li>
|
||||
* <li>区间文本:按字段名取起期(first)或止期(last)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class FlexibleLocalDateDeserializer extends JsonDeserializer<LocalDate> {
|
||||
|
||||
/**
|
||||
* 4 位年 + 1~2 位月 + 1~2 位日,中间允许任意非数字分隔(与 OcrFieldSupport 一致)
|
||||
*/
|
||||
private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4})\\D+(\\d{1,2})\\D+(\\d{1,2})");
|
||||
|
||||
private static final Pattern COMPACT_PATTERN = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})");
|
||||
|
||||
@Override
|
||||
public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException {
|
||||
String text = parser.getValueAsString();
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
String value = text.trim();
|
||||
if (value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// 「长期」等非日期文案:按空值处理,避免把无法解析的字符串打成 400
|
||||
if (value.contains("长期") || value.contains("永久")) {
|
||||
return null;
|
||||
}
|
||||
boolean preferLast = isEndDateField(parser.getCurrentName());
|
||||
LocalDate parsed = parseFlexible(value, preferLast);
|
||||
if (parsed != null) {
|
||||
return parsed;
|
||||
}
|
||||
return (LocalDate) context.handleWeirdStringValue(LocalDate.class, value,
|
||||
"期望日期格式 yyyy-MM-dd / yyyyMMdd / yyyy年M月d日 等 OCR 常见写法");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析宽松日期文本;区间时按 preferLast 取首段或末段。
|
||||
*
|
||||
* @param value 原始文本
|
||||
* @param preferLast true 取止期,false 取起期
|
||||
* @return 合法 LocalDate;无法识别时返回 null
|
||||
*/
|
||||
public static LocalDate parseFlexible(String value, boolean preferLast) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String text = value.trim();
|
||||
if (text.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<LocalDate> candidates = matchDates(text);
|
||||
if (!candidates.isEmpty()) {
|
||||
return preferLast ? candidates.get(candidates.size() - 1) : candidates.get(0);
|
||||
}
|
||||
// 去掉所有非数字后恰为 8 位时按 yyyyMMdd 处理(如 20260317、2026-0317 混杂)
|
||||
String compact = text.replaceAll("\\D", "");
|
||||
if (compact.length() == 8) {
|
||||
return toLocalDate(compact.substring(0, 4), compact.substring(4, 6), compact.substring(6, 8));
|
||||
}
|
||||
Matcher compactMatcher = COMPACT_PATTERN.matcher(text);
|
||||
if (compactMatcher.find()) {
|
||||
return toLocalDate(compactMatcher.group(1), compactMatcher.group(2), compactMatcher.group(3));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<LocalDate> matchDates(String text) {
|
||||
List<LocalDate> dates = new ArrayList<>();
|
||||
Matcher matcher = DATE_PATTERN.matcher(text);
|
||||
while (matcher.find()) {
|
||||
LocalDate date = toLocalDate(matcher.group(1), matcher.group(2), matcher.group(3));
|
||||
if (date != null) {
|
||||
dates.add(date);
|
||||
}
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
private static LocalDate toLocalDate(String yearText, String monthText, String dayText) {
|
||||
try {
|
||||
int year = Integer.parseInt(yearText);
|
||||
int month = Integer.parseInt(monthText);
|
||||
int day = Integer.parseInt(dayText);
|
||||
return LocalDate.of(year, month, day);
|
||||
} catch (NumberFormatException | DateTimeException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 止期字段名通常以 End / EndDate 结尾,区间文本应取最后一段日期。
|
||||
*/
|
||||
private static boolean isEndDateField(String fieldName) {
|
||||
if (fieldName == null || fieldName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return fieldName.endsWith("End") || fieldName.endsWith("EndDate")
|
||||
|| fieldName.contains("EndDate") || fieldName.contains("ValidEnd");
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -46,4 +46,7 @@ public class ExceptionDisposalFollowRequest implements Serializable {
|
||||
@Schema(description = "跟进说明")
|
||||
private String followContent;
|
||||
|
||||
@Schema(description = "附件JSON,[{\"name\":\"...\",\"url\":\"...\"}]")
|
||||
private String attachmentsJson;
|
||||
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 异常处置编辑请求
|
||||
* <p>
|
||||
* 只承载上报四件套。车牌、项目、承运商、配载单号等字段由上报时从运单回填,
|
||||
* 不在此列,避免编辑后与运单脱钩。
|
||||
* <p>
|
||||
* 不含处置状态:状态只能经跟进与完成流转,编辑不得改变它。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "异常处置编辑请求")
|
||||
public class ExceptionDisposalUpdateRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "异常处置ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "异常类型")
|
||||
private String exceptionType;
|
||||
|
||||
@Schema(description = "异常原因")
|
||||
private String exceptionReason;
|
||||
|
||||
@Schema(description = "上报说明")
|
||||
private String reportDescription;
|
||||
|
||||
@Schema(description = "现场照片,逗号分隔")
|
||||
private String scenePhotos;
|
||||
|
||||
}
|
||||
+34
-17
@@ -5,23 +5,22 @@
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* Using this software signifies agreement to the License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
@@ -32,12 +31,12 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 金蝶票池实时统一查询条件(收票登记选票与正式结算单认领共用)
|
||||
* 金蝶票据池镜像统一查询条件(收票登记选票与正式结算单认领共用,查本地镜像)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "金蝶票池实时统一查询条件")
|
||||
@Schema(description = "金蝶票据池镜像统一查询条件")
|
||||
public class KingdeeInvoicePoolQueryDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
@@ -73,6 +72,24 @@ public class KingdeeInvoicePoolQueryDTO implements Serializable {
|
||||
@Schema(description = "开票日期止 yyyy-MM-dd")
|
||||
private String invoiceDateEnd;
|
||||
|
||||
/**
|
||||
* 开票单位(销方名称),模糊
|
||||
*/
|
||||
@Schema(description = "开票单位(销方名称,模糊)")
|
||||
private String salerName;
|
||||
|
||||
/**
|
||||
* 受票单位(购方名称),模糊
|
||||
*/
|
||||
@Schema(description = "受票单位(购方名称,模糊)")
|
||||
private String buyerName;
|
||||
|
||||
/**
|
||||
* 发票类型编码(票易通字典,如 26/27,镜像按 invoice_type_code 精确匹配)
|
||||
*/
|
||||
@Schema(description = "发票类型编码(票易通字典,如 26/27)")
|
||||
private String invoiceType;
|
||||
|
||||
/**
|
||||
* 销方税号,精确
|
||||
*/
|
||||
@@ -86,14 +103,14 @@ public class KingdeeInvoicePoolQueryDTO implements Serializable {
|
||||
private String buyerTaxNo;
|
||||
|
||||
/**
|
||||
* 发票类型编码(票易通字典,如 26/27)
|
||||
* 认领过滤:true 仅已认领,false 仅未认领,null 不过滤(已认领=存在非作废正式结算单发票行)
|
||||
*/
|
||||
@Schema(description = "发票类型编码(票易通字典,如 26/27)")
|
||||
private String invoiceType;
|
||||
@Schema(description = "认领过滤:true已认领/false未认领/null不限")
|
||||
private Boolean claimed;
|
||||
|
||||
/**
|
||||
* 续扫游标:下一页翻页时原样回传上页返回的 nextCursor,空表示按页码锚定重新扫描
|
||||
* 登记过滤:true 仅已登记,false 仅未登记,null 不过滤(已登记=存在非作废收票登记)
|
||||
*/
|
||||
@Schema(description = "续扫游标(下一页请求回传上页 nextCursor)")
|
||||
private String cursor;
|
||||
@Schema(description = "登记过滤:true已登记/false未登记/null不限")
|
||||
private Boolean registered;
|
||||
}
|
||||
|
||||
+13
-1
@@ -30,7 +30,19 @@ public class NodeSubmitDTO implements Serializable {
|
||||
@Schema(description = "定位信息")
|
||||
private Location location;
|
||||
|
||||
@Schema(description = "凭证照片 URL 列表")
|
||||
/**
|
||||
* 带类型的打卡照片,类型由提交方显式给出;非空时优先于 photos。
|
||||
*/
|
||||
@Schema(description = "带类型的照片列表,非空时优先于 photos")
|
||||
private List<PunchPhotoDTO> photoItems;
|
||||
|
||||
/**
|
||||
* 旧版小程序提交的裸 URL 列表,类型只能按下标回推过程配置的凭证类型。
|
||||
* <p>
|
||||
* 与 photoItems 并存是为了不强制升级小程序:新版提交 photoItems,旧版仍走这里;
|
||||
* 等旧版本覆盖率达标后由后续改造删除。
|
||||
*/
|
||||
@Schema(description = "凭证照片 URL 列表,类型按下标回推")
|
||||
private List<String> photos;
|
||||
|
||||
@Schema(description = "重量(吨)")
|
||||
|
||||
+10
-23
@@ -23,44 +23,31 @@
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 金蝶票池实时查询游标分页返回体(A801-1 无总数:hasMore 驱动上一页/下一页)
|
||||
* 证件 OCR 识别请求(小程序)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "金蝶票池游标分页返回体")
|
||||
public class KingdeeInvoicePoolPageVO implements Serializable {
|
||||
@Schema(description = "证件OCR识别请求")
|
||||
public class OcrRecognizeRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 本页数据(金蝶实时行 + 本地认领/登记装饰标记) */
|
||||
@Schema(description = "本页数据")
|
||||
private List<KingdeeInvoicePoolVO> records;
|
||||
@Schema(description = "已上传图片的地址,必须是百度可访问的HTTP或HTTPS地址")
|
||||
private String imageUrl;
|
||||
@Schema(description = "证件类型:idcard-身份证,driver-驾驶证,vehicle-行驶证,road_transport-道路运输证")
|
||||
private String type;
|
||||
@Schema(description = "正副面:front或back,默认front")
|
||||
private String side;
|
||||
|
||||
/** 当前页码,从 1 开始 */
|
||||
@Schema(description = "当前页码")
|
||||
private Long current;
|
||||
|
||||
/** 每页条数 */
|
||||
@Schema(description = "每页条数")
|
||||
private Long size;
|
||||
|
||||
/** 是否还有下一页(服务端有界补页扫描判断) */
|
||||
@Schema(description = "是否还有下一页")
|
||||
private Boolean hasMore;
|
||||
|
||||
/** 下一页续扫游标("远程页码:已消费过滤行数"),null 表示没有更多;翻下一页时原样回传 */
|
||||
@Schema(description = "下一页续扫游标,null 表示没有更多")
|
||||
private String nextCursor;
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 司机端个人认证提交请求
|
||||
* <p>
|
||||
* 一次提交承载个人认证的**全部**内容(身份证 + 驾驶证 + 从业资格证),
|
||||
* 三证一起提交、一起审核,不拆开。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端个人认证提交请求")
|
||||
public class PersonCertSubmitRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "真实姓名")
|
||||
private String realName;
|
||||
@Schema(description = "身份证号")
|
||||
private String idCardNo;
|
||||
@Schema(description = "身份证正面照")
|
||||
private String idCardFrontUrl;
|
||||
@Schema(description = "身份证反面照")
|
||||
private String idCardBackUrl;
|
||||
@Schema(description = "身份证有效期起")
|
||||
private LocalDate idCardValidStart;
|
||||
@Schema(description = "身份证有效期止")
|
||||
private LocalDate idCardValidEnd;
|
||||
@Schema(description = "身份证长期有效")
|
||||
private Integer idCardLongTerm;
|
||||
|
||||
@Schema(description = "驾驶证档案编号")
|
||||
private String driverFileNo;
|
||||
@Schema(description = "准驾车型")
|
||||
private String driverClass;
|
||||
@Schema(description = "驾驶证主页照片")
|
||||
private String driverLicenseFrontUrl;
|
||||
@Schema(description = "驾驶证副页照片")
|
||||
private String driverLicenseBackUrl;
|
||||
@Schema(description = "驾驶证有效期起")
|
||||
private LocalDate driverValidStart;
|
||||
@Schema(description = "驾驶证有效期止")
|
||||
private LocalDate driverValidEnd;
|
||||
@Schema(description = "驾驶证长期有效")
|
||||
private Integer driverLongTerm;
|
||||
|
||||
@Schema(description = "从业资格证类型")
|
||||
private String qualificationType;
|
||||
@Schema(description = "从业资格证号")
|
||||
private String qualificationNo;
|
||||
@Schema(description = "从业资格证封面页")
|
||||
private String qualificationFront;
|
||||
@Schema(description = "从业资格证内容页")
|
||||
private String qualificationBack;
|
||||
@Schema(description = "从业资格证有效期起")
|
||||
private LocalDate qualificationValidStart;
|
||||
@Schema(description = "从业资格证有效期止")
|
||||
private LocalDate qualificationValidEnd;
|
||||
@Schema(description = "从业资格证长期有效")
|
||||
private Integer qualificationLongTerm;
|
||||
|
||||
@Schema(description = "大头照")
|
||||
private String headPhoto;
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 带类型的打卡照片
|
||||
* <p>
|
||||
* 类型取值域是固定的「打卡照片」加该节点过程配置勾选的凭证类型,域外类型一律拒绝:
|
||||
* 类型是照片分组的唯一依据,放进来路不明的类型会让回显分组错乱。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "带类型的打卡照片")
|
||||
public class PunchPhotoDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "照片类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "图片URL")
|
||||
private String url;
|
||||
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for their own commercial purposes.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this license agreement and terms of use,
|
||||
* and you expressly agree to be bound by the terms of use of this software.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 收款认领推送金蝶结果(A301-3)
|
||||
* <p>
|
||||
* 推送失败不抛异常跨事务边界(保证失败状态落库不被回滚),以本结果对象向调用方报告成败与原因。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "收款认领推送金蝶结果")
|
||||
public class ReceiptClaimPushResult implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "是否推送成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "金蝶收款单号(成功时返回,可能为空:金蝶响应未返回单号)")
|
||||
private String kingdeeBillNo;
|
||||
|
||||
@Schema(description = "失败原因(成功时为空)")
|
||||
private String message;
|
||||
|
||||
public static ReceiptClaimPushResult success(String kingdeeBillNo) {
|
||||
ReceiptClaimPushResult result = new ReceiptClaimPushResult();
|
||||
result.setSuccess(true);
|
||||
result.setKingdeeBillNo(kingdeeBillNo);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ReceiptClaimPushResult fail(String message) {
|
||||
ReceiptClaimPushResult result = new ReceiptClaimPushResult();
|
||||
result.setSuccess(false);
|
||||
result.setMessage(message);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 司机端车辆认证提交请求
|
||||
* <p>
|
||||
* 一次提交承载该车辆认证的**全部**内容(行驶证 + 道路运输证),两证一起提交、一起审核。
|
||||
* 提交只产生新的待审记录,不覆盖车辆当前已生效的认证内容。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端车辆认证提交请求")
|
||||
public class VehicleCertSubmitRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "车辆ID:新增时为空,重新认证时必填")
|
||||
private Long id;
|
||||
@Schema(description = "车牌号")
|
||||
private String plateNo;
|
||||
@Schema(description = "所有人:行驶证登记的单位名称,不是司机")
|
||||
private String ownerName;
|
||||
@Schema(description = "车牌颜色")
|
||||
private String plateColor;
|
||||
@Schema(description = "车辆类型")
|
||||
private String vehicleType;
|
||||
@Schema(description = "车辆识别代号(车架号),选填")
|
||||
private String vin;
|
||||
|
||||
@Schema(description = "外廓长度,毫米")
|
||||
private Integer outerLength;
|
||||
@Schema(description = "外廓宽度,毫米")
|
||||
private Integer outerWidth;
|
||||
@Schema(description = "外廓高度,毫米")
|
||||
private Integer outerHeight;
|
||||
@Schema(description = "核定载质量,KG")
|
||||
private Integer approvedLoadKg;
|
||||
@Schema(description = "准牵引总质量,KG")
|
||||
private Integer tractionMassKg;
|
||||
@Schema(description = "能源类型")
|
||||
private String energyType;
|
||||
@Schema(description = "业务关系:自有 / 管理挂靠 / 业务合作")
|
||||
private String businessRelation;
|
||||
@Schema(description = "强制报废日期")
|
||||
private LocalDate compulsoryScrapDate;
|
||||
@Schema(description = "强制报废长期有效:0否 1是")
|
||||
private Integer compulsoryScrapLongTerm;
|
||||
|
||||
@Schema(description = "行驶证档案编号")
|
||||
private String drivingLicenseNo;
|
||||
@Schema(description = "行驶证主页正面照片")
|
||||
private String drivingLicenseImage;
|
||||
@Schema(description = "行驶证主页反面照片")
|
||||
private String drivingLicenseMainBack;
|
||||
@Schema(description = "行驶证副页正面照片")
|
||||
private String drivingLicenseViceFront;
|
||||
@Schema(description = "行驶证副页反面照片")
|
||||
private String drivingLicenseViceBack;
|
||||
@Schema(description = "行驶证有效期起")
|
||||
private LocalDate drivingLicenseStartDate;
|
||||
@Schema(description = "行驶证有效期止")
|
||||
private LocalDate drivingLicenseEndDate;
|
||||
@Schema(description = "行驶证长期有效:0否 1是")
|
||||
private Integer drivingLicenseLongTerm;
|
||||
|
||||
@Schema(description = "道路运输证号")
|
||||
private String roadTransportCertNo;
|
||||
@Schema(description = "道路运输证照片")
|
||||
private String roadTransportCertImage;
|
||||
@Schema(description = "道路运输证有效期起")
|
||||
private LocalDate roadTransportCertStartDate;
|
||||
@Schema(description = "道路运输证有效期止")
|
||||
private LocalDate roadTransportCertEndDate;
|
||||
@Schema(description = "道路运输证长期有效:0否 1是")
|
||||
private Integer roadTransportCertLongTerm;
|
||||
|
||||
@Schema(description = "机动车登记编号")
|
||||
private String registrationNo;
|
||||
@Schema(description = "机动车登记本照片")
|
||||
private String registrationImage;
|
||||
|
||||
}
|
||||
+8
@@ -32,5 +32,13 @@ public class WaybillImportBatchRequest {
|
||||
private Long planId;
|
||||
private String planName;
|
||||
private String remark;
|
||||
/** 列表筛选:创建人 */
|
||||
private Long createUser;
|
||||
/** 列表筛选:创建时间起(yyyy-MM-dd HH:mm:ss) */
|
||||
private String createTimeStart;
|
||||
/** 列表筛选:创建时间止(yyyy-MM-dd HH:mm:ss) */
|
||||
private String createTimeEnd;
|
||||
private List<Map<String, Object>> rows;
|
||||
/** 分批确认导入时,是否追加到已有批次(首批为 false,后续批为 true)。 */
|
||||
private Boolean append;
|
||||
}
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理端打卡记录编辑请求
|
||||
* <p>
|
||||
* 承载时间轴上单条打卡记录的全部可编辑项,字段按过程配置取舍:
|
||||
* 打卡定位(地址/经纬度)、上传货量(重量/体积/数量)、上传凭证(照片)。
|
||||
* <p>
|
||||
* {@code punched=false} 表示取消打卡,服务端会清除该节点的打卡流水
|
||||
* ——时间轴上「未打卡」的判据就是没有流水,故不能只改状态位。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "管理端打卡记录编辑请求")
|
||||
public class WaybillPunchRecordSaveRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "运单ID")
|
||||
private Long waybillId;
|
||||
|
||||
@Schema(description = "打卡类型:node=过程节点打卡,enroute=在途打卡")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "过程节点 key")
|
||||
private String nodeCode;
|
||||
|
||||
@Schema(description = "过程节点名称")
|
||||
private String nodeName;
|
||||
|
||||
@Schema(description = "打卡状态:true 已打卡,false 未打卡")
|
||||
private Boolean punched;
|
||||
|
||||
@Schema(description = "打卡时间 yyyy-MM-dd HH:mm:ss,为空时取当前时间")
|
||||
private String punchTime;
|
||||
|
||||
@Schema(description = "打卡地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "经度")
|
||||
private String longitude;
|
||||
|
||||
@Schema(description = "纬度")
|
||||
private String latitude;
|
||||
|
||||
@Schema(description = "重量(吨)")
|
||||
private String weight;
|
||||
|
||||
@Schema(description = "体积(方)")
|
||||
private String volume;
|
||||
|
||||
@Schema(description = "数量(件)")
|
||||
private String quantity;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "打卡凭证图,按上传顺序回写凭证类型")
|
||||
private List<Photo> photos = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 打卡凭证图
|
||||
* <p>
|
||||
* 类型随图一起回写:展示层对「逗号分隔的裸 URL」按凭证类型下标回推标签,
|
||||
* 编辑换了顺序就会串标,所以这里保留原类型。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "打卡凭证图")
|
||||
public static class Photo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "凭证类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "图片URL")
|
||||
private String url;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+10
-3
@@ -61,10 +61,10 @@ public class ContractManage extends TenantEntity {
|
||||
@Schema(description = "所属项目")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "所属组织ID")
|
||||
@Schema(description = "合同负责部门ID")
|
||||
private Long organizationId;
|
||||
|
||||
@Schema(description = "所属组织")
|
||||
@Schema(description = "合同负责部门")
|
||||
private String organizationName;
|
||||
|
||||
@Schema(description = "合同类别")
|
||||
@@ -100,6 +100,9 @@ public class ContractManage extends TenantEntity {
|
||||
@Schema(description = "签订日期")
|
||||
private LocalDate signDate;
|
||||
|
||||
@Schema(description = "申请日期(提交正式合同时写入)")
|
||||
private LocalDate applyDate;
|
||||
|
||||
@Schema(description = "结算方式")
|
||||
private String settlementMode;
|
||||
|
||||
@@ -107,6 +110,7 @@ public class ContractManage extends TenantEntity {
|
||||
private String contractFormat;
|
||||
|
||||
@Schema(description = "是否需要加盖法人章")
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
private Integer legalSealFlag;
|
||||
|
||||
@Schema(description = "一式份数")
|
||||
@@ -206,7 +210,10 @@ public class ContractManage extends TenantEntity {
|
||||
@Schema(description = "终止原因")
|
||||
private String terminateReason;
|
||||
|
||||
@Schema(description = "备注")
|
||||
@Schema(description = "合同主要内容")
|
||||
private String mainContent;
|
||||
|
||||
@Schema(description = "其他事项说明")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "合规系统单据ID(fdContractId)")
|
||||
|
||||
+56
-2
@@ -28,10 +28,15 @@ package org.springblade.transport.pojo.entity;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
import org.springblade.transport.jackson.FlexibleLocalDateDeserializer;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDate;
|
||||
@@ -64,6 +69,7 @@ public class Driver extends TenantEntity {
|
||||
* 出生年月
|
||||
*/
|
||||
@Schema(description = "出生年月")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate birthday;
|
||||
/**
|
||||
* 性别
|
||||
@@ -111,6 +117,23 @@ public class Driver extends TenantEntity {
|
||||
*/
|
||||
@Schema(description = "身份证反面照")
|
||||
private String idCardBack;
|
||||
/**
|
||||
* 身份证有效期起
|
||||
*/
|
||||
@Schema(description = "身份证有效期起")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate idCardValidStart;
|
||||
/**
|
||||
* 身份证有效期止
|
||||
*/
|
||||
@Schema(description = "身份证有效期止")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate idCardValidEnd;
|
||||
/**
|
||||
* 身份证长期有效
|
||||
*/
|
||||
@Schema(description = "身份证长期有效")
|
||||
private Integer idCardLongTerm;
|
||||
/**
|
||||
* 大头照
|
||||
*/
|
||||
@@ -130,11 +153,13 @@ public class Driver extends TenantEntity {
|
||||
* 驾驶证有效期起
|
||||
*/
|
||||
@Schema(description = "驾驶证有效期起")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate drivingLicenseStartDate;
|
||||
/**
|
||||
* 驾驶证有效期止
|
||||
*/
|
||||
@Schema(description = "驾驶证有效期止")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate drivingLicenseEndDate;
|
||||
/**
|
||||
* 驾驶证长期有效
|
||||
@@ -165,6 +190,7 @@ public class Driver extends TenantEntity {
|
||||
* 从业资格证有效期止
|
||||
*/
|
||||
@Schema(description = "从业资格证有效期止")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate qualificationEndDate;
|
||||
/**
|
||||
* 从业资格证长期有效
|
||||
@@ -192,9 +218,10 @@ public class Driver extends TenantEntity {
|
||||
@Schema(description = "手机号")
|
||||
private String mobile;
|
||||
/**
|
||||
* 关联系统用户ID
|
||||
* 关联小程序用户ID(blade_mp_user.id)
|
||||
*/
|
||||
@Schema(description = "关联系统用户ID")
|
||||
@Schema(description = "关联小程序用户ID")
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long userId;
|
||||
/**
|
||||
* 与联系人关系
|
||||
@@ -221,5 +248,32 @@ public class Driver extends TenantEntity {
|
||||
*/
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
/**
|
||||
* 最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null 表示从未提交
|
||||
* <p>
|
||||
* 仅为列表展示而冗余,<b>不是</b>可用性判据;「是否已认证」一律读生效快照指针。
|
||||
*/
|
||||
@Schema(description = "最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null为从未提交")
|
||||
private Integer certificationStatus;
|
||||
/**
|
||||
* 最新一次驳回原因展示缓存
|
||||
*/
|
||||
@Schema(description = "最新一次驳回原因展示缓存")
|
||||
private String certificationRejectReason;
|
||||
/**
|
||||
* 生效快照指针:指向 blade_transport_cert_record.id
|
||||
* <p>
|
||||
* 「该司机是否已认证」的<b>唯一</b>判据。为 null 表示从未认证通过;
|
||||
* 一经设置<b>永不回退</b>——提交、驳回、作废都不得修改它,
|
||||
* 因此重新提交认证不会让一个已认证的司机失去接单资格。
|
||||
* <p>
|
||||
* ⚠️ {@code READ_ONLY}:本字段<b>只能由审核通过时</b>写入。
|
||||
* 司机档案的 {@code /submit} 直接以本实体接收请求体,若允许反序列化,
|
||||
* 调用方就能自行填一个记录 ID 把自己「认证」掉,绕过审核。
|
||||
*/
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "生效快照指针")
|
||||
private Long effectiveCertRecordId;
|
||||
|
||||
}
|
||||
|
||||
+6
@@ -51,6 +51,12 @@ public class ExceptionDisposal extends TenantEntity {
|
||||
@Schema(description = "运单号")
|
||||
private String waybillNo;
|
||||
|
||||
@Schema(description = "过程节点 key(为空表示未指明节点)")
|
||||
private String nodeCode;
|
||||
|
||||
@Schema(description = "过程节点名称(上报时复制,此后不随过程配置变化)")
|
||||
private String nodeName;
|
||||
|
||||
@Schema(description = "配载单号/总单号")
|
||||
private String loadingOrMasterNo;
|
||||
|
||||
|
||||
+3
@@ -51,6 +51,9 @@ public class ExceptionDisposalFollowRecord extends TenantEntity {
|
||||
@Schema(description = "跟进说明")
|
||||
private String followContent;
|
||||
|
||||
@Schema(description = "附件JSON")
|
||||
private String attachmentsJson;
|
||||
|
||||
@Schema(description = "跟进人")
|
||||
private Long followUser;
|
||||
|
||||
|
||||
+4
@@ -144,6 +144,10 @@ public class FormalSettlement extends TenantEntity {
|
||||
* 金蝶单据编号
|
||||
*/
|
||||
private String kingdeeBillNo;
|
||||
/**
|
||||
* 金蝶单据id(星瀚内部主键,A902 附件上传定位用)
|
||||
*/
|
||||
private String kingdeeBillId;
|
||||
/**
|
||||
* 金蝶同步状态:unsynced-未同步 synced-已同步 failed-同步失败
|
||||
*/
|
||||
|
||||
+4
@@ -159,6 +159,10 @@ public class InvoiceApplication extends TenantEntity {
|
||||
* 金蝶开票申请单编号(A303-1 返回,A303-5 查询 serialNo)
|
||||
*/
|
||||
private String kingdeeBillNo;
|
||||
/**
|
||||
* 金蝶开票申请单id(星瀚内部主键,A902 附件上传定位用)
|
||||
*/
|
||||
private String kingdeeBillId;
|
||||
/**
|
||||
* 金蝶同步状态:unsynced-未同步 synced-已同步 failed-同步失败 invoiced-开票完成(终态)
|
||||
*/
|
||||
|
||||
+13
@@ -33,6 +33,7 @@ import java.time.LocalDateTime;
|
||||
* 开票申请金蝶发票结果实体类
|
||||
* <p>
|
||||
* 定时任务经 A303-5 开票申请发票查询回写的发票明细(发票号唯一键幂等落库),
|
||||
* 发票文件地址(板式文件/数电PDF/XML/预览图)独立落库供详情页查看下载,
|
||||
* 原文 JSON 整体留档便于联调与追溯
|
||||
*
|
||||
* @author Chill
|
||||
@@ -61,6 +62,18 @@ public class InvoiceApplicationInvoice extends TenantEntity {
|
||||
@Schema(description = "价税合计")
|
||||
private BigDecimal invoiceAmount;
|
||||
|
||||
@Schema(description = "板式文件下载地址(数电票为OFD,航信百望税盘普通电票为PDF,电子专票/UKey电票为OFD)")
|
||||
private String invoiceFileUrl;
|
||||
|
||||
@Schema(description = "PDF图片预览地址")
|
||||
private String invoiceImageUrl;
|
||||
|
||||
@Schema(description = "数电PDF下载地址(发票云5.0.025版本返回)")
|
||||
private String invoicePdfFileUrl;
|
||||
|
||||
@Schema(description = "数电XML下载地址(发票云5.0.025版本返回)")
|
||||
private String invoiceXmlFileUrl;
|
||||
|
||||
@Schema(description = "A303-5 返回原文JSON留档")
|
||||
private String rawJson;
|
||||
|
||||
|
||||
+4
@@ -152,6 +152,10 @@ public class InvoiceReceipt extends TenantEntity {
|
||||
* 金蝶收票单单据编号(A303-2 批量保存生成/幂等预检命中回填)
|
||||
*/
|
||||
private String kingdeeReceivedInvoiceNo;
|
||||
/**
|
||||
* 金蝶收票单id(星瀚内部主键,A902 附件上传定位用)
|
||||
*/
|
||||
private String kingdeeReceivedInvoiceId;
|
||||
/**
|
||||
* 收票单推送状态(unsynced 未推送 / pushed 已保存收票单 / assigned 已指定应付 / failed 推送失败)
|
||||
*/
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 金蝶附件同步实体类(A902 附件上传到指定单据,单附件行级状态)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_kingdee_attachment_sync")
|
||||
@Schema(description = "金蝶附件同步")
|
||||
public class KingdeeAttachmentSync extends TenantEntity {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 业务单据类型(pay_application/formal_settlement_ap/formal_settlement_ar/invoice_receipt/invoice_application)
|
||||
*/
|
||||
private String bizType;
|
||||
/**
|
||||
* 业务单据id
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long bizId;
|
||||
/**
|
||||
* 附件幂等键(用户附件=附件url;发票文件=A801-2 的 path)
|
||||
*/
|
||||
private String attachmentKey;
|
||||
/**
|
||||
* 附件名称(展示/排查用)
|
||||
*/
|
||||
private String attachmentName;
|
||||
/**
|
||||
* 金蝶侧文件路径(A902 返回 data.path)
|
||||
*/
|
||||
private String kingdeePath;
|
||||
/**
|
||||
* 同步状态:1 成功 / 0 失败
|
||||
*/
|
||||
private Integer syncStatus;
|
||||
/**
|
||||
* 失败原因(超限/下载失败/金蝶报错)
|
||||
*/
|
||||
private String failReason;
|
||||
/**
|
||||
* 最近上传时间
|
||||
*/
|
||||
private LocalDateTime syncTime;
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 金蝶单据锁定记录实体类
|
||||
* <p>
|
||||
* 一行 = 一张金蝶目标单:推送成功后 lockBusiData 锁定(FAILED→LOCKED),金蝶经 unLockNotifyBusiData
|
||||
* 回调解锁(→UNLOCKED,DELETE 时废除源单并还原关联数据);同时承担当前锁状态(编辑守卫/回调定位)
|
||||
* 与锁定补偿扫表(FAILED+try_count)、锁定/解锁留痕
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_kingdee_bill_lock")
|
||||
@Schema(description = "金蝶单据锁定记录")
|
||||
public class KingdeeBillLock extends TenantEntity {
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* WLTMS 单据类型(SETTLEMENT/INVOICE_APPLY,预留 PAY_APPLY/RECEIVED_INVOICE)
|
||||
*/
|
||||
private String sourceBillType;
|
||||
/**
|
||||
* WLTMS 业务主键
|
||||
*/
|
||||
private Long sourceBillId;
|
||||
/**
|
||||
* WLTMS 单号(正式结算单号/开票申请单号)
|
||||
*/
|
||||
private String sourceBillNo;
|
||||
/**
|
||||
* 金蝶单据类型(ApFin_other_BT_S/arfin_other_BT_S/cdm_paybill_bank_elect_BT_S 等固定值)
|
||||
*/
|
||||
private String targetBillType;
|
||||
/**
|
||||
* 金蝶单据编号(即业务单 kingdeeBillNo,开票申请按发票张次逐张一行)
|
||||
*/
|
||||
private String targetBillNo;
|
||||
/**
|
||||
* 锁状态:failed-锁定失败(补偿重试对象)/locked-金蝶锁定中/unlocked-已解锁(终态)
|
||||
*/
|
||||
private String lockStatus;
|
||||
/**
|
||||
* 最近动作来源操作类型:SAVE-单据新增,DELETE-单据删除(DELETE 回调触发废除还原)
|
||||
*/
|
||||
private String operationType;
|
||||
/**
|
||||
* 锁定重试次数(补偿任务用,超上限停试)
|
||||
*/
|
||||
private Integer tryCount;
|
||||
/**
|
||||
* 锁定时间
|
||||
*/
|
||||
private LocalDateTime lockTime;
|
||||
/**
|
||||
* 解锁时间
|
||||
*/
|
||||
private LocalDateTime unlockTime;
|
||||
/**
|
||||
* 推单发起人(锁定操作人)
|
||||
*/
|
||||
private String lockOperator;
|
||||
/**
|
||||
* 回调操作人(金蝶侧操作人)
|
||||
*/
|
||||
private String notifyOperator;
|
||||
/**
|
||||
* 锁定失败原因
|
||||
*/
|
||||
private String failReason;
|
||||
}
|
||||
+8
@@ -143,4 +143,12 @@ public class KingdeeInvoicePool extends TenantEntity {
|
||||
* 票池数据更新时间(A801-1 billCreateTime,镜像排序字段)
|
||||
*/
|
||||
private LocalDateTime sourceUpdatedTime;
|
||||
/**
|
||||
* 剩余可用金额(A801-1 availableAmount,金蝶可用税额口径,选票本地过滤用:>0 才可选)
|
||||
*/
|
||||
private BigDecimal availableAmount;
|
||||
/**
|
||||
* 金额使用状态(A801-1 amountUseStatus,0 未使用可选 / 2 全部使用;选票本地过滤用)
|
||||
*/
|
||||
private String amountUseStatus;
|
||||
}
|
||||
|
||||
+60
@@ -80,16 +80,76 @@ public class KingdeeReceiptFlow extends TenantEntity {
|
||||
* 收款人(A301-2 recpayee)
|
||||
*/
|
||||
private String payeeName;
|
||||
/**
|
||||
* 收款通知单号(A301-2 billno)
|
||||
*/
|
||||
private String receiptNoticeNo;
|
||||
/**
|
||||
* 付款人名称(A301-2 recviewpayer)
|
||||
*/
|
||||
private String payerName;
|
||||
/**
|
||||
* 收款金额(A301-2 reamount)
|
||||
*/
|
||||
private BigDecimal receiptAmount;
|
||||
/**
|
||||
* 对方单位名称(A301-2 oppunit)
|
||||
*/
|
||||
private String counterpartyName;
|
||||
/**
|
||||
* 对方银行账号(A301-2 oppbanknumber)
|
||||
*/
|
||||
private String counterpartyAccount;
|
||||
/**
|
||||
* 对方开户行(A301-2 oppbank)
|
||||
*/
|
||||
private String counterpartyBank;
|
||||
/**
|
||||
* 摘要(A301-2 description)
|
||||
*/
|
||||
private String summary;
|
||||
/**
|
||||
* 交易日期(A301-2 tradetime)
|
||||
*/
|
||||
private LocalDateTime transactionTime;
|
||||
/**
|
||||
* 明细流水号(A301-2 detailid,回退 tradedetailno / bankdetailno,幂等键)
|
||||
*/
|
||||
private String detailSerialNo;
|
||||
/**
|
||||
* 已认领金额(本地认领驱动,初始为0)
|
||||
*/
|
||||
private BigDecimal claimedAmount;
|
||||
/**
|
||||
* 认领状态(unclaimed未认领/partial部分认领/claimed认领完成,累计认领与公告未认领金额对表)
|
||||
*/
|
||||
private String claimStatus;
|
||||
/**
|
||||
* 推送状态(none未推送/pushed已推送/failed推送失败;已推送为终态闸门,不允许二次推送)
|
||||
*/
|
||||
private String pushStatus;
|
||||
/**
|
||||
* 最近一次推送失败原因(成功后清空)
|
||||
*/
|
||||
private String pushFailReason;
|
||||
/**
|
||||
* 首次推送金蝶成功时间
|
||||
*/
|
||||
private LocalDateTime pushedTime;
|
||||
/**
|
||||
* 金蝶推送来源单据编码(分摊结算单号下划线拼接,推送时生成落库;分摊不变则重试编码不变,A301-1 回写匹配键)
|
||||
*/
|
||||
private String awrpSrcCode;
|
||||
/**
|
||||
* 金蝶收款单号(推送响应回填 / A301-1 回写推进)
|
||||
*/
|
||||
private String kingdeeBillNo;
|
||||
/**
|
||||
* 金蝶回写状态(none未推送/pushed已推送/approved审核通过/received已收款/failed失败)
|
||||
*/
|
||||
private String kingdeeBillStatus;
|
||||
/**
|
||||
* 来源更新时间(取交易日期,同步时刷新)
|
||||
*/
|
||||
private LocalDateTime sourceUpdatedTime;
|
||||
}
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class LoadingManage extends TenantEntity {
|
||||
@Schema(description = "货物名称")
|
||||
private String cargoName;
|
||||
|
||||
@Schema(description = "车/船/航班/班列")
|
||||
@Schema(description = "车牌号/船班号/船次号/班次号")
|
||||
private String vehicleNo;
|
||||
|
||||
@Schema(description = "挂车车牌号")
|
||||
|
||||
+3
-3
@@ -70,10 +70,10 @@ public class ProjectApply extends TenantEntity {
|
||||
@Schema(description = "业务部门")
|
||||
private String businessDeptName;
|
||||
|
||||
@Schema(description = "承办部门ID")
|
||||
private Long undertakeDeptId;
|
||||
@Schema(description = "平台公司ID,多个逗号分隔")
|
||||
private String undertakeDeptId;
|
||||
|
||||
@Schema(description = "承办部门")
|
||||
@Schema(description = "平台公司,多个顿号分隔")
|
||||
private String undertakeDeptName;
|
||||
|
||||
@Schema(description = "项目由来")
|
||||
|
||||
+4
-2
@@ -66,10 +66,12 @@ public class ReceiptClaim extends TenantEntity {
|
||||
private String remark;
|
||||
private String claimStatus;
|
||||
/**
|
||||
* 金蝶认领结果推送来源单据编码(A301-3 awrp_src_code,A301-1 回写按此匹配)
|
||||
* 金蝶收款单号(冗余展示:推送成功/A301-1 回写时由流水级联刷新)
|
||||
*/
|
||||
private String awrpSrcCode;
|
||||
private String kingdeeBillNo;
|
||||
/**
|
||||
* 金蝶回写状态(冗余展示:与流水 kingdee_bill_status 同步刷新)
|
||||
*/
|
||||
private String kingdeeBillStatus;
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long voidedBy;
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 认证记录实体类
|
||||
* <p>
|
||||
* 认证对象(司机 / 车辆)<b>每一次提交</b>所产生的、<b>不可变</b>的快照。
|
||||
* 记录一经创建,其 {@code snapshotJson} 与提交元信息永不修改;被同对象的后续提交取代时
|
||||
* 标记为「已作废」,而<b>不是</b>删除。判定某个对象「是否已认证」只看它持有的生效快照指针,
|
||||
* 与记录表里的任何一条记录的状态都无关。
|
||||
* <p>
|
||||
* ⚠️ <b>{@code status} 承载的是「审核状态」,不是 BladeX 的「启用 / 停用」。</b>
|
||||
* 该字段继承自 {@link TenantEntity},带来两条必须堵住的框架通道:
|
||||
* 一是 {@code BaseServiceImpl#resolveEntity} 在插入时会把 {@code null} 的 {@code status}
|
||||
* <b>静默填成 {@code 1}</b>(恰好是 {@link #STATUS_APPROVED});
|
||||
* 二是继承来的 {@code changeStatus(ids, 1)} 会直接写 {@code status},等于批量通过审核。
|
||||
* 二者都已在 {@code TransportCertRecordServiceImpl} 中封堵(前者校验抛错、后者覆写抛错)。
|
||||
* 审核必须逐条、带审核人与审核时间落库,不得借道 {@code changeStatus} 批量改状态。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_transport_cert_record")
|
||||
@Schema(description = "认证记录")
|
||||
public class TransportCertRecord extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 认证对象类型:司机
|
||||
*/
|
||||
public static final String BIZ_TYPE_DRIVER = "driver";
|
||||
/**
|
||||
* 认证对象类型:车辆
|
||||
*/
|
||||
public static final String BIZ_TYPE_VEHICLE = "vehicle";
|
||||
|
||||
/**
|
||||
* 审核状态:审核中(即该对象的「待审快照」)
|
||||
*/
|
||||
public static final Integer STATUS_PENDING = 0;
|
||||
/**
|
||||
* 审核状态:已通过(该对象最新一条已通过者即其「生效快照」)
|
||||
*/
|
||||
public static final Integer STATUS_APPROVED = 1;
|
||||
/**
|
||||
* 审核状态:已驳回(审核的否定结论,需带驳回原因)
|
||||
*/
|
||||
public static final Integer STATUS_REJECTED = 2;
|
||||
/**
|
||||
* 审核状态:已作废(被同对象的再次提交取代,非驳回)
|
||||
*/
|
||||
public static final Integer STATUS_SUPERSEDED = 3;
|
||||
|
||||
/**
|
||||
* 驳回原因最大长度,与 {@code reject_reason} 列宽保持一致
|
||||
*/
|
||||
public static final int REJECT_REASON_MAX_LENGTH = 200;
|
||||
|
||||
/**
|
||||
* 认证对象类型:driver-司机,vehicle-车辆
|
||||
* <p>
|
||||
* {@code NEVER}:记录一经创建,认证对象的身份不可改。
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
@Schema(description = "认证对象类型:driver-司机,vehicle-车辆")
|
||||
private String bizType;
|
||||
/**
|
||||
* 认证对象主键:司机ID 或 车辆ID(车辆按具体车辆,不是司机)
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "认证对象主键")
|
||||
private Long bizId;
|
||||
/**
|
||||
* 展示键:司机姓名 / 车牌号,供审核列表直接展示
|
||||
*/
|
||||
@Schema(description = "展示键:司机姓名 / 车牌号")
|
||||
private String bizKey;
|
||||
/**
|
||||
* 该次提交的全量认证内容(JSON),一经创建不可修改
|
||||
* <p>
|
||||
* {@code NEVER}:本字段<b>永不进入 UPDATE 语句</b>,因此不存在「某条链路
|
||||
* 忘了保护快照」的可能。改内容只能靠<b>新提交一条记录</b>。
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
@Schema(description = "该次提交的全量认证内容JSON")
|
||||
private String snapshotJson;
|
||||
/**
|
||||
* 驳回原因,仅当审核状态为已驳回时有值
|
||||
*/
|
||||
@Schema(description = "驳回原因")
|
||||
private String rejectReason;
|
||||
/**
|
||||
* 提交时间
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
@Schema(description = "提交时间")
|
||||
private LocalDateTime submitTime;
|
||||
/**
|
||||
* 提交人
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "提交人")
|
||||
private Long submitUser;
|
||||
/**
|
||||
* 审核时间
|
||||
*/
|
||||
@Schema(description = "审核时间")
|
||||
private LocalDateTime auditTime;
|
||||
/**
|
||||
* 审核人
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "审核人")
|
||||
private Long auditUser;
|
||||
|
||||
}
|
||||
+38
@@ -26,10 +26,15 @@
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
import org.springblade.transport.jackson.FlexibleLocalDateDeserializer;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDate;
|
||||
@@ -63,6 +68,11 @@ public class TransportVehicle extends TenantEntity {
|
||||
*/
|
||||
@Schema(description = "车牌号")
|
||||
private String plateNo;
|
||||
/**
|
||||
* 所有人:行驶证登记的单位名称,不是司机
|
||||
*/
|
||||
@Schema(description = "所有人(行驶证登记的单位名称,不是司机)")
|
||||
private String ownerName;
|
||||
/**
|
||||
* 车牌颜色
|
||||
*/
|
||||
@@ -73,6 +83,11 @@ public class TransportVehicle extends TenantEntity {
|
||||
*/
|
||||
@Schema(description = "车辆类型")
|
||||
private String vehicleType;
|
||||
/**
|
||||
* 车辆识别代号(车架号)
|
||||
*/
|
||||
@Schema(description = "车辆识别代号(车架号)")
|
||||
private String vin;
|
||||
/**
|
||||
* 外廓长度,毫米
|
||||
*/
|
||||
@@ -112,6 +127,7 @@ public class TransportVehicle extends TenantEntity {
|
||||
* 强制报废日期
|
||||
*/
|
||||
@Schema(description = "强制报废日期")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate compulsoryScrapDate;
|
||||
/**
|
||||
* 强制报废长期有效
|
||||
@@ -132,11 +148,13 @@ public class TransportVehicle extends TenantEntity {
|
||||
* 行驶证有效期起
|
||||
*/
|
||||
@Schema(description = "行驶证有效期起")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate drivingLicenseStartDate;
|
||||
/**
|
||||
* 行驶证有效期止
|
||||
*/
|
||||
@Schema(description = "行驶证有效期止")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate drivingLicenseEndDate;
|
||||
/**
|
||||
* 行驶证长期有效
|
||||
@@ -172,11 +190,13 @@ public class TransportVehicle extends TenantEntity {
|
||||
* 道路运输证有效期起
|
||||
*/
|
||||
@Schema(description = "道路运输证有效期起")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate roadTransportCertStartDate;
|
||||
/**
|
||||
* 道路运输证有效期止
|
||||
*/
|
||||
@Schema(description = "道路运输证有效期止")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate roadTransportCertEndDate;
|
||||
/**
|
||||
* 道路运输证长期有效
|
||||
@@ -192,6 +212,7 @@ public class TransportVehicle extends TenantEntity {
|
||||
* 道路运输年审有效期
|
||||
*/
|
||||
@Schema(description = "道路运输年审有效期")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate annualReviewEndDate;
|
||||
/**
|
||||
* 道路运输年审长期有效
|
||||
@@ -207,6 +228,7 @@ public class TransportVehicle extends TenantEntity {
|
||||
* 机动车登记日期
|
||||
*/
|
||||
@Schema(description = "机动车登记日期")
|
||||
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||
private LocalDate registrationDate;
|
||||
/**
|
||||
* 机动车登记本图片
|
||||
@@ -224,5 +246,21 @@ public class TransportVehicle extends TenantEntity {
|
||||
/** 认证驳回原因 */
|
||||
@Schema(description = "认证驳回原因")
|
||||
private String certificationRejectReason;
|
||||
/**
|
||||
* 生效快照指针:指向 blade_transport_cert_record.id
|
||||
* <p>
|
||||
* 「该车辆是否已认证」的<b>唯一</b>判据。为 null 表示从未认证通过;
|
||||
* 一经设置<b>永不回退</b>——重新提交、待审、被驳回都不得修改它,
|
||||
* 因此重新提交认证不会让一辆已认证的车掉出可用池。
|
||||
* {@link #certificationStatus} 是「最新提交状态」的展示缓存,不代表是否已认证。
|
||||
* <p>
|
||||
* ⚠️ {@code READ_ONLY}:本字段<b>只能由审核通过时</b>写入。
|
||||
* {@code POST /transport-vehicle/submit} 直接以本实体接收请求体,
|
||||
* 若允许反序列化,调用方就能自行填一个记录 ID 把自己「认证」掉,绕过审核。
|
||||
*/
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "生效快照指针")
|
||||
private Long effectiveCertRecordId;
|
||||
|
||||
}
|
||||
|
||||
+10
-1
@@ -25,6 +25,8 @@ package org.springblade.transport.pojo.entity;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -82,6 +84,7 @@ public class Waybill extends TenantEntity {
|
||||
@Schema(description = "型号")
|
||||
private String model;
|
||||
|
||||
@JsonSerialize(nullsUsing = NullSerializer.class)
|
||||
@Schema(description = "数量")
|
||||
private BigDecimal quantity;
|
||||
|
||||
@@ -162,7 +165,7 @@ public class Waybill extends TenantEntity {
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String driverRejectReason;
|
||||
|
||||
@Schema(description = "车/船/航班/班列号")
|
||||
@Schema(description = "车牌号/船班号/船次号/班次号")
|
||||
private String vehicleNo;
|
||||
|
||||
@Schema(description = "船长")
|
||||
@@ -183,6 +186,7 @@ public class Waybill extends TenantEntity {
|
||||
@Schema(description = "押运人手机号")
|
||||
private String escortPhone;
|
||||
|
||||
@JsonSerialize(nullsUsing = NullSerializer.class)
|
||||
@Schema(description = "里程")
|
||||
private BigDecimal mileage;
|
||||
|
||||
@@ -195,12 +199,14 @@ public class Waybill extends TenantEntity {
|
||||
@Schema(description = "预计完成日期")
|
||||
private LocalDate estimatedEndTime;
|
||||
|
||||
@JsonSerialize(nullsUsing = NullSerializer.class)
|
||||
@Schema(description = "单价")
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
@Schema(description = "计价单位")
|
||||
private String priceUnit;
|
||||
|
||||
@JsonSerialize(nullsUsing = NullSerializer.class)
|
||||
@Schema(description = "其他费用合计")
|
||||
private BigDecimal otherFeeTotal;
|
||||
|
||||
@@ -234,6 +240,9 @@ public class Waybill extends TenantEntity {
|
||||
@Schema(description = "配载单号")
|
||||
private String loadingNo;
|
||||
|
||||
@Schema(description = "配载标识号")
|
||||
private String loadingIdentifier;
|
||||
|
||||
@Schema(description = "运单批次号")
|
||||
private String batchNo;
|
||||
|
||||
|
||||
+7
@@ -80,6 +80,13 @@ public class AccidentRecordVO extends AccidentRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+3
@@ -44,4 +44,7 @@ public class AdminHomeBadgesVO implements Serializable {
|
||||
@Schema(description = "风险记录待办数(disposalStatus=pending)")
|
||||
private long risk;
|
||||
|
||||
@Schema(description = "重新派单待办数(司机已拒单且运单未取消未完成)")
|
||||
private long pendingReassign;
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public class AdminHomeVO implements Serializable {
|
||||
@Schema(description = "快捷入口角标")
|
||||
private AdminHomeBadgesVO badges = new AdminHomeBadgesVO();
|
||||
|
||||
@Schema(description = "待处理事项(异常处置状态≠已完成)")
|
||||
@Schema(description = "待处理事项(异常处置状态≠已完成,或司机已拒单待重新派单)")
|
||||
private List<AdminTodoItemVO> feed = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -68,6 +68,10 @@ public class AnnualInspectionRecordVO extends AnnualInspectionRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "更新人姓名")
|
||||
private String updateUserName;
|
||||
|
||||
+10
@@ -56,4 +56,14 @@ public class BaiduOcrResultVO implements Serializable {
|
||||
@Schema(description = "百度OCR原始返回结果")
|
||||
private Map<String, Object> result;
|
||||
|
||||
/**
|
||||
* 归一化后的业务字段
|
||||
* <p>
|
||||
* 键与提交请求里的字段同名({@code plateNo}、{@code ownerName}…),调用方直接回填即可,
|
||||
* 不需要认识百度那套中文键名。只包含**识别到**的字段,没识别到的不出现(而不是给空串),
|
||||
* 调用方据此就能区分「没识别到」与「识别到空值」,不会把用户已填的内容覆盖成空。
|
||||
*/
|
||||
@Schema(description = "归一化后的业务字段,键与提交请求同名")
|
||||
private Map<String, Object> fields;
|
||||
|
||||
}
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 认证审核详情视图
|
||||
* <p>
|
||||
* 审核是「拿新提交的内容和当前生效的内容比」的过程,所以这里**同时**给出两侧:
|
||||
* {@code pendingSnapshotJson} 是本次待审的全量内容,
|
||||
* {@code effectiveSnapshotJson} 是该对象当前生效的全量内容。
|
||||
* 前端对这两个 JSON 做字段级 diff,即可看出司机这次改了什么。
|
||||
* <p>
|
||||
* 两侧的 JSON 结构完全一致(同一套提交结构),因此可以逐键直接比较。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "认证审核详情")
|
||||
public class CertAuditDetailVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "认证记录主键")
|
||||
private Long id;
|
||||
@Schema(description = "认证对象类型:driver-司机,vehicle-车辆")
|
||||
private String bizType;
|
||||
@Schema(description = "认证对象主键")
|
||||
private Long bizId;
|
||||
@Schema(description = "展示键:司机姓名 / 车牌号")
|
||||
private String bizKey;
|
||||
@Schema(description = "审核状态:0审核中 1已通过 2已驳回 3已作废")
|
||||
private Integer status;
|
||||
@Schema(description = "状态中文文案")
|
||||
private String statusText;
|
||||
@Schema(description = "本次提交时间")
|
||||
private LocalDateTime submitTime;
|
||||
@Schema(description = "本次提交人")
|
||||
private Long submitUser;
|
||||
@Schema(description = "本次提交人姓名")
|
||||
private String submitUserName;
|
||||
@Schema(description = "本次审核人;未审核时为 null")
|
||||
private Long auditUser;
|
||||
@Schema(description = "本次审核人姓名;未审核时为空")
|
||||
private String auditUserName;
|
||||
@Schema(description = "审核时间(通过或驳回的时间);未审核时为 null")
|
||||
private LocalDateTime auditTime;
|
||||
|
||||
@Schema(description = "待审快照:本次提交的全量内容")
|
||||
private String pendingSnapshotJson;
|
||||
@Schema(description = "生效快照:该对象当前生效的全量内容;从未认证通过时为 null")
|
||||
private String effectiveSnapshotJson;
|
||||
@Schema(description = "生效快照记录ID;null 表示该对象从未认证通过")
|
||||
private Long effectiveRecordId;
|
||||
|
||||
/**
|
||||
* 该对象是否已认证
|
||||
* <p>
|
||||
* 审核页据此提示「本次驳回不会影响该对象继续使用」——
|
||||
* 已认证的对象即便本次被驳回,既得认证依然有效。
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "该对象当前是否已认证")
|
||||
private Boolean certified;
|
||||
|
||||
}
|
||||
+4
@@ -58,6 +58,10 @@ public class CustomerArchiveVO extends CustomerArchive {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "联系人")
|
||||
private List<CustomerContactVO> contacts = new ArrayList<>();
|
||||
|
||||
+3
@@ -49,4 +49,7 @@ public class DriverNodePunchVO implements Serializable {
|
||||
@Schema(description = "数量")
|
||||
private String quantity;
|
||||
|
||||
@Schema(description = "本次打卡是否使运单自动完成")
|
||||
private Boolean waybillCompleted;
|
||||
|
||||
}
|
||||
|
||||
+31
-1
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -34,9 +36,37 @@ public class DriverPunchNodeVO implements Serializable {
|
||||
@Schema(description = "是否已打卡(在途=今日已打)")
|
||||
private Boolean done;
|
||||
|
||||
@Schema(description = "当前是否可打卡(顺序门禁 + 在途频次时段)")
|
||||
@Schema(description = "当前是否可打卡(已打卡、被异常阻断、在途频次时段都会置否)")
|
||||
private Boolean actionable;
|
||||
|
||||
@Schema(description = "是否被未了结的异常阻断:异常所在节点及其后的节点不可打卡;在途只要有未了结异常即不可打卡")
|
||||
private Boolean blocked;
|
||||
|
||||
@Schema(description = "是否因前置节点未打卡而被顺序门禁拦住(在途豁免,恒为否)")
|
||||
private Boolean blockedByOrder;
|
||||
|
||||
@Schema(description = "顺序门禁拦住的原因,如「请先完成装货打卡」")
|
||||
private String orderLockReason;
|
||||
|
||||
@Schema(description = "是否允许展开:顺序门禁不允许,异常门禁允许(可查看回填内容)")
|
||||
private Boolean expandable;
|
||||
|
||||
@Schema(description = "该节点自身是否被上报了未了结异常(仅异常所在节点为是,其后被牵连阻断的节点为否)")
|
||||
private Boolean hasException;
|
||||
|
||||
@Schema(description = "该运单是否存在未了结的整单异常;整单异常不归属任何节点,故每个节点都带此标记")
|
||||
private Boolean hasWaybillException;
|
||||
|
||||
@Schema(description = "与该节点相关的未了结异常ID,按上报时间升序;相关 = 归属该节点 ∪ 整单异常")
|
||||
private List<String> relatedExceptionIds = new ArrayList<>();
|
||||
|
||||
@Schema(description = "阻断该节点的异常处置ID(司机端「异常详情」跳转用);未被阻断或异常已不存在时为空")
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long blockedExceptionId;
|
||||
|
||||
@Schema(description = "不可打卡的原因,供司机端展示")
|
||||
private String lockReason;
|
||||
|
||||
@Schema(description = "是否展示该卡(在途可能因频次/时段隐藏)")
|
||||
private Boolean visible;
|
||||
|
||||
|
||||
+4
@@ -69,4 +69,8 @@ public class DriverVO extends Driver {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate warningDate;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
}
|
||||
|
||||
+76
-16
@@ -44,29 +44,89 @@ public class DriverVehicleCardVO implements Serializable {
|
||||
@Schema(description = "车牌号")
|
||||
private String plateNo;
|
||||
|
||||
@Schema(description = "所有人(行驶证登记的单位名称,不是司机)")
|
||||
private String ownerName;
|
||||
|
||||
@Schema(description = "车牌颜色")
|
||||
private String plateColor;
|
||||
|
||||
@Schema(description = "车辆类型")
|
||||
private String vehicleType;
|
||||
|
||||
@Schema(description = "行驶证主页照片")
|
||||
private String licenseFrontUrl;
|
||||
|
||||
@Schema(description = "行驶证副页照片")
|
||||
private String licenseBackUrl;
|
||||
|
||||
@Schema(description = "道路运输证号")
|
||||
private String roadTransportNo;
|
||||
|
||||
@Schema(description = "道路运输证照片")
|
||||
private String roadTransportUrl;
|
||||
|
||||
@Schema(description = "车架号")
|
||||
@Schema(description = "车辆识别代号(车架号),选填")
|
||||
private String vin;
|
||||
|
||||
@Schema(description = "发动机号")
|
||||
private String engineNo;
|
||||
@Schema(description = "外廓长度,毫米")
|
||||
private Integer outerLength;
|
||||
|
||||
@Schema(description = "外廓宽度,毫米")
|
||||
private Integer outerWidth;
|
||||
|
||||
@Schema(description = "外廓高度,毫米")
|
||||
private Integer outerHeight;
|
||||
|
||||
@Schema(description = "核定载质量,KG")
|
||||
private Integer approvedLoadKg;
|
||||
|
||||
@Schema(description = "准牵引总质量,KG")
|
||||
private Integer tractionMassKg;
|
||||
|
||||
@Schema(description = "能源类型")
|
||||
private String energyType;
|
||||
|
||||
@Schema(description = "业务关系:自有 / 管理挂靠 / 业务合作")
|
||||
private String businessRelation;
|
||||
|
||||
@Schema(description = "强制报废日期")
|
||||
private String compulsoryScrapDate;
|
||||
|
||||
@Schema(description = "强制报废长期有效:0否 1是")
|
||||
private Integer compulsoryScrapLongTerm;
|
||||
|
||||
@Schema(description = "行驶证档案编号")
|
||||
private String drivingLicenseNo;
|
||||
|
||||
@Schema(description = "行驶证主页正面照片")
|
||||
private String drivingLicenseImage;
|
||||
|
||||
@Schema(description = "行驶证主页反面照片")
|
||||
private String drivingLicenseMainBack;
|
||||
|
||||
@Schema(description = "行驶证副页正面照片")
|
||||
private String drivingLicenseViceFront;
|
||||
|
||||
@Schema(description = "行驶证副页反面照片")
|
||||
private String drivingLicenseViceBack;
|
||||
|
||||
@Schema(description = "行驶证有效期起")
|
||||
private String drivingLicenseStartDate;
|
||||
|
||||
@Schema(description = "行驶证有效期止")
|
||||
private String licenseValidEnd;
|
||||
private String drivingLicenseEndDate;
|
||||
|
||||
@Schema(description = "行驶证长期有效:0否 1是")
|
||||
private Integer drivingLicenseLongTerm;
|
||||
|
||||
@Schema(description = "道路运输证号")
|
||||
private String roadTransportCertNo;
|
||||
|
||||
@Schema(description = "道路运输证照片")
|
||||
private String roadTransportCertImage;
|
||||
|
||||
@Schema(description = "道路运输证有效期起")
|
||||
private String roadTransportCertStartDate;
|
||||
|
||||
@Schema(description = "道路运输证有效期止")
|
||||
private String roadTransportCertEndDate;
|
||||
|
||||
@Schema(description = "道路运输证长期有效:0否 1是")
|
||||
private Integer roadTransportCertLongTerm;
|
||||
|
||||
@Schema(description = "机动车登记编号")
|
||||
private String registrationNo;
|
||||
|
||||
@Schema(description = "机动车登记本照片")
|
||||
private String registrationImage;
|
||||
|
||||
@Schema(description = "认证状态:0认证中 1认证通过 2认证驳回")
|
||||
private Integer certificationStatus;
|
||||
|
||||
+3
@@ -25,5 +25,8 @@ public class EquipmentLedgerVO extends EquipmentLedger {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
@TableField(exist = false)
|
||||
private String updateUserName;
|
||||
}
|
||||
|
||||
+4
@@ -26,6 +26,10 @@ public class EtcRecordVO extends EtcRecord {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "更新人姓名")
|
||||
private String updateUserName;
|
||||
|
||||
+13
@@ -9,6 +9,7 @@
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
@@ -18,6 +19,7 @@ import org.springblade.transport.pojo.entity.FormalSettlementInvoice;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlementSource;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlementPayment;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
|
||||
import org.springblade.transport.pojo.entity.InvoiceApplicationInvoice;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serial;
|
||||
@@ -39,6 +41,15 @@ public class FormalSettlementVO extends FormalSettlement {
|
||||
@TableField(exist = false) private String ids;
|
||||
@TableField(exist = false) private String approvalStatusName;
|
||||
@TableField(exist = false) private String settlementTypeName;
|
||||
@TableField(exist = false) private Integer attachmentCount;
|
||||
@TableField(exist = false) private String attachmentSyncStatus;
|
||||
@TableField(exist = false) private String attachmentSyncStatusName;
|
||||
@TableField(exist = false) @Schema(description = "金蝶锁状态:locked-锁定中/failed-锁定失败;unlocked为终态不返回,null=无锁或已解锁")
|
||||
private String kingdeeLockStatus;
|
||||
@TableField(exist = false) @Schema(description = "金蝶锁失败原因(仅锁定失败时有值)")
|
||||
private String kingdeeLockFailReason;
|
||||
@TableField(exist = false) @Schema(description = "金蝶锁重试次数(达上限5后不再自动补偿,需重推同步金蝶)")
|
||||
private Integer kingdeeTryCount;
|
||||
@TableField(exist = false) private String preSettlementNos;
|
||||
@TableField(exist = false) private String preSettlementNo;
|
||||
@TableField(exist = false) private List<FormalSettlementSource> sources;
|
||||
@@ -46,6 +57,8 @@ public class FormalSettlementVO extends FormalSettlement {
|
||||
@TableField(exist = false) private List<FormalSettlementSummaryFee> summaryFees;
|
||||
@TableField(exist = false) private List<FormalSettlementPayment> payments;
|
||||
@TableField(exist = false) private List<FormalSettlementInvoice> invoices;
|
||||
@TableField(exist = false) @Schema(description = "开票申请经金蝶回写的发票结果(含文件地址,只读展示)")
|
||||
private List<InvoiceApplicationInvoice> issuedInvoices;
|
||||
@TableField(exist = false) private List<PaymentApplicationVO> paymentApplications;
|
||||
@TableField(exist = false) private List<SettlementAdjustmentVO> adjustments;
|
||||
@TableField(exist = false) private List<FormalSettlementChangeRecord> changeRecords;
|
||||
|
||||
+7
@@ -63,6 +63,13 @@ public class InsuranceRecordVO extends InsuranceRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+6
@@ -49,6 +49,12 @@ public class InvoiceApplicationVO extends InvoiceApplication {
|
||||
@TableField(exist = false) private String settlementNos;
|
||||
@TableField(exist = false) private String approvalStatusName;
|
||||
@TableField(exist = false) private String kingdeeStatusName;
|
||||
@TableField(exist = false) private Integer attachmentCount;
|
||||
@TableField(exist = false) private String attachmentSyncStatus;
|
||||
@TableField(exist = false) private String attachmentSyncStatusName;
|
||||
@TableField(exist = false) private String kingdeeLockStatus;
|
||||
@TableField(exist = false) private String kingdeeLockFailReason;
|
||||
@TableField(exist = false) private Integer kingdeeTryCount;
|
||||
@TableField(exist = false) private String createUserName;
|
||||
@TableField(exist = false) private List<InvoiceApplicationSettlement> settlements;
|
||||
@TableField(exist = false) private List<InvoiceApplicationSheetVO> sheets;
|
||||
|
||||
+3
@@ -49,6 +49,9 @@ public class InvoiceReceiptVO extends InvoiceReceipt {
|
||||
@TableField(exist = false) private String settlementNos;
|
||||
@TableField(exist = false) private String approvalStatusName;
|
||||
@TableField(exist = false) private String kingdeePushStatusName;
|
||||
@TableField(exist = false) private Integer attachmentCount;
|
||||
@TableField(exist = false) private String attachmentSyncStatus;
|
||||
@TableField(exist = false) private String attachmentSyncStatusName;
|
||||
@TableField(exist = false) private String createUserName;
|
||||
@TableField(exist = false) private String updateUserName;
|
||||
@TableField(exist = false) private List<InvoiceReceiptSettlement> settlements;
|
||||
|
||||
+7
@@ -62,6 +62,13 @@ public class MaintenancePlanVO extends MaintenancePlan {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+7
@@ -62,6 +62,13 @@ public class MaintenanceRecordVO extends MaintenanceRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+5
@@ -41,6 +41,11 @@ public class MasterOrderVO extends MasterOrder {
|
||||
private List<Waybill> boundWaybills;
|
||||
@TableField(exist = false)
|
||||
private BigDecimal totalQuantity;
|
||||
/**
|
||||
* 各品类已调度量(按货物名称+类型聚合,取各分段已调度量的最大值)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Map<String, BigDecimal> dispatchedGoods;
|
||||
@TableField(exist = false)
|
||||
private String createUserName;
|
||||
@TableField(exist = false)
|
||||
|
||||
+4
@@ -66,6 +66,10 @@ public class MileageRecordVO extends MileageRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "更新人姓名")
|
||||
private String updateUserName;
|
||||
|
||||
+4
@@ -76,6 +76,10 @@ public class OilElectricRecordVO extends OilElectricRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "更新人姓名")
|
||||
private String updateUserName;
|
||||
|
||||
+4
@@ -49,6 +49,10 @@ public class OtherExpenseRecordVO extends OtherExpenseRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "更新人姓名")
|
||||
private String updateUserName;
|
||||
|
||||
+6
@@ -43,6 +43,12 @@ public class PaymentApplicationVO extends PaymentApplication {
|
||||
@TableField(exist = false) private String paymentTypeName;
|
||||
@TableField(exist = false) private String approvalStatusName;
|
||||
@TableField(exist = false) private String kingdeeStatusName;
|
||||
@TableField(exist = false) private Integer attachmentCount;
|
||||
@TableField(exist = false) private String attachmentSyncStatus;
|
||||
@TableField(exist = false) private String attachmentSyncStatusName;
|
||||
@TableField(exist = false) private String kingdeeLockStatus;
|
||||
@TableField(exist = false) private String kingdeeLockFailReason;
|
||||
@TableField(exist = false) private Integer kingdeeTryCount;
|
||||
@TableField(exist = false) private List<PaymentApplicationInvoice> invoices;
|
||||
@TableField(exist = false) private List<PaymentApplicationRecord> paymentRecords;
|
||||
@TableField(exist = false) private List<PaymentApplicationSettlement> settlements;
|
||||
|
||||
+5
@@ -76,6 +76,11 @@ public class ReceiptClaimRecordVO extends ReceiptClaim {
|
||||
private String claimStatusName;
|
||||
@TableField(exist = false)
|
||||
private String kingdeeBillStatusName;
|
||||
/**
|
||||
* 关联流水推送状态(none未推送/pushed已推送/failed推送失败,查询条件)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String pushStatus;
|
||||
@TableField(exist = false)
|
||||
private List<ReceiptClaimSettlement> settlements;
|
||||
@TableField(exist = false)
|
||||
|
||||
+14
@@ -58,6 +58,10 @@ public class ReceiptFlowVO extends KingdeeReceiptFlow {
|
||||
@TableField(exist = false)
|
||||
private String kingdeeClaimStatusName;
|
||||
@TableField(exist = false)
|
||||
private String pushStatusName;
|
||||
@TableField(exist = false)
|
||||
private String kingdeeBillStatusName;
|
||||
@TableField(exist = false)
|
||||
private BigDecimal remainingAmount;
|
||||
@TableField(exist = false)
|
||||
private String createUserName;
|
||||
@@ -75,6 +79,16 @@ public class ReceiptFlowVO extends KingdeeReceiptFlow {
|
||||
@TableField(exist = false)
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime transactionEndTime;
|
||||
/**
|
||||
* 收款金额区间-最小值(查询条件)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private BigDecimal receiptAmountStart;
|
||||
/**
|
||||
* 收款金额区间-最大值(查询条件)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private BigDecimal receiptAmountEnd;
|
||||
@TableField(exist = false)
|
||||
private List<ReceiptFlowRecord> claimRecords = new ArrayList<>();
|
||||
}
|
||||
|
||||
+7
@@ -63,6 +63,13 @@ public class TireReplacementRecordVO extends TireReplacementRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 认证记录视图实体类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "认证记录")
|
||||
public class TransportCertRecordVO extends TransportCertRecord {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "更新人姓名")
|
||||
private String updateUserName;
|
||||
/**
|
||||
* 提交人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "提交人姓名")
|
||||
private String submitUserName;
|
||||
/**
|
||||
* 审核人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "审核人姓名")
|
||||
private String auditUserName;
|
||||
/**
|
||||
* 状态中文文案:审核中 / 已通过 / 已驳回 / 已作废
|
||||
* <p>
|
||||
* 由服务层统一翻译后下发,避免小程序、Web、接口三处各造一套说法。
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "状态中文文案")
|
||||
private String statusText;
|
||||
/**
|
||||
* 司机姓名(认证对象档案的**当前**姓名,不是提交时的历史值)
|
||||
* <p>
|
||||
* 司机侧取认证对象本身;车辆侧取提交该记录的司机。
|
||||
* 解析不到档案时为空——存量订正记录里没有账号的那些司机补不出人,如实留空。
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "司机姓名")
|
||||
private String driverName;
|
||||
/**
|
||||
* 司机手机号(认证对象档案的**当前**手机号,明文下发)
|
||||
* <p>
|
||||
* 审核员靠它联系司机核实材料,故不脱敏;认证快照里从来没有手机号,
|
||||
* 它只有司机档案这一个来源。
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "司机手机号")
|
||||
private String driverMobile;
|
||||
|
||||
}
|
||||
+4
@@ -57,6 +57,10 @@ public class TransportChangeRecordVO extends TransportChangeRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "更新人姓名")
|
||||
private String updateUserName;
|
||||
|
||||
+4
@@ -62,4 +62,8 @@ public class TransportShipVO extends TransportShip {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate warningDate;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -76,4 +76,8 @@ public class TransportVehicleVO extends TransportVehicle {
|
||||
@Schema(description = "绑定司机")
|
||||
private String boundDriver;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
}
|
||||
|
||||
+7
@@ -63,6 +63,13 @@ public class ViolationRecordVO extends ViolationRecord {
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTimeEnd;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ public class VoucherFolderVO implements Serializable {
|
||||
private String folderName;
|
||||
private Long waybillId;
|
||||
private String waybillNo;
|
||||
private String loadingNo;
|
||||
private Integer matched;
|
||||
private String processStatus;
|
||||
private String fileName;
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 运单批量导入单行校验错误。 */
|
||||
@Data
|
||||
@Schema(description = "运单批量导入单行校验错误")
|
||||
public class WaybillImportRowErrorVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "行号(从0开始,相对当前批次)")
|
||||
private Integer rowIndex;
|
||||
|
||||
@Schema(description = "错误信息")
|
||||
private String errorMessage;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** 运单批量导入校验结果。 */
|
||||
@Data
|
||||
@Schema(description = "运单批量导入校验结果")
|
||||
public class WaybillImportValidateResultVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "是否全部通过")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "校验错误列表")
|
||||
private List<WaybillImportRowErrorVO> errors = new ArrayList<>();
|
||||
}
|
||||
+4
@@ -66,6 +66,10 @@ public class WaybillVO extends Waybill {
|
||||
@Schema(description = "业务状态名称")
|
||||
private String businessStatusName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "运输方式名称")
|
||||
private String transportTypeName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否需要司机确认接单(过程配置接单节点)")
|
||||
private Boolean requireAccept;
|
||||
|
||||
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.cache;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.cache.utils.CacheUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.SpringUtil;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
|
||||
/**
|
||||
* 小程序用户缓存
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
public class MpUserCache {
|
||||
|
||||
public static final String MP_USER_CACHE = "blade:mp_user";
|
||||
private static final String MP_USER_CACHE_ID = "mp-user:id:";
|
||||
|
||||
private static IMpUserClient mpUserClient;
|
||||
|
||||
private static IMpUserClient getMpUserClient() {
|
||||
if (mpUserClient == null) {
|
||||
mpUserClient = SpringUtil.getBean(IMpUserClient.class);
|
||||
}
|
||||
return mpUserClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按主键获取小程序用户
|
||||
*/
|
||||
public static MpUser getUser(Long userId) {
|
||||
if (userId == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return CacheUtil.get(MP_USER_CACHE, MP_USER_CACHE_ID, userId, () -> loadUserById(userId));
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("小程序用户缓存读取失败,改为直连查询, userId={}", userId, exception);
|
||||
return loadUserById(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取小程序用户真实姓名(优先 realName → name → account → id)
|
||||
*/
|
||||
public static String getUserRealName(Long userId) {
|
||||
if (userId == null) {
|
||||
return StringPool.EMPTY;
|
||||
}
|
||||
try {
|
||||
MpUser user = getUser(userId);
|
||||
if (user == null) {
|
||||
return Func.toStr(userId);
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getRealName())) {
|
||||
return user.getRealName();
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getName())) {
|
||||
return user.getName();
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getAccount())) {
|
||||
return user.getAccount();
|
||||
}
|
||||
return Func.toStr(userId);
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("翻译小程序用户姓名失败,回退为用户ID, userId={}", userId, exception);
|
||||
return Func.toStr(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理小程序用户缓存
|
||||
*/
|
||||
public static void clear() {
|
||||
CacheUtil.clear(MP_USER_CACHE);
|
||||
CacheUtil.clear(MP_USER_CACHE, Boolean.FALSE);
|
||||
}
|
||||
|
||||
private static MpUser loadUserById(Long userId) {
|
||||
R<MpUser> result = getMpUserClient().userById(userId);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
return null;
|
||||
}
|
||||
return result.getData();
|
||||
}
|
||||
|
||||
}
|
||||
+59
-20
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.springblade.system.cache;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.cache.utils.CacheUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
@@ -42,6 +43,7 @@ import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX;
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
public class UserCache {
|
||||
private static final String USER_CACHE_ID = "user:id:";
|
||||
private static final String USER_CACHE_ACCOUNT = "user:account:";
|
||||
@@ -73,10 +75,18 @@ public class UserCache {
|
||||
* @return
|
||||
*/
|
||||
public static User getUser(Long userId) {
|
||||
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> {
|
||||
R<User> result = getUserClient().userInfoById(userId);
|
||||
return result.getData();
|
||||
});
|
||||
try {
|
||||
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> loadUserById(userId));
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("用户缓存读取失败,清理损坏缓存后重试, userId={}", userId, exception);
|
||||
clearBrokenUserCache();
|
||||
try {
|
||||
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> loadUserById(userId));
|
||||
} catch (RuntimeException retryException) {
|
||||
log.warn("用户缓存重试仍失败,改为直连查询, userId={}", userId, retryException);
|
||||
return loadUserById(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,20 +99,25 @@ public class UserCache {
|
||||
if (userId == null) {
|
||||
return StringPool.EMPTY;
|
||||
}
|
||||
User user = getUser(userId);
|
||||
if (user == null) {
|
||||
try {
|
||||
User user = getUser(userId);
|
||||
if (user == null) {
|
||||
return Func.toStr(userId);
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getRealName())) {
|
||||
return user.getRealName();
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getName())) {
|
||||
return user.getName();
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getAccount())) {
|
||||
return user.getAccount();
|
||||
}
|
||||
return Func.toStr(userId);
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("翻译用户姓名失败,回退为用户ID, userId={}", userId, exception);
|
||||
return Func.toStr(userId);
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getRealName())) {
|
||||
return user.getRealName();
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getName())) {
|
||||
return user.getName();
|
||||
}
|
||||
if (StringUtil.isNotBlank(user.getAccount())) {
|
||||
return user.getAccount();
|
||||
}
|
||||
return Func.toStr(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,10 +128,34 @@ public class UserCache {
|
||||
* @return
|
||||
*/
|
||||
public static User getUser(String tenantId, String account) {
|
||||
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, tenantId + StringPool.DASH + account, () -> {
|
||||
R<User> result = getUserClient().userByAccount(tenantId, account);
|
||||
return result.getData();
|
||||
});
|
||||
String accountKey = tenantId + StringPool.DASH + account;
|
||||
try {
|
||||
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, accountKey, () -> loadUserByAccount(tenantId, account));
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("用户缓存读取失败,清理损坏缓存后重试, accountKey={}", accountKey, exception);
|
||||
clearBrokenUserCache();
|
||||
try {
|
||||
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, accountKey, () -> loadUserByAccount(tenantId, account));
|
||||
} catch (RuntimeException retryException) {
|
||||
log.warn("用户缓存重试仍失败,改为直连查询, accountKey={}", accountKey, retryException);
|
||||
return loadUserByAccount(tenantId, account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static User loadUserById(Long userId) {
|
||||
R<User> result = getUserClient().userInfoById(userId);
|
||||
return result.getData();
|
||||
}
|
||||
|
||||
private static User loadUserByAccount(String tenantId, String account) {
|
||||
R<User> result = getUserClient().userByAccount(tenantId, account);
|
||||
return result.getData();
|
||||
}
|
||||
|
||||
private static void clearBrokenUserCache() {
|
||||
CacheUtil.clear(USER_CACHE);
|
||||
CacheUtil.clear(USER_CACHE, Boolean.FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vendored
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.cache;
|
||||
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
|
||||
/**
|
||||
* 用户展示名解析(系统用户优先,未命中再查小程序用户)
|
||||
* <p>
|
||||
* 小程序链路写入的审计人/提交人 ID 落在 blade_mp_user,需用本工具翻译姓名。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public final class UserDisplayCache {
|
||||
|
||||
private UserDisplayCache() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译用户姓名:先 blade_user,再 blade_mp_user,都没有则回退 ID
|
||||
*/
|
||||
public static String getUserRealName(Long userId) {
|
||||
if (userId == null) {
|
||||
return StringPool.EMPTY;
|
||||
}
|
||||
User user = UserCache.getUser(userId);
|
||||
if (user != null && Func.isNotEmpty(user.getId())) {
|
||||
return resolveName(user.getRealName(), user.getName(), user.getAccount(), userId);
|
||||
}
|
||||
MpUser mpUser = MpUserCache.getUser(userId);
|
||||
if (mpUser != null && Func.isNotEmpty(mpUser.getId())) {
|
||||
return resolveName(mpUser.getRealName(), mpUser.getName(), mpUser.getAccount(), userId);
|
||||
}
|
||||
return Func.toStr(userId);
|
||||
}
|
||||
|
||||
private static String resolveName(String realName, String name, String account, Long userId) {
|
||||
if (StringUtil.isNotBlank(realName)) {
|
||||
return realName;
|
||||
}
|
||||
if (StringUtil.isNotBlank(name)) {
|
||||
return name;
|
||||
}
|
||||
if (StringUtil.isNotBlank(account)) {
|
||||
return account;
|
||||
}
|
||||
return Func.toStr(userId);
|
||||
}
|
||||
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.feign;
|
||||
|
||||
import org.springblade.core.launch.constant.AppConstant;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
* 小程序用户 Feign 接口
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@FeignClient(
|
||||
value = AppConstant.APPLICATION_SYSTEM_NAME
|
||||
)
|
||||
public interface IMpUserClient {
|
||||
|
||||
String API_PREFIX = "/feign/client/mp-user";
|
||||
String USER_BY_ID = API_PREFIX + "/user-by-id";
|
||||
String USER_BY_PHONE = API_PREFIX + "/user-by-phone";
|
||||
String USER_BY_ACCOUNT = API_PREFIX + "/user-by-account";
|
||||
String USER_INFO_BY_ID = API_PREFIX + "/info-by-id";
|
||||
String USER_INFO_BY_PHONE = API_PREFIX + "/info-by-phone";
|
||||
String USER_INFO_BY_ACCOUNT = API_PREFIX + "/info-by-account";
|
||||
String SAVE_USER = API_PREFIX + "/save-user";
|
||||
String UPDATE_USER = API_PREFIX + "/update-user";
|
||||
String BIND_OPENID = API_PREFIX + "/bind-openid";
|
||||
String SET_PASSWORD = API_PREFIX + "/set-password";
|
||||
|
||||
/**
|
||||
* 按主键查询
|
||||
*/
|
||||
@GetMapping(USER_BY_ID)
|
||||
R<MpUser> userById(@RequestParam("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 按租户 + 手机号查询
|
||||
*/
|
||||
@GetMapping(USER_BY_PHONE)
|
||||
R<MpUser> userByPhone(@RequestParam("tenantId") String tenantId, @RequestParam("phone") String phone);
|
||||
|
||||
/**
|
||||
* 按租户 + 账号查询
|
||||
*/
|
||||
@GetMapping(USER_BY_ACCOUNT)
|
||||
R<MpUser> userByAccount(@RequestParam("tenantId") String tenantId, @RequestParam("account") String account);
|
||||
|
||||
/**
|
||||
* 按主键查询登录信息
|
||||
*/
|
||||
@GetMapping(USER_INFO_BY_ID)
|
||||
R<MpUserInfo> userInfoById(@RequestParam("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 按租户 + 手机号查询登录信息
|
||||
*/
|
||||
@GetMapping(USER_INFO_BY_PHONE)
|
||||
R<MpUserInfo> userInfoByPhone(@RequestParam("tenantId") String tenantId, @RequestParam("phone") String phone);
|
||||
|
||||
/**
|
||||
* 按租户 + 账号查询登录信息
|
||||
*/
|
||||
@GetMapping(USER_INFO_BY_ACCOUNT)
|
||||
R<MpUserInfo> userInfoByAccount(@RequestParam("tenantId") String tenantId, @RequestParam("account") String account);
|
||||
|
||||
/**
|
||||
* 新增小程序用户
|
||||
*/
|
||||
@PostMapping(SAVE_USER)
|
||||
R<Boolean> saveUser(@RequestBody MpUser user);
|
||||
|
||||
/**
|
||||
* 更新小程序用户
|
||||
*/
|
||||
@PostMapping(UPDATE_USER)
|
||||
R<Boolean> updateUser(@RequestBody MpUser user);
|
||||
|
||||
/**
|
||||
* 绑定/更新微信小程序 openid
|
||||
*/
|
||||
@PostMapping(BIND_OPENID)
|
||||
R<Boolean> bindOpenId(@RequestParam("tenantId") String tenantId,
|
||||
@RequestParam("userId") Long userId,
|
||||
@RequestParam("openid") String openid);
|
||||
|
||||
/**
|
||||
* 设置小程序用户密码
|
||||
*/
|
||||
@PostMapping(SET_PASSWORD)
|
||||
R<Boolean> setPassword(@RequestParam("userId") Long userId,
|
||||
@RequestParam("password") String password,
|
||||
@RequestParam("password2") String password2);
|
||||
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
import org.springblade.core.tool.jackson.Sensitive;
|
||||
import org.springblade.core.tool.sensitive.SensitiveType;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 小程序用户实体(仅用于小程序登录,与 blade_user 分离)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@TableName("blade_mp_user")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "小程序用户")
|
||||
public class MpUser extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
@Schema(description = "用户编号")
|
||||
private String code;
|
||||
/**
|
||||
* 账号
|
||||
*/
|
||||
@Schema(description = "账号")
|
||||
private String account;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@Schema(description = "昵称")
|
||||
private String name;
|
||||
/**
|
||||
* 真名
|
||||
*/
|
||||
@Schema(description = "真名")
|
||||
private String realName;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@Sensitive(type = SensitiveType.EMAIL)
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
/**
|
||||
* 手机
|
||||
*/
|
||||
@Sensitive(type = SensitiveType.MOBILE)
|
||||
@Schema(description = "手机")
|
||||
private String phone;
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
@Schema(description = "生日")
|
||||
private Date birthday;
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
@Schema(description = "性别")
|
||||
private Integer sex;
|
||||
/**
|
||||
* 微信小程序 openid
|
||||
*/
|
||||
@Schema(description = "微信小程序openid")
|
||||
private String openid;
|
||||
/**
|
||||
* 小程序身份,逗号分隔:driver,admin
|
||||
*/
|
||||
@Schema(description = "小程序身份")
|
||||
private String identities;
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
@Schema(description = "部门id")
|
||||
private String deptId;
|
||||
/**
|
||||
* 岗位id
|
||||
*/
|
||||
@Schema(description = "岗位id")
|
||||
private String postId;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.pojo.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 小程序用户登录信息
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "小程序用户信息")
|
||||
public class MpUserInfo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户基础信息
|
||||
*/
|
||||
@Schema(description = "小程序用户")
|
||||
private MpUser user;
|
||||
|
||||
/**
|
||||
* 身份编码集合
|
||||
*/
|
||||
@Schema(description = "身份集合")
|
||||
private List<String> identities;
|
||||
|
||||
/**
|
||||
* 权限标识集合
|
||||
*/
|
||||
@Schema(description = "权限集合")
|
||||
private List<String> permissions;
|
||||
|
||||
/**
|
||||
* 拓展信息
|
||||
*/
|
||||
@Schema(description = "拓展信息")
|
||||
private Kv detail;
|
||||
|
||||
}
|
||||
+6
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.springblade.system.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
@@ -131,6 +132,11 @@ public class User extends TenantEntity implements IPhone {
|
||||
* 是否是oa的账号
|
||||
*/
|
||||
private Integer isOa;
|
||||
/**
|
||||
* OA上次更新时间(OA 的 modified 字段),仅同步比对使用
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
|
||||
private String oaModified;
|
||||
/**
|
||||
* 是否推送mk
|
||||
*/
|
||||
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.pojo.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 小程序用户身份枚举(可叠加,后续可扩展)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MpUserIdentity {
|
||||
|
||||
/**
|
||||
* 司机
|
||||
*/
|
||||
DRIVER("driver", "司机"),
|
||||
|
||||
/**
|
||||
* 管理员(调度端)
|
||||
*/
|
||||
ADMIN("admin", "管理员"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 身份编码
|
||||
*/
|
||||
private final String code;
|
||||
/**
|
||||
* 身份名称
|
||||
*/
|
||||
private final String label;
|
||||
|
||||
/**
|
||||
* 管理员身份对应的小程序权限码(兼容原 mp_auth)
|
||||
*/
|
||||
public static final String PERMISSION_MP_AUTH = "mp_auth";
|
||||
|
||||
/**
|
||||
* detail 中标记小程序用户的 type 值
|
||||
*/
|
||||
public static final String DETAIL_TYPE = "mp";
|
||||
|
||||
/**
|
||||
* 解析身份编码
|
||||
*/
|
||||
public static MpUserIdentity of(String code) {
|
||||
if (Func.isBlank(code)) {
|
||||
return null;
|
||||
}
|
||||
String normalized = code.trim().toLowerCase(Locale.ROOT);
|
||||
return Arrays.stream(values())
|
||||
.filter(item -> item.code.equals(normalized))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拆分身份串为编码列表(去重、保序)
|
||||
*/
|
||||
public static List<String> split(String identities) {
|
||||
if (Func.isBlank(identities)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Set<String> identitySet = new LinkedHashSet<>();
|
||||
for (String part : Func.toStrList(identities)) {
|
||||
if (Func.isBlank(part)) {
|
||||
continue;
|
||||
}
|
||||
String code = part.trim().toLowerCase(Locale.ROOT);
|
||||
if (of(code) != null || StringUtil.isNotBlank(code)) {
|
||||
identitySet.add(code);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(identitySet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并身份,确保包含指定身份
|
||||
*/
|
||||
public static String merge(String identities, String... appendCodes) {
|
||||
Set<String> identitySet = new LinkedHashSet<>(split(identities));
|
||||
if (appendCodes != null) {
|
||||
for (String code : appendCodes) {
|
||||
if (Func.isNotBlank(code)) {
|
||||
identitySet.add(code.trim().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
}
|
||||
return identitySet.stream().collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否包含指定身份
|
||||
*/
|
||||
public static boolean has(String identities, MpUserIdentity identity) {
|
||||
if (identity == null) {
|
||||
return false;
|
||||
}
|
||||
return split(identities).contains(identity.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据身份生成小程序权限码(不再依赖系统角色菜单)
|
||||
*/
|
||||
public static List<String> toPermissions(String identities) {
|
||||
List<String> permissions = new ArrayList<>();
|
||||
if (has(identities, ADMIN)) {
|
||||
permissions.add(PERMISSION_MP_AUTH);
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user