🔀 合并主干 master 到 dev
- 状态命名统一用 dev 的 pending_return_confirm:master 侧 pending_return 常量、字面量与文案分支一并改名,避免两套状态并存 - 汇票可选按 master 放开:去掉部门/收付款方限制,删除随之失效的 selectableBill 判定 - 结算导入模板按 master 重写:不预置费用项列、不再有结算金额列,导出签名收为两参 - 合入 master 的待确认回单批量完成、变更记录审批过滤、小程序数据权限(MpUserDataScope)与评分量化/客商档案改动 - 受影响的测试构造器补上 MpUserDataScopeSupport 依赖
This commit is contained in:
+6
@@ -207,6 +207,12 @@ public class Dept extends TenantEntity {
|
||||
@Schema(description = "是否平台公司:0否,1是")
|
||||
private Integer isPlatformCompany;
|
||||
|
||||
/**
|
||||
* 是否业务部门:0否,1是
|
||||
*/
|
||||
@Schema(description = "是否业务部门:0否,1是")
|
||||
private Integer isBusinessDept;
|
||||
|
||||
/**
|
||||
* OA主键,仅同步记录,不对外展示
|
||||
*/
|
||||
|
||||
+4
@@ -26,6 +26,10 @@ public class FormalSettlementSaveRequest implements Serializable {
|
||||
private List<Long> sourcePreSettlementIds;
|
||||
private List<Long> sourceDetailIds;
|
||||
private List<DetailAdjustment> detailAdjustments;
|
||||
/** 业务模式(业务字典 business_model) */
|
||||
private String businessModel;
|
||||
/** 运输方式(业务字典 transport_type) */
|
||||
private String transportType;
|
||||
private LocalDate exchangeRateDate;
|
||||
private BigDecimal exchangeRate;
|
||||
private String attachmentsJson;
|
||||
|
||||
+6
@@ -55,6 +55,12 @@ public class PreSettlementSaveRequest implements Serializable {
|
||||
@Schema(description = "结算类型:receivable/payable")
|
||||
private String settlementType;
|
||||
|
||||
@Schema(description = "业务模式(业务字典 business_model)")
|
||||
private String businessModel;
|
||||
|
||||
@Schema(description = "运输方式(业务字典 transport_type)")
|
||||
private String transportType;
|
||||
|
||||
@Schema(description = "汇率日期")
|
||||
private LocalDate exchangeRateDate;
|
||||
|
||||
|
||||
+4
@@ -199,6 +199,10 @@ public class ContractManage extends TenantEntity {
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String changeContent;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "变更类型")
|
||||
private String changeType;
|
||||
|
||||
@Schema(description = "变更原因")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String changeReason;
|
||||
|
||||
+6
@@ -32,6 +32,7 @@ import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 评分量化表实体类
|
||||
@@ -62,5 +63,10 @@ public class CreditScoreQuantification extends TenantEntity {
|
||||
*/
|
||||
@Schema(description = "标准说明")
|
||||
private String standardDescription;
|
||||
/**
|
||||
* 减分项目最高扣分限制:客商评分时所有减分项合计不得超过该值,超出按该上限计分
|
||||
*/
|
||||
@Schema(description = "减分项目最高扣分限制")
|
||||
private BigDecimal maxMinusScore;
|
||||
|
||||
}
|
||||
|
||||
+12
@@ -115,6 +115,10 @@ public class CustomerArchive extends TenantEntity {
|
||||
@Schema(description = "营业期限类型")
|
||||
private String businessTermType;
|
||||
|
||||
@Schema(description = "营业期限开始日")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private LocalDate businessStartDate;
|
||||
|
||||
@Schema(description = "营业期限截止日")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private LocalDate businessEndDate;
|
||||
@@ -123,6 +127,10 @@ public class CustomerArchive extends TenantEntity {
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private BigDecimal registeredCapital;
|
||||
|
||||
@Schema(description = "实缴资本(万元)")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private BigDecimal paidInCapital;
|
||||
|
||||
@Schema(description = "客商负责人")
|
||||
private String principal;
|
||||
|
||||
@@ -140,6 +148,10 @@ public class CustomerArchive extends TenantEntity {
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private BigDecimal applyCreditLimit;
|
||||
|
||||
@Schema(description = "已使用额度(万元),新建可录历史值,后续由收付款业务累加/减更新")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private BigDecimal usedCreditLimit;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
+8
@@ -88,6 +88,14 @@ public class FormalSettlement extends TenantEntity {
|
||||
* 结算金额(结算币种,含全部合计费用)
|
||||
*/
|
||||
private BigDecimal settlementAmount;
|
||||
/**
|
||||
* 业务模式(业务字典 business_model)
|
||||
*/
|
||||
private String businessModel;
|
||||
/**
|
||||
* 运输方式(业务字典 transport_type)
|
||||
*/
|
||||
private String transportType;
|
||||
/**
|
||||
* 本位币,默认 RMB
|
||||
*/
|
||||
|
||||
+6
@@ -92,6 +92,12 @@ public class PreSettlement extends TenantEntity {
|
||||
@Schema(description = "结算金额")
|
||||
private BigDecimal settlementAmount;
|
||||
|
||||
@Schema(description = "业务模式(业务字典 business_model)")
|
||||
private String businessModel;
|
||||
|
||||
@Schema(description = "运输方式(业务字典 transport_type)")
|
||||
private String transportType;
|
||||
|
||||
@Schema(description = "本位币")
|
||||
private String localCurrency;
|
||||
|
||||
|
||||
+8
@@ -29,6 +29,7 @@ import lombok.EqualsAndHashCode;
|
||||
import org.springblade.transport.pojo.entity.CustomerCreditScore;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -57,4 +58,11 @@ public class CustomerCreditScoreVO extends CustomerCreditScore {
|
||||
@Schema(description = "评估标准")
|
||||
private List<CreditRatingStandardVO> standards = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 来自评分量化表的减分项目最高扣分限制(非落库字段,供前端计分使用)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "减分项目最高扣分限制")
|
||||
private BigDecimal maxMinusScore;
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -43,6 +43,8 @@ public class FormalSettlementVO extends FormalSettlement implements KingdeeUnloc
|
||||
@TableField(exist = false) private String ids;
|
||||
@TableField(exist = false) private String approvalStatusName;
|
||||
@TableField(exist = false) private String settlementTypeName;
|
||||
@TableField(exist = false) @Schema(description = "业务模式名称") private String businessModelName;
|
||||
@TableField(exist = false) @Schema(description = "运输方式名称") private String transportTypeName;
|
||||
@TableField(exist = false) private Integer attachmentCount;
|
||||
@TableField(exist = false) private String attachmentSyncStatus;
|
||||
@TableField(exist = false) private String attachmentSyncStatusName;
|
||||
|
||||
+8
@@ -85,6 +85,14 @@ public class PreSettlementVO extends PreSettlement {
|
||||
@Schema(description = "结算类型名称")
|
||||
private String settlementTypeName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "业务模式名称")
|
||||
private String businessModelName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "运输方式名称")
|
||||
private String transportTypeName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "结算明细")
|
||||
private List<PreSettlementDetail> details;
|
||||
|
||||
+3
@@ -56,6 +56,9 @@ public class TripQueryVO implements Serializable {
|
||||
@Schema(description = "可见组织 ID 集合:非空时只出这些组织的运单行,空集合表示不按组织过滤")
|
||||
private List<Long> deptIds;
|
||||
|
||||
@Schema(description = "可见项目 ID 集合:非空时只出这些项目的运单行,空集合表示不按项目过滤(小程序调度端优先用此口径)")
|
||||
private List<Long> projectIds;
|
||||
|
||||
@Schema(description = "状态:0待接单/1进行中/2已完成/3已取消,不传为全部(不含已取消)")
|
||||
private Integer status;
|
||||
|
||||
|
||||
+8
@@ -27,6 +27,7 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.system.pojo.dto.MpUserEnsureDTO;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.vo.MpUserDataScopeVO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -56,6 +57,7 @@ public interface IMpUserClient {
|
||||
String BIND_OPENID = API_PREFIX + "/bind-openid";
|
||||
String SET_PASSWORD = API_PREFIX + "/set-password";
|
||||
String ENSURE_USER = API_PREFIX + "/ensure-user";
|
||||
String RESOLVE_DATA_SCOPE = API_PREFIX + "/resolve-data-scope";
|
||||
|
||||
/**
|
||||
* 按主键查询
|
||||
@@ -63,6 +65,12 @@ public interface IMpUserClient {
|
||||
@GetMapping(USER_BY_ID)
|
||||
R<MpUser> userById(@RequestParam("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 解析小程序用户可见组织(所属组织 + 数据权限范围 + 数据层级范围)
|
||||
*/
|
||||
@GetMapping(RESOLVE_DATA_SCOPE)
|
||||
R<MpUserDataScopeVO> resolveDataScope(@RequestParam("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 按租户 + 手机号查询
|
||||
*/
|
||||
|
||||
+19
-1
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.springblade.system.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -35,6 +36,7 @@ import org.springblade.core.tool.sensitive.SensitiveType;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 小程序用户实体(仅用于小程序登录,与 blade_user 分离)
|
||||
@@ -113,10 +115,26 @@ public class MpUser extends TenantEntity {
|
||||
@Schema(description = "小程序身份")
|
||||
private String identities;
|
||||
/**
|
||||
* 部门id
|
||||
* 部门id(多选,逗号分隔)
|
||||
*/
|
||||
@Schema(description = "部门id")
|
||||
private String deptId;
|
||||
/**
|
||||
* 数据权限范围:1-仅所属组织,2-全部组织,3-自定义
|
||||
*/
|
||||
@Schema(description = "数据权限范围")
|
||||
private Integer dataScopeRange;
|
||||
/**
|
||||
* 数据层级范围:1-包含下级,2-仅本级
|
||||
*/
|
||||
@Schema(description = "数据层级范围")
|
||||
private Integer dataLevelRange;
|
||||
/**
|
||||
* 自定义数据权限组织ID列表(dataScopeRange=3 时生效)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "自定义数据权限组织ID")
|
||||
private List<Long> dataScopeDeptIds;
|
||||
/**
|
||||
* 岗位id
|
||||
*/
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.system.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 小程序用户组织数据权限
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@TableName("blade_mp_user_data_scope")
|
||||
@Schema(description = "小程序用户组织数据权限")
|
||||
public class MpUserDataScope implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "小程序用户主键")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "组织主键")
|
||||
private Long deptId;
|
||||
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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.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;
|
||||
|
||||
/**
|
||||
* 小程序用户数据权限解析结果
|
||||
* <p>
|
||||
* allDept=true 表示全部组织(查询不过滤);否则按 deptIds 收口,空列表表示无可见组织。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "小程序用户数据权限")
|
||||
public class MpUserDataScopeVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 是否全部组织(不过滤)
|
||||
*/
|
||||
@Schema(description = "是否全部组织")
|
||||
private boolean allDept;
|
||||
|
||||
/**
|
||||
* 可见组织 ID;allDept=false 时生效,空表示无权限
|
||||
*/
|
||||
@Schema(description = "可见组织ID")
|
||||
private List<Long> deptIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 全部组织
|
||||
*/
|
||||
public static MpUserDataScopeVO all() {
|
||||
MpUserDataScopeVO scope = new MpUserDataScopeVO();
|
||||
scope.setAllDept(true);
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* 无可见组织
|
||||
*/
|
||||
public static MpUserDataScopeVO empty() {
|
||||
MpUserDataScopeVO scope = new MpUserDataScopeVO();
|
||||
scope.setAllDept(false);
|
||||
scope.setDeptIds(new ArrayList<>());
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定可见组织
|
||||
*/
|
||||
public static MpUserDataScopeVO of(List<Long> deptIds) {
|
||||
MpUserDataScopeVO scope = new MpUserDataScopeVO();
|
||||
scope.setAllDept(false);
|
||||
scope.setDeptIds(deptIds == null ? new ArrayList<>() : new ArrayList<>(deptIds));
|
||||
return scope;
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -42,6 +42,13 @@ public class MpUserVO extends MpUser {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 所属组织名称(逗号分隔)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "所属组织名称")
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 身份名称(逗号分隔中文)
|
||||
*/
|
||||
|
||||
+11
@@ -260,6 +260,17 @@ public class DeptController extends BladeController {
|
||||
return R.data(deptService.listPlatformCompany());
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务部门下拉(是否业务部门=是)
|
||||
*/
|
||||
@PreAuth(AuthConstant.PERMIT_ALL)
|
||||
@GetMapping("/business-dept-select")
|
||||
@ApiOperationSupport(order = 12)
|
||||
@Operation(summary = "业务部门下拉", description = "返回是否业务部门=是的部门列表")
|
||||
public R<List<Dept>> businessDeptSelect() {
|
||||
return R.data(deptService.listBusinessDept());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门的主管信息
|
||||
*/
|
||||
|
||||
+2
-1
@@ -71,7 +71,8 @@ public class MpUserController extends BladeController {
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "详情", description = "传入id")
|
||||
public R<MpUserVO> detail(MpUser mpUser) {
|
||||
MpUser detail = mpUserService.getOne(Condition.getQueryWrapper(mpUser));
|
||||
Long id = mpUser == null ? null : mpUser.getId();
|
||||
MpUser detail = id == null ? null : mpUserService.detail(id);
|
||||
if (detail == null) {
|
||||
return R.fail("小程序用户不存在");
|
||||
}
|
||||
|
||||
+8
@@ -29,10 +29,12 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.system.pojo.dto.MpUserEnsureDTO;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.vo.MpUserDataScopeVO;
|
||||
import org.springblade.system.service.IMpUserService;
|
||||
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;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@@ -54,6 +56,12 @@ public class MpUserClient implements IMpUserClient {
|
||||
return R.data(mpUserService.getById(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(RESOLVE_DATA_SCOPE)
|
||||
public R<MpUserDataScopeVO> resolveDataScope(@RequestParam("userId") Long userId) {
|
||||
return R.data(mpUserService.resolveDataScope(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(USER_BY_PHONE)
|
||||
public R<MpUser> userByPhone(String tenantId, String phone) {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<result column="mnemonic_code" property="mnemonicCode"/>
|
||||
<result column="carrier_customer_id" property="carrierCustomerId"/>
|
||||
<result column="is_platform_company" property="isPlatformCompany"/>
|
||||
<result column="is_business_dept" property="isBusinessDept"/>
|
||||
<result column="sort" property="sort"/>
|
||||
<result column="remark" property="remark"/>
|
||||
<result column="status" property="status"/>
|
||||
@@ -43,6 +44,7 @@
|
||||
<result column="mnemonic_code" property="mnemonicCode"/>
|
||||
<result column="carrier_customer_id" property="carrierCustomerId"/>
|
||||
<result column="is_platform_company" property="isPlatformCompany"/>
|
||||
<result column="is_business_dept" property="isBusinessDept"/>
|
||||
<result column="sort" property="sort"/>
|
||||
<result column="remark" property="remark"/>
|
||||
<result column="status" property="status"/>
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springblade.system.pojo.entity.MpUserDataScope;
|
||||
|
||||
/**
|
||||
* 小程序用户组织数据权限 Mapper
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface MpUserDataScopeMapper extends BaseMapper<MpUserDataScope> {
|
||||
}
|
||||
+7
@@ -106,6 +106,13 @@ public interface IDeptService extends IService<Dept> {
|
||||
*/
|
||||
List<Dept> listPlatformCompany();
|
||||
|
||||
/**
|
||||
* 业务部门下拉(是否业务部门=是)
|
||||
*
|
||||
* @return 业务部门列表
|
||||
*/
|
||||
List<Dept> listBusinessDept();
|
||||
|
||||
/**
|
||||
* 根据部门名称精确匹配获取部门ID集合
|
||||
*
|
||||
|
||||
+13
@@ -27,6 +27,7 @@ import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.system.pojo.dto.MpUserEnsureDTO;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.vo.MpUserDataScopeVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -80,6 +81,18 @@ public interface IMpUserService extends BaseService<MpUser> {
|
||||
*/
|
||||
IPage<MpUser> selectMpUserPage(IPage<MpUser> page, MpUser query);
|
||||
|
||||
/**
|
||||
* 详情(含自定义数据权限组织)
|
||||
*/
|
||||
MpUser detail(Long id);
|
||||
|
||||
/**
|
||||
* 解析小程序用户可见组织(所属组织 + 数据权限范围 + 数据层级范围)
|
||||
* <p>
|
||||
* allDept=true 表示全部组织;否则 deptIds 为可见组织,空列表表示无权限。
|
||||
*/
|
||||
MpUserDataScopeVO resolveDataScope(Long userId);
|
||||
|
||||
/**
|
||||
* 保存小程序用户
|
||||
*/
|
||||
|
||||
+22
-1
@@ -337,6 +337,18 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
return list(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Dept> listBusinessDept() {
|
||||
LambdaQueryWrapper<Dept> queryWrapper = Wrappers.<Dept>lambdaQuery()
|
||||
.eq(Dept::getIsBusinessDept, 1)
|
||||
.orderByAsc(Dept::getSort)
|
||||
.orderByAsc(Dept::getId);
|
||||
if (!AuthUtil.isAdministrator()) {
|
||||
queryWrapper.eq(Dept::getTenantId, AuthUtil.getTenantId());
|
||||
}
|
||||
return list(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeptIds(String tenantId, String deptNames) {
|
||||
List<Dept> deptList = baseMapper.selectList(Wrappers.<Dept>query().lambda().eq(Dept::getTenantId, tenantId).in(Dept::getDeptName, Func.toStrList(deptNames)));
|
||||
@@ -443,6 +455,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
rootDept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||
rootDept.setIsOa(0);
|
||||
rootDept.setIsPlatformCompany(0);
|
||||
rootDept.setIsBusinessDept(0);
|
||||
rootDept.setSyncTime(new Date());
|
||||
this.save(rootDept);
|
||||
log.info("已创建 OA 同步集团顶级 id={} name={} code={}",
|
||||
@@ -536,6 +549,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
newRoot.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||
newRoot.setIsOa(0);
|
||||
newRoot.setIsPlatformCompany(0);
|
||||
newRoot.setIsBusinessDept(0);
|
||||
newRoot.setSyncTime(new Date());
|
||||
this.save(newRoot);
|
||||
log.info("迁移创建新集团顶级 id={}(来自历史 id={})", newRootId, legacyRootId);
|
||||
@@ -598,6 +612,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
externalDept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||
externalDept.setIsOa(0);
|
||||
externalDept.setIsPlatformCompany(0);
|
||||
externalDept.setIsBusinessDept(0);
|
||||
externalDept.setSyncTime(new Date());
|
||||
this.save(externalDept);
|
||||
log.info("已创建外部组织顶级 id={}", externalDept.getId());
|
||||
@@ -678,6 +693,9 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
if (dept.getIsPlatformCompany() == null) {
|
||||
dept.setIsPlatformCompany(0);
|
||||
}
|
||||
if (dept.getIsBusinessDept() == null) {
|
||||
dept.setIsBusinessDept(0);
|
||||
}
|
||||
// 排序未填时默认 1
|
||||
if (dept.getSort() == null) {
|
||||
dept.setSort(1);
|
||||
@@ -804,6 +822,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
dept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||
dept.setIsOa(1);
|
||||
dept.setIsPlatformCompany(0);
|
||||
dept.setIsBusinessDept(0);
|
||||
dept.setSyncTime(new Date());
|
||||
boolean saved = save(dept);
|
||||
if (saved) {
|
||||
@@ -877,7 +896,9 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
|
||||
private void validateContractInitialSerial(Dept dept) {
|
||||
Integer serial = dept.getContractInitialSerial();
|
||||
if (serial == null) {
|
||||
// -1 为空占位,未填写时按 null 落库,不自动保留 -1
|
||||
if (serial == null || serial == -1) {
|
||||
dept.setContractInitialSerial(null);
|
||||
return;
|
||||
}
|
||||
if (serial < 1 || serial > 999) {
|
||||
|
||||
+152
-2
@@ -38,16 +38,21 @@ 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.cache.MpUserCache;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.mapper.MpUserDataScopeMapper;
|
||||
import org.springblade.system.mapper.MpUserMapper;
|
||||
import org.springblade.system.pojo.dto.MpUserEnsureDTO;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserDataScope;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springblade.system.pojo.vo.MpUserDataScopeVO;
|
||||
import org.springblade.system.service.IMpUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -72,6 +77,19 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
*/
|
||||
private static final String TENANT_HEADER = "Tenant-Id";
|
||||
|
||||
/** 数据权限范围:仅所属组织 */
|
||||
private static final int DATA_SCOPE_OWN = 1;
|
||||
/** 数据权限范围:全部组织 */
|
||||
private static final int DATA_SCOPE_ALL = 2;
|
||||
/** 数据权限范围:自定义 */
|
||||
private static final int DATA_SCOPE_CUSTOM = 3;
|
||||
/** 数据层级范围:包含下级 */
|
||||
private static final int DATA_LEVEL_INCLUDE_CHILDREN = 1;
|
||||
/** 数据层级范围:仅本级 */
|
||||
private static final int DATA_LEVEL_CURRENT_ONLY = 2;
|
||||
|
||||
private final MpUserDataScopeMapper mpUserDataScopeMapper;
|
||||
|
||||
@Override
|
||||
public MpUser getByPhone(String tenantId, String phone) {
|
||||
if (Func.isBlank(tenantId) || Func.isBlank(phone)) {
|
||||
@@ -213,6 +231,64 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
.orderByDesc(MpUser::getCreateTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MpUser detail(Long id) {
|
||||
if (Func.isEmpty(id)) {
|
||||
return null;
|
||||
}
|
||||
return fillScopes(getById(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MpUserDataScopeVO resolveDataScope(Long userId) {
|
||||
if (Func.isEmpty(userId) || userId <= 0) {
|
||||
return MpUserDataScopeVO.empty();
|
||||
}
|
||||
MpUser user = getById(userId);
|
||||
if (user == null || Func.isEmpty(user.getId())) {
|
||||
return MpUserDataScopeVO.empty();
|
||||
}
|
||||
Integer dataScopeRange = user.getDataScopeRange() == null ? DATA_SCOPE_OWN : user.getDataScopeRange();
|
||||
Integer dataLevelRange = user.getDataLevelRange() == null ? DATA_LEVEL_INCLUDE_CHILDREN : user.getDataLevelRange();
|
||||
if (Objects.equals(dataScopeRange, DATA_SCOPE_ALL)) {
|
||||
return MpUserDataScopeVO.all();
|
||||
}
|
||||
List<Long> baseDeptIds;
|
||||
if (Objects.equals(dataScopeRange, DATA_SCOPE_CUSTOM)) {
|
||||
baseDeptIds = mpUserDataScopeMapper.selectList(Wrappers.<MpUserDataScope>lambdaQuery()
|
||||
.eq(MpUserDataScope::getUserId, userId))
|
||||
.stream()
|
||||
.map(MpUserDataScope::getDeptId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
} else {
|
||||
// 仅所属组织
|
||||
baseDeptIds = Func.toLongList(user.getDeptId());
|
||||
}
|
||||
if (Func.isEmpty(baseDeptIds)) {
|
||||
return MpUserDataScopeVO.empty();
|
||||
}
|
||||
boolean includeChildren = !Objects.equals(dataLevelRange, DATA_LEVEL_CURRENT_ONLY);
|
||||
LinkedHashSet<Long> allowedDeptIds = new LinkedHashSet<>();
|
||||
for (Long deptId : baseDeptIds) {
|
||||
if (Func.isEmpty(deptId) || deptId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (includeChildren) {
|
||||
List<Long> childIds = SysCache.getDeptChildIds(deptId);
|
||||
if (Func.isNotEmpty(childIds)) {
|
||||
allowedDeptIds.addAll(childIds);
|
||||
} else {
|
||||
allowedDeptIds.add(deptId);
|
||||
}
|
||||
} else {
|
||||
allowedDeptIds.add(deptId);
|
||||
}
|
||||
}
|
||||
return MpUserDataScopeVO.of(List.copyOf(allowedDeptIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submit(MpUser user) {
|
||||
@@ -223,7 +299,7 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
throw new ServiceException("该手机号已存在小程序用户");
|
||||
}
|
||||
encryptPasswordIfPresent(user);
|
||||
boolean saved = save(user);
|
||||
boolean saved = save(user) && submitScopes(user);
|
||||
if (saved) {
|
||||
MpUserCache.clear();
|
||||
}
|
||||
@@ -236,7 +312,18 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
if (user == null || Func.isEmpty(user.getId())) {
|
||||
throw new ServiceException("小程序用户主键不能为空");
|
||||
}
|
||||
MpUser dbUser = getById(user.getId());
|
||||
if (dbUser == null || Func.isEmpty(dbUser.getId())) {
|
||||
throw new ServiceException("小程序用户不存在");
|
||||
}
|
||||
prepare(user);
|
||||
// 未改手机号(前端脱敏未提交 / 仍为脱敏值)时沿用库中原值,避免把 138****8000 写回
|
||||
if (Func.isBlank(user.getPhone()) || isMaskedPhone(user.getPhone())) {
|
||||
user.setPhone(dbUser.getPhone());
|
||||
}
|
||||
if (Func.isBlank(user.getEmail()) || isMaskedEmail(user.getEmail())) {
|
||||
user.setEmail(dbUser.getEmail());
|
||||
}
|
||||
validate(user);
|
||||
MpUser existed = getByPhone(user.getTenantId(), user.getPhone());
|
||||
if (existed != null && Func.isNotEmpty(existed.getId()) && !existed.getId().equals(user.getId())) {
|
||||
@@ -244,7 +331,7 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
}
|
||||
// 后台编辑不改密码,避免空串覆盖
|
||||
user.setPassword(null);
|
||||
boolean updated = updateById(user);
|
||||
boolean updated = updateById(user) && submitScopes(user);
|
||||
if (updated) {
|
||||
MpUserCache.clear();
|
||||
}
|
||||
@@ -268,6 +355,7 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
}
|
||||
boolean removed = deleteLogic(ids);
|
||||
if (removed) {
|
||||
mpUserDataScopeMapper.delete(Wrappers.<MpUserDataScope>lambdaQuery().in(MpUserDataScope::getUserId, ids));
|
||||
MpUserCache.clear();
|
||||
}
|
||||
return removed;
|
||||
@@ -422,6 +510,15 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
user.setPostId(trimToNull(user.getPostId()));
|
||||
user.setRemark(trimToNull(user.getRemark()));
|
||||
user.setIdentities(normalizeIdentities(user.getIdentities()));
|
||||
if (user.getDataScopeRange() == null) {
|
||||
user.setDataScopeRange(1);
|
||||
}
|
||||
if (user.getDataLevelRange() == null) {
|
||||
user.setDataLevelRange(1);
|
||||
}
|
||||
if (!Objects.equals(user.getDataScopeRange(), 3)) {
|
||||
user.setDataScopeDeptIds(List.of());
|
||||
}
|
||||
if (Func.isBlank(user.getAccount()) && Func.isNotBlank(user.getPhone())) {
|
||||
user.setAccount(user.getPhone());
|
||||
}
|
||||
@@ -455,6 +552,9 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
if (Func.isBlank(user.getPhone())) {
|
||||
throw new ServiceException("手机号不能为空");
|
||||
}
|
||||
if (Func.isBlank(user.getDeptId())) {
|
||||
throw new ServiceException("请选择所属组织");
|
||||
}
|
||||
if (Func.isBlank(user.getIdentities())) {
|
||||
throw new ServiceException("小程序身份不能为空");
|
||||
}
|
||||
@@ -467,6 +567,42 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
throw new ServiceException("不支持的小程序身份:" + code);
|
||||
}
|
||||
}
|
||||
Integer dataScopeRange = user.getDataScopeRange();
|
||||
if (dataScopeRange == null || (dataScopeRange != 1 && dataScopeRange != 2 && dataScopeRange != 3)) {
|
||||
throw new ServiceException("数据权限范围不正确");
|
||||
}
|
||||
Integer dataLevelRange = user.getDataLevelRange();
|
||||
if (dataLevelRange == null || (dataLevelRange != 1 && dataLevelRange != 2)) {
|
||||
throw new ServiceException("数据层级范围不正确");
|
||||
}
|
||||
if (Objects.equals(dataScopeRange, 3) && Func.isEmpty(user.getDataScopeDeptIds())) {
|
||||
throw new ServiceException("请选择自定义数据权限组织");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean submitScopes(MpUser user) {
|
||||
if (user == null || Func.isEmpty(user.getId())) {
|
||||
return false;
|
||||
}
|
||||
mpUserDataScopeMapper.delete(Wrappers.<MpUserDataScope>lambdaQuery().eq(MpUserDataScope::getUserId, user.getId()));
|
||||
if (Objects.equals(user.getDataScopeRange(), 3) && Func.isNotEmpty(user.getDataScopeDeptIds())) {
|
||||
user.getDataScopeDeptIds().stream().filter(Objects::nonNull).distinct().forEach(deptId -> {
|
||||
MpUserDataScope scope = new MpUserDataScope();
|
||||
scope.setUserId(user.getId());
|
||||
scope.setDeptId(deptId);
|
||||
mpUserDataScopeMapper.insert(scope);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private MpUser fillScopes(MpUser user) {
|
||||
if (user == null || Func.isEmpty(user.getId())) {
|
||||
return user;
|
||||
}
|
||||
user.setDataScopeDeptIds(mpUserDataScopeMapper.selectList(Wrappers.<MpUserDataScope>lambdaQuery()
|
||||
.eq(MpUserDataScope::getUserId, user.getId())).stream().map(MpUserDataScope::getDeptId).toList());
|
||||
return user;
|
||||
}
|
||||
|
||||
private String normalizeIdentities(String identities) {
|
||||
@@ -485,4 +621,18 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
|
||||
return trimValue.isEmpty() ? null : trimValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为前端脱敏手机号(如 138****8000),不可直接落库。
|
||||
*/
|
||||
private boolean isMaskedPhone(String phone) {
|
||||
return Func.isNotBlank(phone) && phone.contains("*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为前端脱敏邮箱。
|
||||
*/
|
||||
private boolean isMaskedEmail(String email) {
|
||||
return Func.isNotBlank(email) && email.contains("*");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+57
-1
@@ -935,7 +935,9 @@ public class OASyncServiceImpl implements IOASyncService {
|
||||
.ne(Dept::getOaId, "")
|
||||
.select(Dept::getId, Dept::getDeptCode, Dept::getDeptName, Dept::getFullName,
|
||||
Dept::getOaModified, Dept::getParentId, Dept::getOaId, Dept::getBelongCompanyCode,
|
||||
Dept::getParentCode, Dept::getOaSupSubComId));
|
||||
Dept::getParentCode, Dept::getOaSupSubComId,
|
||||
Dept::getContractCodePrefix, Dept::getContractInitialSerial,
|
||||
Dept::getIsPlatformCompany, Dept::getIsBusinessDept));
|
||||
Map<String, Dept> byOaId = new HashMap<>();
|
||||
for (Dept dept : deptList) {
|
||||
if (StringUtils.isNotBlank(dept.getOaId())) {
|
||||
@@ -998,6 +1000,11 @@ public class OASyncServiceImpl implements IOASyncService {
|
||||
} else {
|
||||
dept.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
|
||||
}
|
||||
// OA 不同步合同本地配置:保留已有值,并把 -1 空占位规范为 null,避免 ALWAYS 策略写回 -1/清空
|
||||
preserveLocalContractFields(dept, existing);
|
||||
// OA 不同步「是否平台公司 / 是否业务部门」:更新时强制保留本地已配置值,新增默认否
|
||||
preserveLocalBizFlags(dept, existing);
|
||||
normalizeContractInitialSerial(dept);
|
||||
if (StringUtils.isNotBlank(dept.getDeptCode())) {
|
||||
deptIdByCode.put(dept.getDeptCode(), dept.getId());
|
||||
}
|
||||
@@ -1058,6 +1065,55 @@ public class OASyncServiceImpl implements IOASyncService {
|
||||
return new OrgSyncCount(syncedCount, skippedCount, notHandledList);
|
||||
}
|
||||
|
||||
/**
|
||||
* OA 同步对象不含合同本地配置,更新时保留库中已有合同编码前缀与初始流水号。
|
||||
*/
|
||||
private void preserveLocalContractFields(Dept incoming, Dept existing) {
|
||||
if (incoming == null || existing == null) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isBlank(incoming.getContractCodePrefix())) {
|
||||
incoming.setContractCodePrefix(existing.getContractCodePrefix());
|
||||
}
|
||||
Integer incomingSerial = incoming.getContractInitialSerial();
|
||||
if (incomingSerial == null || incomingSerial == -1) {
|
||||
incoming.setContractInitialSerial(existing.getContractInitialSerial());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OA 同步不携带本地业务标志:更新时保留已配置的「是否平台公司 / 是否业务部门」,新增默认否。
|
||||
*/
|
||||
private void preserveLocalBizFlags(Dept incoming, Dept existing) {
|
||||
if (incoming == null) {
|
||||
return;
|
||||
}
|
||||
if (existing != null) {
|
||||
incoming.setIsPlatformCompany(existing.getIsPlatformCompany() != null ? existing.getIsPlatformCompany() : 0);
|
||||
incoming.setIsBusinessDept(existing.getIsBusinessDept() != null ? existing.getIsBusinessDept() : 0);
|
||||
return;
|
||||
}
|
||||
if (incoming.getIsPlatformCompany() == null) {
|
||||
incoming.setIsPlatformCompany(0);
|
||||
}
|
||||
if (incoming.getIsBusinessDept() == null) {
|
||||
incoming.setIsBusinessDept(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同初始流水号 -1 为空占位,同步落库前规范为 null。
|
||||
*/
|
||||
private void normalizeContractInitialSerial(Dept dept) {
|
||||
if (dept == null) {
|
||||
return;
|
||||
}
|
||||
Integer serial = dept.getContractInitialSerial();
|
||||
if (serial != null && serial == -1) {
|
||||
dept.setContractInitialSerial(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段。
|
||||
* 组织编码已按新规则重算、父子挂载不一致、或名称字段与当前映射不一致时,即使 modified 相同也要更新。
|
||||
|
||||
+4
@@ -26,6 +26,7 @@ import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.DictCache;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.enums.DictEnum;
|
||||
@@ -53,6 +54,9 @@ public class MpUserWrapper extends BaseEntityWrapper<MpUser, MpUserVO> {
|
||||
// 密码哈希不下发前端
|
||||
mpUserVO.setPassword(null);
|
||||
mpUserVO.setIdentitiesName(resolveIdentitiesName(mpUser.getIdentities()));
|
||||
if (Func.isNotBlank(mpUser.getDeptId())) {
|
||||
mpUserVO.setDeptName(Func.join(SysCache.getDeptNames(mpUser.getDeptId())));
|
||||
}
|
||||
if (mpUser.getSex() != null) {
|
||||
mpUserVO.setSexName(DictCache.getValue(DictEnum.SEX, mpUser.getSex()));
|
||||
}
|
||||
|
||||
+2
@@ -99,6 +99,8 @@ public class FormalSettlementController extends BladeController {
|
||||
excel.setContractNo(vo.getContractNo());
|
||||
excel.setContractName(vo.getContractName());
|
||||
excel.setSettlementAmount(formatMoney(vo.getSettlementAmount(), vo.getCurrency()));
|
||||
excel.setBusinessModelName(vo.getBusinessModelName());
|
||||
excel.setTransportTypeName(vo.getTransportTypeName());
|
||||
excel.setLocalSettlementAmount(formatMoney(vo.getLocalSettlementAmount(), vo.getLocalCurrency()));
|
||||
excel.setExchangeRate(vo.getExchangeRate() == null ? "" : vo.getExchangeRate().stripTrailingZeros().toPlainString());
|
||||
excel.setInvoiceStatusName(invoiceStatusName(vo.getInvoiceStatus(), vo.getSettlementType()));
|
||||
|
||||
+2
@@ -250,6 +250,8 @@ public class PreSettlementController extends BladeController {
|
||||
excel.setContractNo(vo.getContractNo());
|
||||
excel.setContractName(vo.getContractName());
|
||||
excel.setSettlementAmount(formatMoney(vo.getSettlementAmount(), vo.getCurrency()));
|
||||
excel.setBusinessModelName(vo.getBusinessModelName());
|
||||
excel.setTransportTypeName(vo.getTransportTypeName());
|
||||
excel.setLocalSettlementAmount(formatMoney(vo.getLocalSettlementAmount(), vo.getLocalCurrency()));
|
||||
excel.setExchangeRate(vo.getExchangeRate() == null ? "" : vo.getExchangeRate().stripTrailingZeros().toPlainString());
|
||||
excel.setAdvanceAppliedAmount(formatMoney(vo.getAdvanceAppliedAmount(), vo.getCurrency()));
|
||||
|
||||
+4
@@ -39,6 +39,10 @@ public class FormalSettlementExcel implements Serializable {
|
||||
private String contractName;
|
||||
@ExcelProperty("原币结算金额")
|
||||
private String settlementAmount;
|
||||
@ExcelProperty("业务模式")
|
||||
private String businessModelName;
|
||||
@ExcelProperty("运输方式")
|
||||
private String transportTypeName;
|
||||
@ExcelProperty("本位币结算金额")
|
||||
private String localSettlementAmount;
|
||||
@ExcelProperty("结算汇率")
|
||||
|
||||
+4
@@ -50,6 +50,10 @@ public class PreSettlementExcel implements Serializable {
|
||||
private String contractName;
|
||||
@ExcelProperty("原币结算金额")
|
||||
private String settlementAmount;
|
||||
@ExcelProperty("业务模式")
|
||||
private String businessModelName;
|
||||
@ExcelProperty("运输方式")
|
||||
private String transportTypeName;
|
||||
@ExcelProperty("本位币结算金额")
|
||||
private String localSettlementAmount;
|
||||
@ExcelProperty("结算汇率")
|
||||
|
||||
+4
-19
@@ -96,8 +96,7 @@ public final class SettlementImportTemplateExporter {
|
||||
private SettlementImportTemplateExporter() {
|
||||
}
|
||||
|
||||
public static void export(HttpServletResponse response, List<String> transportTypeOptions,
|
||||
List<String> feeItemNames) {
|
||||
public static void export(HttpServletResponse response, List<String> transportTypeOptions) {
|
||||
List<String> headers = buildHeaders();
|
||||
// 运输方式由调用方传入业务字典 transport_type 的字典名称,禁止写死
|
||||
List<String> transportOptions = transportTypeOptions == null ? List.of() : transportTypeOptions.stream()
|
||||
@@ -106,7 +105,7 @@ public final class SettlementImportTemplateExporter {
|
||||
.distinct()
|
||||
.toList();
|
||||
try (Workbook workbook = new XSSFWorkbook()) {
|
||||
writeInstructionSheet(workbook, transportOptions, feeItemNames);
|
||||
writeInstructionSheet(workbook);
|
||||
Sheet dataSheet = workbook.createSheet("导入结算单模板");
|
||||
CellStyle headerStyle = createHeaderStyle(workbook);
|
||||
Row headerRow = dataSheet.createRow(0);
|
||||
@@ -137,8 +136,7 @@ public final class SettlementImportTemplateExporter {
|
||||
return headers;
|
||||
}
|
||||
|
||||
private static void writeInstructionSheet(Workbook workbook, List<String> transportOptions,
|
||||
List<String> feeItemNames) {
|
||||
private static void writeInstructionSheet(Workbook workbook) {
|
||||
Sheet sheet = workbook.createSheet("填写说明");
|
||||
CellStyle titleStyle = workbook.createCellStyle();
|
||||
Font titleFont = workbook.createFont();
|
||||
@@ -147,24 +145,11 @@ public final class SettlementImportTemplateExporter {
|
||||
List<String> lines = new ArrayList<>();
|
||||
lines.add("导入结算单填写说明");
|
||||
lines.add("1. 请不要修改「导入结算单模板」工作表的固定表头;带 * 号为必填项。");
|
||||
lines.add("2. 运输方式请从下拉项中选择,须与业务字典 transport_type 的字典名称一致。");
|
||||
lines.add("2. 运输方式请从下拉项中选择。");
|
||||
lines.add("3. 日期请填写 yyyy-MM-dd(如 2026-01-01);Excel 日期单元格也可直接选择。");
|
||||
lines.add("4. 里程(km) 可为空;填写时须为正数。");
|
||||
lines.add("5. 「*运输费」之后、「*实际发货时间」之前可按需自行插入费用项列,列名须与系统已启用费用项名称完全一致;金额不为 0 时若不存在或已停用则无法导入。");
|
||||
lines.add("6. 模板不含结算金额列;导入后系统按「运输费 + 各费用项」自动合计结算金额。");
|
||||
if (transportOptions != null && !transportOptions.isEmpty()) {
|
||||
lines.add("7. 当前可选运输方式(业务字典 transport_type 名称):"
|
||||
+ String.join("、", transportOptions) + "。");
|
||||
} else {
|
||||
lines.add("7. 运输方式请从业务字典 transport_type 维护的名称中选择。");
|
||||
}
|
||||
if (feeItemNames != null && !feeItemNames.isEmpty()) {
|
||||
lines.add("8. 费用项请按实际需要在模板中自行增列,列名须与系统已启用费用项一致;当前系统已启用费用项:"
|
||||
+ String.join("、", feeItemNames.stream().limit(30).toList())
|
||||
+ (feeItemNames.size() > 30 ? " 等" : "") + "。");
|
||||
} else {
|
||||
lines.add("8. 费用项请按实际需要在模板中自行增列,列名须与系统已启用的费用项主数据名称完全一致。");
|
||||
}
|
||||
for (int index = 0; index < lines.size(); index++) {
|
||||
Row row = sheet.createRow(index);
|
||||
Cell cell = row.createCell(0);
|
||||
|
||||
+6
@@ -33,13 +33,16 @@
|
||||
<result column="business_scope" property="businessScope"/>
|
||||
<result column="network_freight_platform" property="networkFreightPlatform"/>
|
||||
<result column="business_term_type" property="businessTermType"/>
|
||||
<result column="business_start_date" property="businessStartDate"/>
|
||||
<result column="business_end_date" property="businessEndDate"/>
|
||||
<result column="registered_capital" property="registeredCapital"/>
|
||||
<result column="paid_in_capital" property="paidInCapital"/>
|
||||
<result column="principal" property="principal"/>
|
||||
<result column="mnemonic_code" property="mnemonicCode"/>
|
||||
<result column="customer_level" property="customerLevel"/>
|
||||
<result column="max_credit_limit" property="maxCreditLimit"/>
|
||||
<result column="apply_credit_limit" property="applyCreditLimit"/>
|
||||
<result column="used_credit_limit" property="usedCreditLimit"/>
|
||||
<result column="remark" property="remark"/>
|
||||
<result column="qualification_attachments" property="qualificationAttachments"/>
|
||||
<result column="access_type" property="accessType"/>
|
||||
@@ -85,13 +88,16 @@
|
||||
business_scope,
|
||||
network_freight_platform,
|
||||
business_term_type,
|
||||
business_start_date,
|
||||
business_end_date,
|
||||
registered_capital,
|
||||
paid_in_capital,
|
||||
principal,
|
||||
mnemonic_code,
|
||||
customer_level,
|
||||
max_credit_limit,
|
||||
apply_credit_limit,
|
||||
used_credit_limit,
|
||||
remark,
|
||||
qualification_attachments,
|
||||
access_type,
|
||||
|
||||
+1
-32
@@ -107,7 +107,7 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
|
||||
private List<BillLedgerVO> availableList(String keyword, Long deptId, Long selectedId, String payerName,
|
||||
String payeeName) {
|
||||
LocalDate today = LocalDate.now();
|
||||
// 未到期且有余额;收票且部门可用,或出票/收票单位与结算收付款方一致(开票、收票都可)
|
||||
// 可选汇票:未到期且可用余额 > 0(已选中的可回显);不再按部门、开票/收票类型、结算收付款方过滤
|
||||
return list(Wrappers.<BillLedger>lambdaQuery()
|
||||
.and(Func.isNotEmpty(keyword), wrapper -> wrapper.like(BillLedger::getBillNo, keyword)
|
||||
.or().like(BillLedger::getIssuerName, keyword)
|
||||
@@ -119,35 +119,10 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
|
||||
.or(selectedId != null, child -> child.eq(BillLedger::getId, selectedId)))
|
||||
.orderByAsc(BillLedger::getMaturityDate)
|
||||
.orderByDesc(BillLedger::getCreateTime)).stream()
|
||||
.filter(item -> selectedId != null && Objects.equals(item.getId(), selectedId)
|
||||
|| selectableBill(item, deptId, payerName, payeeName))
|
||||
.map(item -> BillLedgerWrapper.build().entityVO(item))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收票且当前部门在可用范围内,或出票单位、收票单位与结算付款方、收款方一致(顺序不限)。
|
||||
*/
|
||||
private boolean selectableBill(BillLedger ledger, Long deptId, String payerName, String payeeName) {
|
||||
boolean receivedAndDept = "received".equals(ledger.getBillType()) && departmentAvailable(ledger, deptId);
|
||||
return receivedAndDept || partiesMatch(ledger.getIssuerName(), ledger.getReceiverName(), payerName, payeeName);
|
||||
}
|
||||
|
||||
private boolean partiesMatch(String issuerName, String receiverName, String payerName, String payeeName) {
|
||||
String issuer = trimName(issuerName);
|
||||
String receiver = trimName(receiverName);
|
||||
String payer = trimName(payerName);
|
||||
String payee = trimName(payeeName);
|
||||
if (issuer.isEmpty() || receiver.isEmpty() || payer.isEmpty() || payee.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return (issuer.equals(payer) && receiver.equals(payee)) || (issuer.equals(payee) && receiver.equals(payer));
|
||||
}
|
||||
|
||||
private String trimName(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long submit(BillLedgerSaveRequest request) {
|
||||
@@ -292,12 +267,6 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
private boolean departmentAvailable(BillLedger ledger, Long deptId) {
|
||||
List<?> values = parseArray(ledger.getAvailableDeptIdsJson(), "可用部门");
|
||||
if (values.stream().anyMatch(value -> "all".equals(String.valueOf(value)))) return true;
|
||||
return deptId == null || values.stream().anyMatch(value -> String.valueOf(deptId).equals(String.valueOf(value)));
|
||||
}
|
||||
|
||||
private List<?> parseArray(String value, String name) {
|
||||
if (Func.isEmpty(value)) return List.of();
|
||||
try {
|
||||
|
||||
+4
-20
@@ -32,7 +32,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
@@ -112,7 +111,7 @@ public class BillPaymentServiceImpl extends BaseServiceImpl<BillPaymentMapper, B
|
||||
String deptName = required(SysCache.getDeptName(deptId), "使用部门", 100);
|
||||
BillLedger ledger = lockedBill(request.getBillLedgerId());
|
||||
BigDecimal usedAmount = positive(request.getUsedAmount(), "本次使用");
|
||||
validateLedgerAmount(ledger, usedAmount, deptId);
|
||||
validateLedgerAmount(ledger, usedAmount);
|
||||
if (entity.getId() == null) {
|
||||
entity.setPaymentNo(nextNo());
|
||||
entity.setApprovalStatus(TransportBusiConstant.DRAFT);
|
||||
@@ -165,7 +164,7 @@ public class BillPaymentServiceImpl extends BaseServiceImpl<BillPaymentMapper, B
|
||||
throw new ServiceException("仅审批中的汇票付款允许审核");
|
||||
}
|
||||
BillLedger ledger = lockedBill(entity.getBillLedgerId());
|
||||
validateLedgerAmount(ledger, entity.getUsedAmount(), entity.getDeptId());
|
||||
validateLedgerAmount(ledger, entity.getUsedAmount());
|
||||
BillLedgerUsage exists = usageMapper.selectOne(Wrappers.<BillLedgerUsage>lambdaQuery()
|
||||
.eq(BillLedgerUsage::getBillPaymentId, entity.getId())
|
||||
.eq(BillLedgerUsage::getUsageStatus, TransportBusiConstant.APPROVED)
|
||||
@@ -245,13 +244,13 @@ public class BillPaymentServiceImpl extends BaseServiceImpl<BillPaymentMapper, B
|
||||
|
||||
private void validateStoredAmount(BillPayment entity) {
|
||||
BillLedger ledger = lockedBill(entity.getBillLedgerId());
|
||||
validateLedgerAmount(ledger, entity.getUsedAmount(), entity.getDeptId());
|
||||
validateLedgerAmount(ledger, entity.getUsedAmount());
|
||||
entity.setBillNo(ledger.getBillNo());
|
||||
entity.setFaceAmount(ledger.getFaceAmount());
|
||||
entity.setAvailableBalance(ledger.getAvailableBalance());
|
||||
}
|
||||
|
||||
private void validateLedgerAmount(BillLedger ledger, BigDecimal usedAmount, Long deptId) {
|
||||
private void validateLedgerAmount(BillLedger ledger, BigDecimal usedAmount) {
|
||||
if (usedAmount == null || usedAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new ServiceException("本次使用必须大于0");
|
||||
}
|
||||
@@ -261,21 +260,6 @@ public class BillPaymentServiceImpl extends BaseServiceImpl<BillPaymentMapper, B
|
||||
if (usedAmount.compareTo(money(ledger.getAvailableBalance())) > 0) {
|
||||
throw new ServiceException("本次使用不能超过汇票可用余额");
|
||||
}
|
||||
if (!departmentAvailable(ledger, deptId)) {
|
||||
throw new ServiceException("当前使用部门不在汇票可用部门范围内");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean departmentAvailable(BillLedger ledger, Long deptId) {
|
||||
if (Func.isEmpty(ledger.getAvailableDeptIdsJson())) return false;
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(ledger.getAvailableDeptIdsJson(), List.class);
|
||||
if (!(parsed instanceof List<?> values)) return false;
|
||||
if (values.stream().anyMatch(item -> "all".equals(String.valueOf(item)))) return true;
|
||||
return deptId != null && values.stream().anyMatch(item -> String.valueOf(deptId).equals(String.valueOf(item)));
|
||||
} catch (Exception exception) {
|
||||
throw new ServiceException("汇票可用部门配置不正确");
|
||||
}
|
||||
}
|
||||
|
||||
private BillLedger lockedBill(Long id) {
|
||||
|
||||
+78
-22
@@ -83,6 +83,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
private static final String ARCHIVE_STATUS_PENDING = "未归档";
|
||||
private static final String ARCHIVE_STATUS_ARCHIVED = "已归档";
|
||||
private static final String FILE_TYPE_SEAL_ARCHIVE = "双章归档文件";
|
||||
private static final String CHANGE_TYPE_INFO = "合同信息变更";
|
||||
private static final String CHANGE_TYPE_SETTLEMENT = "结算规则变更";
|
||||
private static final long TEMPORARY_VALIDITY_DAYS = 30L;
|
||||
private static final BigDecimal TAX_RATE_MIN = BigDecimal.ZERO;
|
||||
private static final BigDecimal TAX_RATE_MAX = BigDecimal.valueOf(100);
|
||||
@@ -285,45 +287,99 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
|| !List.of(STATUS_APPROVED, STATUS_CHANGE_APPROVED, STATUS_CHANGE_REJECTED).contains(source.getApprovalStatus())) {
|
||||
throw new ServiceException("当前合同状态不允许发起变更");
|
||||
}
|
||||
TransportBusinessSupport.validateRequired(request.getSettlementCurrency(), "请选择结算币种");
|
||||
String changeType = resolveChangeType(request.getChangeType());
|
||||
boolean settlementRuleOnly = Objects.equals(changeType, CHANGE_TYPE_SETTLEMENT);
|
||||
String changeReason = TransportBusinessSupport.trimToNull(request.getChangeReason());
|
||||
if (Func.isEmpty(changeReason) && Objects.equals(source.getApprovalStatus(), STATUS_CHANGE_REJECTED)) {
|
||||
changeReason = resolveResubmitChangeReason(source);
|
||||
}
|
||||
TransportBusinessSupport.validateLength(changeReason, 500, "变更原因不能超过500字");
|
||||
ContractManage candidate = new ContractManage();
|
||||
BeanUtil.copyProperties(source, candidate);
|
||||
candidate.setPartyB(TransportBusinessSupport.trimToNull(request.getPartyB()));
|
||||
validateContractPartyCombinationUnique(candidate);
|
||||
validateBillingPlans(request.getBillingPlanJson());
|
||||
validatePaymentRatios(request.getPaymentRatioJson());
|
||||
Map<String, Object> beforeData = buildContractChangeSnapshot(source);
|
||||
applyContractNo(source, request.getContractNo(), false);
|
||||
source.setContractName(request.getContractName()); source.setPartyB(request.getPartyB());
|
||||
source.setStartDate(request.getStartDate()); source.setEndDate(request.getEndDate()); source.setContractFormat(request.getContractFormat());
|
||||
source.setSettlementMode(request.getSettlementMode()); source.setLegalSealFlag(request.getLegalSealFlag()); source.setCopyCount(request.getCopyCount());
|
||||
source.setSettlementCurrency(TransportBusinessSupport.trimToNull(request.getSettlementCurrency())); source.setInvoiceCycle(request.getInvoiceCycle());
|
||||
source.setPaymentDays(request.getPaymentDays()); source.setRemark(request.getRemark()); source.setBillingEnabled(request.getBillingEnabled()); source.setFeeGenerationMode(request.getFeeGenerationMode());
|
||||
source.setContractAmount(request.getContractAmount()); source.setTemplateFlag(request.getTemplateFlag());
|
||||
source.setOriginalContractNo(TransportBusinessSupport.trimToNull(request.getOriginalContractNo()));
|
||||
source.setElectronicSealFlag(request.getElectronicSealFlag());
|
||||
normalizeOptionalIntegerFields(source);
|
||||
validateContractAmountRequired(source);
|
||||
validateAdditionalFields(source);
|
||||
source.setBillingPlanJson(request.getBillingPlanJson()); source.setSettlementRuleJson(request.getSettlementRuleJson()); source.setPreSettlementConfigJson(request.getPreSettlementConfigJson()); source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson()); source.setPaymentRatioJson(request.getPaymentRatioJson());
|
||||
source.setContractFileJson(request.getContractFileJson()); source.setAttachmentsJson(request.getAttachmentsJson());
|
||||
source.setChangeAttachmentsJson(TransportBusinessSupport.trimToNull(request.getChangeAttachmentsJson()));
|
||||
if (settlementRuleOnly) {
|
||||
applySettlementRuleChange(source, request);
|
||||
} else {
|
||||
applyContractInfoChange(source, request);
|
||||
}
|
||||
Map<String, Object> afterData = buildContractChangeSnapshot(source);
|
||||
retainChangedSnapshotFields(beforeData, afterData);
|
||||
source.setApprovalStatus(STATUS_CHANGE_REVIEWING);
|
||||
source.setChangeContent(TransportBusinessSupport.trimToNull(request.getChangeContent()));
|
||||
source.setChangeReason(changeReason);
|
||||
source.setChangeAttachmentsJson(TransportBusinessSupport.trimToNull(request.getChangeAttachmentsJson()));
|
||||
source.setCurrentNode("合同变更审批");
|
||||
source.setCurrentProcessor("待处理");
|
||||
appendChangeRecord(source, "合同信息变更", changeReason, STATUS_CHANGE_REVIEWING, "审核中", beforeData, afterData);
|
||||
appendChangeRecord(source, changeType, changeReason, STATUS_CHANGE_REVIEWING, "审核中", beforeData, afterData);
|
||||
return updateById(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并校验合同变更类型,兼容历史未传类型的请求。
|
||||
*/
|
||||
private String resolveChangeType(String changeType) {
|
||||
String normalized = TransportBusinessSupport.trimToNull(changeType);
|
||||
if (Func.isEmpty(normalized)) {
|
||||
return CHANGE_TYPE_INFO;
|
||||
}
|
||||
if (!List.of(CHANGE_TYPE_INFO, CHANGE_TYPE_SETTLEMENT).contains(normalized)) {
|
||||
throw new ServiceException("请选择变更类型");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结算规则变更:仅允许更新计费信息与结算单规则。
|
||||
*/
|
||||
private void applySettlementRuleChange(ContractManage source, ContractManage request) {
|
||||
source.setBillingEnabled(request.getBillingEnabled());
|
||||
source.setFeeGenerationMode(request.getFeeGenerationMode());
|
||||
source.setBillingPlanJson(request.getBillingPlanJson());
|
||||
source.setSettlementRuleJson(request.getSettlementRuleJson());
|
||||
source.setPreSettlementConfigJson(request.getPreSettlementConfigJson());
|
||||
source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson());
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同信息变更:更新合同基本信息、计费/结算规则、付款比例及附件。
|
||||
*/
|
||||
private void applyContractInfoChange(ContractManage source, ContractManage request) {
|
||||
TransportBusinessSupport.validateRequired(request.getSettlementCurrency(), "请选择结算币种");
|
||||
ContractManage candidate = new ContractManage();
|
||||
BeanUtil.copyProperties(source, candidate);
|
||||
candidate.setPartyB(TransportBusinessSupport.trimToNull(request.getPartyB()));
|
||||
validateContractPartyCombinationUnique(candidate);
|
||||
validatePaymentRatios(request.getPaymentRatioJson());
|
||||
applyContractNo(source, request.getContractNo(), false);
|
||||
source.setContractName(request.getContractName());
|
||||
source.setPartyB(request.getPartyB());
|
||||
source.setStartDate(request.getStartDate());
|
||||
source.setEndDate(request.getEndDate());
|
||||
source.setContractFormat(request.getContractFormat());
|
||||
source.setSettlementMode(request.getSettlementMode());
|
||||
source.setLegalSealFlag(request.getLegalSealFlag());
|
||||
source.setCopyCount(request.getCopyCount());
|
||||
source.setSettlementCurrency(TransportBusinessSupport.trimToNull(request.getSettlementCurrency()));
|
||||
source.setInvoiceCycle(request.getInvoiceCycle());
|
||||
source.setPaymentDays(request.getPaymentDays());
|
||||
source.setRemark(request.getRemark());
|
||||
source.setBillingEnabled(request.getBillingEnabled());
|
||||
source.setFeeGenerationMode(request.getFeeGenerationMode());
|
||||
source.setContractAmount(request.getContractAmount());
|
||||
source.setTemplateFlag(request.getTemplateFlag());
|
||||
source.setOriginalContractNo(TransportBusinessSupport.trimToNull(request.getOriginalContractNo()));
|
||||
source.setElectronicSealFlag(request.getElectronicSealFlag());
|
||||
normalizeOptionalIntegerFields(source);
|
||||
validateContractAmountRequired(source);
|
||||
validateAdditionalFields(source);
|
||||
source.setBillingPlanJson(request.getBillingPlanJson());
|
||||
source.setSettlementRuleJson(request.getSettlementRuleJson());
|
||||
source.setPreSettlementConfigJson(request.getPreSettlementConfigJson());
|
||||
source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson());
|
||||
source.setPaymentRatioJson(request.getPaymentRatioJson());
|
||||
source.setContractFileJson(request.getContractFileJson());
|
||||
source.setAttachmentsJson(request.getAttachmentsJson());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateAttachments(ContractManage contractManage) {
|
||||
|
||||
+8
@@ -458,6 +458,10 @@ public class CreditScoreQuantificationServiceImpl extends BaseServiceImpl<Credit
|
||||
quantification.setName(trimToEmpty(quantification.getName()));
|
||||
quantification.setRemark(trimToNull(quantification.getRemark()));
|
||||
quantification.setStandardDescription(trimToNull(quantification.getStandardDescription()));
|
||||
if (quantification.getMaxMinusScore() != null
|
||||
&& quantification.getMaxMinusScore().compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new ServiceException("减分项目最高扣分限制不能小于0");
|
||||
}
|
||||
if (Func.isEmpty(quantification.getStatus())) {
|
||||
quantification.setStatus(STATUS_DRAFT);
|
||||
}
|
||||
@@ -472,6 +476,10 @@ public class CreditScoreQuantificationServiceImpl extends BaseServiceImpl<Credit
|
||||
validateLength(quantification.getName(), NAME_MAX_LENGTH, "评定表名称最多30字符");
|
||||
validateLength(quantification.getRemark(), REMARK_MAX_LENGTH, "备注最多500字符");
|
||||
validateLength(quantification.getStandardDescription(), REMARK_MAX_LENGTH, "标准说明最多500字符");
|
||||
if (quantification.getMaxMinusScore() != null
|
||||
&& quantification.getMaxMinusScore().compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new ServiceException("减分项目最高扣分限制不能小于0");
|
||||
}
|
||||
LambdaQueryWrapper<CreditScoreQuantification> queryWrapper = Wrappers.<CreditScoreQuantification>lambdaQuery()
|
||||
.eq(CreditScoreQuantification::getName, quantification.getName())
|
||||
.eq(CreditScoreQuantification::getIsDeleted, 0);
|
||||
|
||||
+90
-7
@@ -280,6 +280,8 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
customer.setCustomerCode(nextCustomerCode());
|
||||
}
|
||||
CustomerArchive entity = Objects.requireNonNull(BeanUtil.copyProperties(customer, CustomerArchive.class));
|
||||
// 曾审核通过后的评分替换:保存/再提交时仍沿用原生效等级与额度,待再次审核通过后再切换
|
||||
this.keepEffectiveCreditUntilReapproved(before, entity);
|
||||
boolean result = saveOrUpdate(entity);
|
||||
customer.setId(entity.getId());
|
||||
replaceDetail(entity.getId(), customer);
|
||||
@@ -420,6 +422,8 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
after.setCurrentNode("审核通过");
|
||||
after.setCurrentProcessor(Func.isEmpty(processorName) ? "系统" : processorName.trim());
|
||||
after.setApprovedTime(LocalDateTime.now());
|
||||
// 审核通过后按当前未删除评分刷新生效等级/额度(替换旧评分场景在此切换生效)
|
||||
this.applyCreditFromActiveScore(after);
|
||||
addChangeRecord(id, "客商准入审核通过", before, after);
|
||||
boolean updated = updateById(after);
|
||||
log.info("流程结束审核通过 id={} processor={} result={}", id, after.getCurrentProcessor(), updated);
|
||||
@@ -484,6 +488,8 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
after.setCurrentNode("审核通过");
|
||||
after.setCurrentProcessor(AuthUtil.getUserName());
|
||||
after.setApprovedTime(LocalDateTime.now());
|
||||
// 审核通过后按当前未删除评分刷新生效等级/额度(替换旧评分场景在此切换生效)
|
||||
this.applyCreditFromActiveScore(after);
|
||||
addChangeRecord(id, "客商准入审核通过", before, after);
|
||||
return updateById(after);
|
||||
}
|
||||
@@ -943,6 +949,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
score.setScoreDate(LocalDate.now());
|
||||
score.setSelfStatus("未完成");
|
||||
score.setReviewStatus("未完成");
|
||||
score.setMaxMinusScore(quantification.getMaxMinusScore());
|
||||
score.setDetails(loadScoreTemplateDetails(quantification.getId()));
|
||||
return score;
|
||||
}
|
||||
@@ -963,13 +970,16 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
customer.setBusinessScope(trimToNull(customer.getBusinessScope()));
|
||||
customer.setInvoiceTaxRate(normalizeSentinelAmount(customer.getInvoiceTaxRate()));
|
||||
customer.setRegisteredCapital(normalizeSentinelAmount(customer.getRegisteredCapital()));
|
||||
customer.setPaidInCapital(normalizeSentinelAmount(customer.getPaidInCapital()));
|
||||
customer.setMaxCreditLimit(normalizeSentinelAmount(customer.getMaxCreditLimit()));
|
||||
customer.setApplyCreditLimit(normalizeSentinelAmount(customer.getApplyCreditLimit()));
|
||||
customer.setUsedCreditLimit(normalizeSentinelAmount(customer.getUsedCreditLimit()));
|
||||
customer.setRegisteredRegionName(trimToNull(customer.getRegisteredRegionName()));
|
||||
customer.setRegisteredDetailAddress(trimToNull(customer.getRegisteredDetailAddress()));
|
||||
customer.setRegisteredAddress(buildRegisteredAddress(customer));
|
||||
customer.setLegalPerson(trimToEmpty(customer.getLegalPerson()));
|
||||
customer.setContactPhone(trimToEmpty(customer.getContactPhone()));
|
||||
customer.setContacts(customer.getContacts() == null ? new ArrayList<>() : customer.getContacts());
|
||||
syncContactPhoneFromContacts(customer);
|
||||
applyFixedOwnerDept(customer);
|
||||
customer.setAccessType(Func.isEmpty(customer.getAccessType()) ? ACCESS_TEMPORARY : customer.getAccessType());
|
||||
customer.setApprovalStatus(Func.isEmpty(customer.getApprovalStatus()) ? APPROVAL_DRAFT : customer.getApprovalStatus());
|
||||
@@ -977,12 +987,33 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
if (Func.isEmpty(customer.getStatus())) {
|
||||
customer.setStatus(STATUS_ENABLED);
|
||||
}
|
||||
customer.setContacts(customer.getContacts() == null ? new ArrayList<>() : customer.getContacts());
|
||||
customer.setReceiptAccounts(customer.getReceiptAccounts() == null ? new ArrayList<>() : customer.getReceiptAccounts());
|
||||
customer.setInvoices(customer.getInvoices() == null ? new ArrayList<>() : customer.getInvoices());
|
||||
customer.setScores(customer.getScores() == null ? new ArrayList<>() : customer.getScores());
|
||||
}
|
||||
|
||||
/**
|
||||
* 主表联系电话改由联系人维护:取第一条有效联系人手机号回填,兼容历史 NOT NULL 及金蝶同步字段。
|
||||
*/
|
||||
private void syncContactPhoneFromContacts(CustomerArchiveVO customer) {
|
||||
if (Func.isNotEmpty(customer.getContactPhone())) {
|
||||
customer.setContactPhone(trimToEmpty(customer.getContactPhone()));
|
||||
return;
|
||||
}
|
||||
List<CustomerContactVO> contacts = customer.getContacts();
|
||||
if (Func.isEmpty(contacts)) {
|
||||
customer.setContactPhone(null);
|
||||
return;
|
||||
}
|
||||
String phone = contacts.stream()
|
||||
.map(CustomerContactVO::getContactPhone)
|
||||
.filter(Func::isNotEmpty)
|
||||
.map(String::trim)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
customer.setContactPhone(phone);
|
||||
}
|
||||
|
||||
private void validateBase(CustomerArchiveVO customer) {
|
||||
if (Func.isEmpty(customer.getFullName())) {
|
||||
throw new ServiceException("客商全称不能为空");
|
||||
@@ -1009,10 +1040,8 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
if (Func.isEmpty(customer.getLegalPerson())) {
|
||||
throw new ServiceException("法人/负责人不能为空");
|
||||
}
|
||||
if (Func.isEmpty(customer.getContactPhone())) {
|
||||
throw new ServiceException("联系电话不能为空");
|
||||
}
|
||||
if (!customer.getContactPhone().matches("^1\\d{10}$")) {
|
||||
validateContactsRequired(customer);
|
||||
if (Func.isNotEmpty(customer.getContactPhone()) && !customer.getContactPhone().matches("^1\\d{10}$")) {
|
||||
throw new ServiceException("联系电话必须为11位手机号");
|
||||
}
|
||||
if (Func.isEmpty(customer.getDeptName())) {
|
||||
@@ -1028,16 +1057,30 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
validateLength(customer.getRegisteredRegionName(), 100, "省市区最多100个字符");
|
||||
validateLength(customer.getRegisteredDetailAddress(), 255, "详细地址最多255个字符");
|
||||
validateLength(customer.getRegisteredAddress(), 400, "地址最多400个字符");
|
||||
validateLength(customer.getBusinessScope(), 500, "经营范围最多500个字符");
|
||||
validateLength(customer.getBusinessScope(), 2000, "经营范围最多2000个字符");
|
||||
validateLength(customer.getRemark(), 500, "备注最多500个字符");
|
||||
validateNonNegative(customer.getInvoiceTaxRate(), "开票税点不能小于0");
|
||||
validateMax(customer.getInvoiceTaxRate(), new BigDecimal("13"), "开票税点必须在0到13之间");
|
||||
validateNonNegative(customer.getRegisteredCapital(), "注册资金不能小于0");
|
||||
validateNonNegative(customer.getPaidInCapital(), "实缴资本不能小于0");
|
||||
validateNonNegative(customer.getMaxCreditLimit(), "最大资金使用额度不能小于0");
|
||||
validateNonNegative(customer.getApplyCreditLimit(), "申请总资金使用额度不能小于0");
|
||||
validateNonNegative(customer.getUsedCreditLimit(), "已使用额度不能小于0");
|
||||
validateUnique(customer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交客商档案时必须至少维护一条联系人。
|
||||
*/
|
||||
private void validateContactsRequired(CustomerArchiveVO customer) {
|
||||
List<CustomerContactVO> contacts = customer.getContacts() == null ? List.of() : customer.getContacts();
|
||||
boolean hasContact = contacts.stream()
|
||||
.anyMatch(item -> Func.isNotEmpty(item.getContactName()) || Func.isNotEmpty(item.getContactPhone()));
|
||||
if (!hasContact) {
|
||||
throw new ServiceException("请至少维护一条联系人");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateCustomerType(String customerType) {
|
||||
List<String> typeList = Func.toStrList(customerType.replace(",", ","))
|
||||
.stream()
|
||||
@@ -1364,6 +1407,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
}
|
||||
|
||||
private void deleteScores(List<Long> customerIds) {
|
||||
// 逻辑删除保留历史评分明细;曾审核通过的旧评分在再次审核通过前仍通过主表等级/额度暂时生效
|
||||
List<CustomerCreditScore> scores = creditScoreMapper.selectList(Wrappers.<CustomerCreditScore>lambdaQuery()
|
||||
.in(CustomerCreditScore::getCustomerId, customerIds)
|
||||
.and(wrapper -> wrapper.ne(CustomerCreditScore::getIsDeleted, 1).or().isNull(CustomerCreditScore::getIsDeleted)));
|
||||
@@ -1374,6 +1418,42 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 客商曾审核通过后,保存或再次提审时不覆盖主表已生效的等级/额度。
|
||||
*/
|
||||
private void keepEffectiveCreditUntilReapproved(CustomerArchive before, CustomerArchive entity) {
|
||||
if (before == null || before.getApprovedTime() == null || entity == null) {
|
||||
return;
|
||||
}
|
||||
entity.setCustomerLevel(before.getCustomerLevel());
|
||||
entity.setMaxCreditLimit(before.getMaxCreditLimit());
|
||||
entity.setApplyCreditLimit(before.getApplyCreditLimit());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按当前未删除评分刷新客商生效等级与额度;优先取复评已完成的评分。
|
||||
*/
|
||||
private void applyCreditFromActiveScore(CustomerArchive customer) {
|
||||
if (customer == null || customer.getId() == null) {
|
||||
return;
|
||||
}
|
||||
List<CustomerCreditScoreVO> scores = loadScores(customer.getId());
|
||||
if (Func.isEmpty(scores)) {
|
||||
return;
|
||||
}
|
||||
CustomerCreditScoreVO activeScore = scores.stream()
|
||||
.filter(score -> "已完成".equals(score.getReviewStatus()))
|
||||
.findFirst()
|
||||
.orElse(scores.get(0));
|
||||
if (Func.isNotEmpty(activeScore.getCreditLevel())) {
|
||||
customer.setCustomerLevel(activeScore.getCreditLevel());
|
||||
}
|
||||
if (activeScore.getMaxCreditLimit() != null) {
|
||||
customer.setMaxCreditLimit(activeScore.getMaxCreditLimit());
|
||||
customer.setApplyCreditLimit(activeScore.getMaxCreditLimit());
|
||||
}
|
||||
}
|
||||
|
||||
private List<CustomerContactVO> loadContacts(Long customerId) {
|
||||
return contactMapper.selectList(Wrappers.<CustomerContact>lambdaQuery()
|
||||
.eq(CustomerContact::getCustomerId, customerId)
|
||||
@@ -2040,13 +2120,16 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
snapshot.put("开票税点", customer.getInvoiceTaxRate());
|
||||
snapshot.put("经营范围", customer.getBusinessScope());
|
||||
snapshot.put("营业期限类型", customer.getBusinessTermType());
|
||||
snapshot.put("营业期限开始日", customer.getBusinessStartDate());
|
||||
snapshot.put("营业期限截止日", customer.getBusinessEndDate());
|
||||
snapshot.put("注册资金(万元)", customer.getRegisteredCapital());
|
||||
snapshot.put("实缴资本(万元)", customer.getPaidInCapital());
|
||||
snapshot.put("客商负责人", customer.getPrincipal());
|
||||
snapshot.put("助记码", customer.getMnemonicCode());
|
||||
snapshot.put("客户等级", customer.getCustomerLevel());
|
||||
snapshot.put("最大资金使用额度(万元)", customer.getMaxCreditLimit());
|
||||
snapshot.put("申请总资金使用额度(万元)", customer.getApplyCreditLimit());
|
||||
snapshot.put("已使用额度(万元)", customer.getUsedCreditLimit());
|
||||
snapshot.put("备注", customer.getRemark());
|
||||
snapshot.put("客商材料", customer.getQualificationAttachments());
|
||||
snapshot.put("准入类型", customer.getAccessType());
|
||||
|
||||
+72
-20
@@ -40,11 +40,14 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerSaveDTO;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.constant.CustomerBfiConstants;
|
||||
import org.springblade.transport.pojo.vo.CustomerArchiveVO;
|
||||
import org.springblade.transport.pojo.vo.CustomerContactVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -86,12 +89,13 @@ public class CustomerBfiConverter {
|
||||
param.setType(this.resolvePartnerType(customer.getCustomerNature()));
|
||||
// 详细地址:注册地址(省市区 + 详细地址拼接文本)
|
||||
param.setBizpartnerAddress(customer.getRegisteredAddress());
|
||||
// 经营范围:多选编码 → 字典文本「、」拼接
|
||||
// 经营范围:长文本直传;兼容历史多选编码→字典文本
|
||||
param.setBusinessscope(this.buildBusinessScopeText(customer.getBusinessScope()));
|
||||
// 营业期限:长期传「长期」,固定期限传「至 yyyy-MM-dd」(TMS 未采集起始日)
|
||||
param.setBusinessterm(this.buildBusinessTerm(customer.getBusinessTermType(), customer.getBusinessEndDate()));
|
||||
// 联系电话:客商联系电话
|
||||
param.setBizpartnerPhone(customer.getContactPhone());
|
||||
// 营业期限:长期传「长期」,固定期限传「yyyy-MM-dd 至 yyyy-MM-dd」
|
||||
param.setBusinessterm(this.buildBusinessTerm(
|
||||
customer.getBusinessTermType(), customer.getBusinessStartDate(), customer.getBusinessEndDate()));
|
||||
// 联系电话:优先主表,空时回退首条联系人
|
||||
param.setBizpartnerPhone(this.resolveContactPhone(customer));
|
||||
// 备注:客商备注,空时不传
|
||||
param.setFcusbeizhu(customer.getRemark());
|
||||
// 业务职能:固定全选(1销售,2结算,3付款,4收货)
|
||||
@@ -107,7 +111,7 @@ public class CustomerBfiConverter {
|
||||
param.setEntryGroupstandard(List.of(groupEntry));
|
||||
// 银行信息分录:收款账户明细(账户名称传开户行,币种默认 RMB)
|
||||
param.setEntryBank(this.buildBankEntries(customer));
|
||||
// 联系人信息分录:页面填写的客商负责人与联系电话
|
||||
// 联系人信息分录:客商联系人明细(兼容历史负责人字段)
|
||||
param.setEntryLinkman(this.buildLinkmanEntries(customer));
|
||||
return param;
|
||||
}
|
||||
@@ -175,25 +179,58 @@ public class CustomerBfiConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 A902-2 联系人信息分录:页面填写的客商负责人与联系电话,未填负责人时返回空分录
|
||||
* 组装 A902-2 联系人信息分录:优先推送客商联系人明细;无明细时兼容历史「负责人+联系电话」。
|
||||
*
|
||||
* @param customer 客商档案
|
||||
* @return 联系人信息分录
|
||||
*/
|
||||
private List<BfiCustomerLinkmanEntryDTO> buildLinkmanEntries(CustomerArchiveVO customer) {
|
||||
if (Func.isNotEmpty(customer.getContacts())) {
|
||||
List<BfiCustomerLinkmanEntryDTO> entries = new ArrayList<>();
|
||||
boolean defaultAssigned = false;
|
||||
for (CustomerContactVO contact : customer.getContacts()) {
|
||||
if (Func.isBlank(contact.getContactName()) && Func.isBlank(contact.getContactPhone())) {
|
||||
continue;
|
||||
}
|
||||
BfiCustomerLinkmanEntryDTO entry = new BfiCustomerLinkmanEntryDTO();
|
||||
entry.setContactperson(contact.getContactName());
|
||||
entry.setPhone(contact.getContactPhone());
|
||||
boolean isDefault = !defaultAssigned && (Objects.equals(contact.getIsDefault(), 1)
|
||||
|| Func.isNotBlank(contact.getContactName()));
|
||||
entry.setIsdefaultLinkman(isDefault);
|
||||
if (isDefault) {
|
||||
defaultAssigned = true;
|
||||
}
|
||||
entries.add(entry);
|
||||
}
|
||||
if (!entries.isEmpty()) {
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
if (Func.isBlank(customer.getPrincipal())) {
|
||||
return List.of();
|
||||
}
|
||||
BfiCustomerLinkmanEntryDTO entry = new BfiCustomerLinkmanEntryDTO();
|
||||
// 联系人:客商负责人
|
||||
entry.setContactperson(customer.getPrincipal());
|
||||
// 电话:联系电话
|
||||
entry.setPhone(customer.getContactPhone());
|
||||
// 默认联系人
|
||||
entry.setPhone(this.resolveContactPhone(customer));
|
||||
entry.setIsdefaultLinkman(Boolean.TRUE);
|
||||
return List.of(entry);
|
||||
}
|
||||
|
||||
private String resolveContactPhone(CustomerArchiveVO customer) {
|
||||
if (Func.isNotBlank(customer.getContactPhone())) {
|
||||
return customer.getContactPhone();
|
||||
}
|
||||
if (Func.isEmpty(customer.getContacts())) {
|
||||
return null;
|
||||
}
|
||||
return customer.getContacts().stream()
|
||||
.map(CustomerContactVO::getContactPhone)
|
||||
.filter(Func::isNotBlank)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶伙伴类型编码:客商性质(nature_of_client 字典)——
|
||||
* 值为 dictKey(即伙伴类型编码,1~17)时直接使用;
|
||||
@@ -248,35 +285,50 @@ public class CustomerBfiConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 经营范围多选编码 → 字典文本「、」拼接,字典缺失时回退原编码
|
||||
* 经营范围:长文本直传;兼容历史多选编码 → 字典文本「、」拼接。
|
||||
*
|
||||
* @param businessScope 逗号拼接的经营范围编码
|
||||
* @param businessScope 经营范围长文本或历史逗号拼接编码
|
||||
* @return 金蝶经营范围文本
|
||||
*/
|
||||
public String buildBusinessScopeText(String businessScope) {
|
||||
if (Func.isEmpty(businessScope)) {
|
||||
return "";
|
||||
}
|
||||
String text = businessScope.trim();
|
||||
// 长文本(含中文描述)直接推送,不再按字典拆分
|
||||
if (text.length() > 40 || text.contains(";") || text.contains("。") || text.contains("\n")) {
|
||||
return text;
|
||||
}
|
||||
List<DictBiz> dictList = DictBizCache.getList("scope_of_business");
|
||||
Map<String, String> scopeNames = dictList == null ? Map.of() : dictList.stream()
|
||||
.collect(Collectors.toMap(DictBiz::getDictKey, DictBiz::getDictValue, (first, second) -> first));
|
||||
return Func.toStrList(businessScope.replace(",", ",")).stream()
|
||||
return Func.toStrList(text.replace(",", ",")).stream()
|
||||
.map(code -> scopeNames.getOrDefault(code, code))
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 营业期限:长期传「长期」;固定期限传「至 yyyy-MM-dd」(TMS 未采集起始日)
|
||||
* 营业期限:长期传「长期」;固定期限传「yyyy-MM-dd 至 yyyy-MM-dd」或「至 yyyy-MM-dd」。
|
||||
*
|
||||
* @param businessTermType 营业期限类型
|
||||
* @param businessEndDate 营业期限截止日
|
||||
* @param businessTermType 营业期限类型
|
||||
* @param businessStartDate 营业期限开始日
|
||||
* @param businessEndDate 营业期限截止日
|
||||
* @return 金蝶营业期限文本
|
||||
*/
|
||||
public String buildBusinessTerm(String businessTermType, LocalDate businessEndDate) {
|
||||
public String buildBusinessTerm(String businessTermType, LocalDate businessStartDate, LocalDate businessEndDate) {
|
||||
if (CustomerBfiConstants.BUSINESS_TERM_LONG.equals(businessTermType)) {
|
||||
return CustomerBfiConstants.BUSINESS_TERM_LONG;
|
||||
if (businessStartDate == null) {
|
||||
return CustomerBfiConstants.BUSINESS_TERM_LONG;
|
||||
}
|
||||
return businessStartDate + " 至 " + CustomerBfiConstants.BUSINESS_TERM_LONG;
|
||||
}
|
||||
return businessEndDate == null ? "" : "至 " + businessEndDate;
|
||||
if (businessStartDate != null && businessEndDate != null) {
|
||||
return businessStartDate + " 至 " + businessEndDate;
|
||||
}
|
||||
if (businessEndDate != null) {
|
||||
return "至 " + businessEndDate;
|
||||
}
|
||||
return businessStartDate == null ? "" : String.valueOf(businessStartDate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -50,6 +50,7 @@ import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
|
||||
import org.springblade.transport.service.IDriverWaybillService;
|
||||
import org.springblade.transport.service.IExceptionDisposalService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.MpUserDataScopeSupport;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -117,15 +118,18 @@ public class ExceptionDisposalServiceImpl
|
||||
private final IWaybillService waybillService;
|
||||
private final IDriverWaybillService driverWaybillService;
|
||||
private final LoadingManageMapper loadingManageMapper;
|
||||
private final MpUserDataScopeSupport mpUserDataScopeSupport;
|
||||
|
||||
public ExceptionDisposalServiceImpl(ExceptionDisposalFollowRecordMapper followRecordMapper,
|
||||
IWaybillService waybillService,
|
||||
IDriverWaybillService driverWaybillService,
|
||||
LoadingManageMapper loadingManageMapper) {
|
||||
LoadingManageMapper loadingManageMapper,
|
||||
MpUserDataScopeSupport mpUserDataScopeSupport) {
|
||||
this.followRecordMapper = followRecordMapper;
|
||||
this.waybillService = waybillService;
|
||||
this.driverWaybillService = driverWaybillService;
|
||||
this.loadingManageMapper = loadingManageMapper;
|
||||
this.mpUserDataScopeSupport = mpUserDataScopeSupport;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -929,6 +933,8 @@ public class ExceptionDisposalServiceImpl
|
||||
.like(Func.isNotEmpty(query.getCarrierName()), ExceptionDisposal::getCarrierName, query.getCarrierName())
|
||||
.eq(Func.isNotEmpty(query.getExceptionType()), ExceptionDisposal::getExceptionType, query.getExceptionType())
|
||||
.eq(Func.isNotEmpty(query.getDisposalStatus()), ExceptionDisposal::getDisposalStatus, query.getDisposalStatus());
|
||||
// 调度端列表按「权限组织 → 项目业务部门 → 项目」收口;司机端按运单查自己的异常时不套此过滤
|
||||
mpUserDataScopeSupport.applyProjectScopeIfMpDispatcher(wrapper, ExceptionDisposal::getProjectId);
|
||||
return wrapper.orderByDesc(ExceptionDisposal::getCreateTime);
|
||||
}
|
||||
|
||||
|
||||
+54
-7
@@ -350,9 +350,14 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
.eq(SettlementAdjustment::getFormalSettlementId, id)
|
||||
.orderByDesc(SettlementAdjustment::getCreateTime)).stream()
|
||||
.map(this::toAdjustmentVO).toList());
|
||||
vo.setChangeRecords(changeRecordMapper.selectList(Wrappers.<FormalSettlementChangeRecord>lambdaQuery()
|
||||
.eq(FormalSettlementChangeRecord::getFormalSettlementId, id)
|
||||
.orderByDesc(FormalSettlementChangeRecord::getChangeTime)));
|
||||
List<FormalSettlementChangeRecord> changeRecords = changeRecordMapper.selectList(
|
||||
Wrappers.<FormalSettlementChangeRecord>lambdaQuery()
|
||||
.eq(FormalSettlementChangeRecord::getFormalSettlementId, id)
|
||||
.orderByDesc(FormalSettlementChangeRecord::getChangeTime));
|
||||
// 审批通过/驳回类不展示在变更记录中
|
||||
vo.setChangeRecords(changeRecords.stream()
|
||||
.filter(record -> !isApprovalChangeContent(record.getChangeContent()))
|
||||
.toList());
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -558,6 +563,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
settlement.setKingdeeSyncStatus("unsynced");
|
||||
}
|
||||
if (first == null) copyHeader(contract, settlementType, directDetails.get(0), settlement); else copyHeader(first, settlement);
|
||||
settlement.setBusinessModel(limit(request.getBusinessModel(), 64));
|
||||
settlement.setTransportType(limit(request.getTransportType(), 64));
|
||||
settlement.setExchangeRateDate(request.getExchangeRateDate());
|
||||
settlement.setExchangeRate(request.getExchangeRate() == null ? BigDecimal.ONE : positive(request.getExchangeRate(), "结算汇率"));
|
||||
BigDecimal sourceAmount = sources.stream().map(PreSettlement::getSettlementAmount).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
@@ -631,7 +638,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
settlement.setCurrentProcessor(AuthUtil.getUserName());
|
||||
settlement.setApprovedTime(LocalDateTime.now());
|
||||
updateById(settlement);
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "正式结算审批通过", request.getReason());
|
||||
// 审批通过不落变更记录
|
||||
// 审批事务提交后自动推送金蝶(AFTER_COMMIT 监听器消费,推送失败不影响审批结果)
|
||||
eventPublisher.publishEvent(new KingdeeBillApprovedEvent(KingdeeBillApprovedEvent.BIZ_FORMAL_SETTLEMENT, settlement.getId()));
|
||||
}
|
||||
@@ -1268,8 +1275,30 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
}
|
||||
Map<Long, ReceivablePayableCargoFee> sourceFeeMap = receivablePayableCargoFeeMapper.selectBatchIds(sourceFeeIds).stream()
|
||||
.collect(Collectors.toMap(ReceivablePayableCargoFee::getId, Function.identity(), (left, right) -> left));
|
||||
// 历史数据可能把预结算费用行ID误写入 sourceFeeId,需再解析到应收应付费用行
|
||||
List<Long> unresolvedIds = sourceFeeIds.stream().filter(id -> !sourceFeeMap.containsKey(id)).toList();
|
||||
Map<Long, PreSettlementDetailFee> preFeeMap = unresolvedIds.isEmpty() ? Map.of()
|
||||
: preDetailFeeMapper.selectBatchIds(unresolvedIds).stream()
|
||||
.collect(Collectors.toMap(PreSettlementDetailFee::getId, Function.identity(), (left, right) -> left));
|
||||
List<Long> receivableFeeIds = preFeeMap.values().stream().map(PreSettlementDetailFee::getSourceFeeId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
if (!receivableFeeIds.isEmpty()) {
|
||||
receivablePayableCargoFeeMapper.selectBatchIds(receivableFeeIds).forEach(item ->
|
||||
sourceFeeMap.putIfAbsent(item.getId(), item));
|
||||
}
|
||||
fees.forEach(fee -> {
|
||||
ReceivablePayableCargoFee sourceFee = sourceFeeMap.get(fee.getSourceFeeId());
|
||||
Long sourceFeeId = fee.getSourceFeeId();
|
||||
ReceivablePayableCargoFee sourceFee = sourceFeeMap.get(sourceFeeId);
|
||||
if (sourceFee == null && sourceFeeId != null) {
|
||||
PreSettlementDetailFee preFee = preFeeMap.get(sourceFeeId);
|
||||
if (preFee != null && preFee.getSourceFeeId() != null) {
|
||||
sourceFee = sourceFeeMap.get(preFee.getSourceFeeId());
|
||||
if (sourceFee != null) {
|
||||
// 仅回写响应,便于前端试算;不落库覆盖历史 sourceFeeId
|
||||
fee.setSourceFeeId(preFee.getSourceFeeId());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sourceFee != null) {
|
||||
fee.setBillingRulesJson(sourceFee.getBillingRulesJson());
|
||||
}
|
||||
@@ -2029,7 +2058,11 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
for (PreSettlementDetailFee item : fees) {
|
||||
FormalSettlementDetailFee fee = Objects.requireNonNull(BeanUtil.copyProperties(item, FormalSettlementDetailFee.class));
|
||||
fee.setTransportQuantity(money(fee.getTransportQuantity()));
|
||||
fee.setId(null); fee.setFormalSettlementDetailId(target.getId()); fee.setSourceFeeId(item.getId()); detailFeeMapper.insert(fee);
|
||||
fee.setId(null);
|
||||
fee.setFormalSettlementDetailId(target.getId());
|
||||
// 优先保留应收应付费用行ID,便于正式结算调整时按合同计费方案试算
|
||||
fee.setSourceFeeId(item.getSourceFeeId() != null ? item.getSourceFeeId() : item.getId());
|
||||
detailFeeMapper.insert(fee);
|
||||
}
|
||||
if (fees.isEmpty()) createSingleFee(target);
|
||||
}
|
||||
@@ -2334,7 +2367,10 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
settlement.setApprovalStatus(target); settlement.setCurrentNode(node); settlement.setCurrentProcessor(AuthUtil.getUserName());
|
||||
if (TransportBusiConstant.RETURNED.equals(target)) settlement.setVoidReason(limit(reason, 200));
|
||||
updateById(settlement);
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", node, reason);
|
||||
// 审批驳回不落变更记录;提交等其它状态流转仍记录
|
||||
if (!TransportBusiConstant.RETURNED.equals(target)) {
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", node, reason);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveManualSummaryFeeChange(Long settlementId, boolean creating, String beforeFeeType,
|
||||
@@ -2393,6 +2429,14 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
}
|
||||
}
|
||||
|
||||
/** 审批通过、驳回等审批流转不落变更记录。 */
|
||||
private boolean isApprovalChangeContent(String content) {
|
||||
if (Func.isEmpty(content)) {
|
||||
return false;
|
||||
}
|
||||
return content.contains("审批通过") || content.contains("审批驳回") || "已驳回".equals(content);
|
||||
}
|
||||
|
||||
private void saveChange(Long settlementId, String changeType, Integer lineNo, String operationType,
|
||||
String content, String reason) {
|
||||
saveChange(settlementId, changeType, lineNo, operationType, content, reason, null, null);
|
||||
@@ -2400,6 +2444,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
|
||||
private void saveChange(Long settlementId, String changeType, Integer lineNo, String operationType,
|
||||
String content, String reason, Map<String, Object> beforeData, Map<String, Object> afterData) {
|
||||
if (isApprovalChangeContent(content)) {
|
||||
return;
|
||||
}
|
||||
FormalSettlementChangeRecord record = new FormalSettlementChangeRecord();
|
||||
record.setFormalSettlementId(settlementId);
|
||||
record.setChangeType(changeType);
|
||||
|
||||
+204
-6
@@ -20,9 +20,11 @@ import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.transport.excel.LoadingManageExcel;
|
||||
import org.springblade.transport.mapper.LoadingManageMapper;
|
||||
import org.springblade.transport.mapper.WaybillMapper;
|
||||
import org.springblade.transport.mapper.WaybillNodePunchMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.entity.WaybillNodePunch;
|
||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||
import org.springblade.transport.pojo.vo.LoadingCarrierContractVO;
|
||||
import org.springblade.transport.pojo.vo.LoadingManageVO;
|
||||
@@ -74,6 +76,9 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
@Resource
|
||||
private WaybillMapper waybillMapper;
|
||||
|
||||
@Resource
|
||||
private WaybillNodePunchMapper waybillNodePunchMapper;
|
||||
|
||||
@Resource
|
||||
private IContractManageService contractManageService;
|
||||
|
||||
@@ -139,10 +144,26 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
loadingManage.setLoadingNo(nextCode());
|
||||
}
|
||||
validateCarrierContract(loadingManage, false);
|
||||
if (STATUS_RUNNING.equals(businessStatus) && Func.isNotEmpty(loadingManage.getId())) {
|
||||
LoadingManage oldRecord = getById(loadingManage.getId());
|
||||
if (oldRecord != null) {
|
||||
validateRunningImmutableTaskFields(oldRecord, loadingManage);
|
||||
Set<Long> nextIds = new HashSet<>(waybillIds(loadingManage.getWaybillIdsJson()));
|
||||
for (Long oldId : oldWaybillIds) {
|
||||
if (oldId != null && !nextIds.contains(oldId) && hasLoadOrUnloadPunch(oldId)) {
|
||||
throw new ServiceException("已打卡的子运单不可从进行中配载单移除");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean result = saveOrUpdate(loadingManage);
|
||||
// 草稿仅预占运单号,不把运单业务状态改成 draft;正式确认时再完整同步为待执行
|
||||
if (STATUS_DRAFT.equals(businessStatus)) {
|
||||
syncDraftWaybillReservation(loadingManage, oldWaybillIds);
|
||||
} else if (STATUS_RUNNING.equals(businessStatus)) {
|
||||
// 进行中暂存:不重置原有子单状态/接单,仅同步任务信息与新增子单
|
||||
List<Waybill> waybills = loadWaybills(waybillIds(loadingManage.getWaybillIdsJson()));
|
||||
writeWaybillsForRunningEdit(loadingManage, oldWaybillIds, waybills);
|
||||
} else {
|
||||
syncAssociatedWaybills(loadingManage, businessStatus, oldWaybillIds);
|
||||
}
|
||||
@@ -156,28 +177,103 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
validate(loadingManage, false);
|
||||
List<Long> oldWaybillIds = new ArrayList<>();
|
||||
boolean ownChildrenAdmitted = false;
|
||||
boolean runningEdit = false;
|
||||
LoadingManage oldRecord = null;
|
||||
if (Func.isNotEmpty(loadingManage.getId())) {
|
||||
LoadingManage oldRecord = loadEditable(loadingManage.getId(), true);
|
||||
oldRecord = loadEditable(loadingManage.getId(), true);
|
||||
oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson());
|
||||
// 草稿只预占运单号、未过判据,草稿转正式不得沿用本单豁免
|
||||
ownChildrenAdmitted = !STATUS_DRAFT.equals(oldRecord.getBusinessStatus());
|
||||
runningEdit = STATUS_RUNNING.equals(oldRecord.getBusinessStatus());
|
||||
loadingManage.setLoadingNo(oldRecord.getLoadingNo());
|
||||
loadingManage.setDeptId(oldRecord.getDeptId());
|
||||
loadingManage.setDeptName(oldRecord.getDeptName());
|
||||
}
|
||||
prepareCreateOrUpdate(loadingManage);
|
||||
// 确认提交:草稿/新建统一生成正式配载单(待执行)
|
||||
loadingManage.setBusinessStatus(STATUS_PENDING);
|
||||
if (Func.isEmpty(loadingManage.getLoadingNo())) {
|
||||
loadingManage.setLoadingNo(nextCode());
|
||||
}
|
||||
List<Waybill> waybills = validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson()), ownChildrenAdmitted);
|
||||
validateCarrierContract(loadingManage, true, waybills);
|
||||
if (runningEdit && oldRecord != null) {
|
||||
validateRunningEdit(oldRecord, loadingManage, waybills, oldWaybillIds);
|
||||
}
|
||||
// 进行中编辑保持 running;新建/草稿确认按项目过程配置:需接单→待执行,无需接单→进行中
|
||||
loadingManage.setBusinessStatus(runningEdit ? STATUS_RUNNING : deriveLoadingBusinessStatus(waybills));
|
||||
boolean result = saveOrUpdate(loadingManage);
|
||||
writeWaybillsForDriver(loadingManage, oldWaybillIds, waybills);
|
||||
// 进行中编辑:保留原有子单状态与接单事实,仅对新加入子单做派车同步
|
||||
if (runningEdit) {
|
||||
writeWaybillsForRunningEdit(loadingManage, oldWaybillIds, waybills);
|
||||
} else {
|
||||
writeWaybillsForDriver(loadingManage, oldWaybillIds, waybills);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行中配载编辑校验:必填任务字段不可改;已装/卸打卡的子运单不可移除。
|
||||
*/
|
||||
private void validateRunningEdit(LoadingManage oldRecord, LoadingManage incoming,
|
||||
List<Waybill> waybills, List<Long> oldWaybillIds) {
|
||||
validateRunningImmutableTaskFields(oldRecord, incoming);
|
||||
Set<Long> nextIds = waybills.stream().map(Waybill::getId).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
for (Long oldId : oldWaybillIds) {
|
||||
if (oldId == null || nextIds.contains(oldId)) {
|
||||
continue;
|
||||
}
|
||||
if (hasLoadOrUnloadPunch(oldId)) {
|
||||
throw new ServiceException("已打卡的子运单不可从进行中配载单移除");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRunningImmutableTaskFields(LoadingManage oldRecord, LoadingManage incoming) {
|
||||
if (!Objects.equals(trimText(oldRecord.getCarrierType()), trimText(incoming.getCarrierType()))) {
|
||||
throw new ServiceException("进行中配载单不允许修改承运类型");
|
||||
}
|
||||
if (Objects.equals(oldRecord.getCarrierType(), "承运商")
|
||||
&& !Objects.equals(oldRecord.getCarrierContractId(), incoming.getCarrierContractId())) {
|
||||
throw new ServiceException("进行中配载单不允许修改承运商");
|
||||
}
|
||||
if (!Objects.equals(trimText(oldRecord.getVehicleNo()), trimText(incoming.getVehicleNo()))) {
|
||||
throw new ServiceException("进行中配载单不允许修改车牌号");
|
||||
}
|
||||
if (Objects.equals(oldRecord.getCarrierType(), CARRIER_SELF)) {
|
||||
if (!Objects.equals(trimText(oldRecord.getDriverName()), trimText(incoming.getDriverName()))) {
|
||||
throw new ServiceException("进行中配载单不允许修改司机");
|
||||
}
|
||||
if (!Objects.equals(trimText(oldRecord.getDriverPhone()), trimText(incoming.getDriverPhone()))) {
|
||||
throw new ServiceException("进行中配载单不允许修改手机号");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasLoadOrUnloadPunch(Long waybillId) {
|
||||
if (waybillId == null) {
|
||||
return false;
|
||||
}
|
||||
List<WaybillNodePunch> punches = waybillNodePunchMapper.selectList(Wrappers.<WaybillNodePunch>lambdaQuery()
|
||||
.eq(WaybillNodePunch::getWaybillId, waybillId)
|
||||
.eq(WaybillNodePunch::getIsDeleted, 0));
|
||||
return punches.stream().anyMatch(this::isLoadOrUnloadPunch);
|
||||
}
|
||||
|
||||
private boolean isLoadOrUnloadPunch(WaybillNodePunch punch) {
|
||||
if (punch == null) {
|
||||
return false;
|
||||
}
|
||||
String code = Func.toStr(punch.getNodeCode(), "").trim().toLowerCase();
|
||||
String name = Func.toStr(punch.getNodeName(), "").trim();
|
||||
return "load".equals(code) || name.contains("装货")
|
||||
|| "unload".equals(code) || name.contains("卸货")
|
||||
|| "arrival".equals(code) || name.contains("到货")
|
||||
|| "sign".equals(code) || name.contains("签收");
|
||||
}
|
||||
|
||||
private String trimText(String value) {
|
||||
return Func.toStr(value, "").trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createFromImportedWaybills(String loadingNo, List<Waybill> waybills) {
|
||||
@@ -776,8 +872,8 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
String prefix = "运单" + waybill.getWaybillNo();
|
||||
return prefix + switch (reason) {
|
||||
case NOT_ROAD -> "运输方式不是公路,不可配载";
|
||||
case NOT_PENDING -> "当前状态不是待执行,不可配载";
|
||||
case ON_OTHER_MANIFEST -> "已关联其他配载单";
|
||||
case NOT_LOADABLE_STATUS -> "当前状态不是待执行或进行中,不可配载";
|
||||
case ON_OTHER_MANIFEST -> "已关联其他有效配载单";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -910,6 +1006,108 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
clearAssociatedWaybillAcceptRecords(loadingManage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行中配载编辑同步:原有子单只更新任务可选信息,保留业务状态与接单事实;
|
||||
* 新加入子单按正常派车逻辑推导状态并清接单;被移除且未打卡的子单释放回待执行。
|
||||
*/
|
||||
private void writeWaybillsForRunningEdit(LoadingManage loadingManage, List<Long> oldWaybillIds, List<Waybill> waybills) {
|
||||
List<Long> newWaybillIds = waybillIds(loadingManage.getWaybillIdsJson());
|
||||
Set<Long> oldIdSet = new HashSet<>(oldWaybillIds);
|
||||
List<Long> removedIds = oldWaybillIds.stream()
|
||||
.filter(id -> id != null && !newWaybillIds.contains(id))
|
||||
.toList();
|
||||
releaseAssociatedWaybills(removedIds, STATUS_PENDING);
|
||||
List<Long> keptIds = newWaybillIds.stream()
|
||||
.filter(oldIdSet::contains)
|
||||
.toList();
|
||||
if (Func.isNotEmpty(keptIds)) {
|
||||
updateAssociatedWaybillTaskFields(loadingManage, keptIds, null);
|
||||
}
|
||||
List<Long> addedIds = newWaybillIds.stream()
|
||||
.filter(id -> id != null && !oldIdSet.contains(id))
|
||||
.toList();
|
||||
if (Func.isEmpty(addedIds)) {
|
||||
return;
|
||||
}
|
||||
List<Waybill> addedWaybills = waybills.stream()
|
||||
.filter(waybill -> waybill.getId() != null && addedIds.contains(waybill.getId()))
|
||||
.toList();
|
||||
Map<Long, String> statusByWaybillId = deriveWaybillBusinessStatuses(addedWaybills);
|
||||
Map<String, List<Long>> waybillIdsByStatus = new LinkedHashMap<>();
|
||||
for (Long waybillId : addedIds) {
|
||||
waybillIdsByStatus
|
||||
.computeIfAbsent(statusByWaybillId.getOrDefault(waybillId, STATUS_PENDING), key -> new ArrayList<>())
|
||||
.add(waybillId);
|
||||
}
|
||||
waybillIdsByStatus.forEach((status, ids) -> updateAssociatedWaybillFields(loadingManage, ids, status));
|
||||
clearWaybillAcceptRecords(addedIds);
|
||||
}
|
||||
|
||||
private void updateAssociatedWaybillTaskFields(LoadingManage loadingManage, List<Long> waybillIds, String status) {
|
||||
if (Func.isEmpty(waybillIds)) {
|
||||
return;
|
||||
}
|
||||
var update = Wrappers.<Waybill>lambdaUpdate()
|
||||
.in(Waybill::getId, waybillIds)
|
||||
.set(Waybill::getLoadingNo, loadingManage.getLoadingNo())
|
||||
.set(Waybill::getCarrierType, loadingManage.getCarrierType())
|
||||
.set(Waybill::getCarrierContractId, loadingManage.getCarrierContractId())
|
||||
.set(Waybill::getCarrierName, loadingManage.getCarrierName())
|
||||
.set(Waybill::getDriverName, loadingManage.getDriverName())
|
||||
.set(Waybill::getDriverPhone, loadingManage.getDriverPhone())
|
||||
.set(Waybill::getVehicleNo, loadingManage.getVehicleNo())
|
||||
.set(Waybill::getTrailerVehicleNo, loadingManage.getTrailerVehicleNo())
|
||||
.set(Waybill::getEscortName, loadingManage.getEscortName())
|
||||
.set(Waybill::getEscortPhone, loadingManage.getEscortPhone())
|
||||
.set(Waybill::getMileage, loadingManage.getMileage())
|
||||
.set(Waybill::getEstimatedStartTime, toDateTime(loadingManage.getEstimatedStartDate()))
|
||||
.set(Waybill::getEstimatedEndTime, toDateTime(loadingManage.getEstimatedEndDate()))
|
||||
.set(Waybill::getTaskRemark, loadingManage.getTaskRemark())
|
||||
.set(Waybill::getTaskInfoJson, loadingManage.getTaskInfoJson())
|
||||
.set(Waybill::getCurrentProcessNode, loadingManage.getCurrentProcessNode());
|
||||
if (Func.isNotEmpty(status)) {
|
||||
update.set(Waybill::getBusinessStatus, status);
|
||||
if (STATUS_COMPLETED.equals(status)) {
|
||||
update.set(Waybill::getEndDate, LocalDate.now());
|
||||
}
|
||||
}
|
||||
waybillMapper.update(null, update);
|
||||
}
|
||||
|
||||
private void clearWaybillAcceptRecords(List<Long> waybillIds) {
|
||||
if (Func.isEmpty(waybillIds)) {
|
||||
return;
|
||||
}
|
||||
waybillMapper.update(null, Wrappers.<Waybill>lambdaUpdate()
|
||||
.in(Waybill::getId, waybillIds)
|
||||
.set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_PENDING)
|
||||
.set(Waybill::getDriverAcceptTime, null)
|
||||
.set(Waybill::getDriverAcceptDriverId, null)
|
||||
.set(Waybill::getDriverRejectTime, null)
|
||||
.set(Waybill::getDriverRejectReason, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 配载单业务状态按子运单所属项目的实时过程配置推导:需要接单 → 待执行,不需要 → 进行中。
|
||||
* <p>
|
||||
* 一张配载单的子运单同属一个项目,任一张需接单则整单待执行。
|
||||
*/
|
||||
private String deriveLoadingBusinessStatus(List<Waybill> waybills) {
|
||||
if (Func.isEmpty(waybills)) {
|
||||
return STATUS_RUNNING;
|
||||
}
|
||||
Map<Long, String> liveConfigByProject = LiveProcessConfigSupport.byProjects(processConfigService,
|
||||
waybills.stream().map(Waybill::getProjectId).toList());
|
||||
for (Waybill waybill : waybills) {
|
||||
String processJson = LiveProcessConfigSupport.resolve(
|
||||
liveConfigByProject.get(waybill.getProjectId()), waybill.getProcessJson());
|
||||
if (WaybillProcessSupport.requiresDriverAcceptConfirmation(processJson)) {
|
||||
return STATUS_PENDING;
|
||||
}
|
||||
}
|
||||
return STATUS_RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 子运单业务状态逐张按各自项目的实时过程配置推导:需要接单 → 待执行,不需要 → 进行中。
|
||||
* <p>
|
||||
|
||||
+102
-22
@@ -33,6 +33,7 @@ import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserDisplayCache;
|
||||
import org.springblade.system.utils.MpUserAuth;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
@@ -59,6 +60,7 @@ import org.springblade.transport.service.IManageWaybillService;
|
||||
import org.springblade.transport.service.IRiskDisposalService;
|
||||
import org.springblade.transport.service.ITripQueryService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.MpUserDataScopeSupport;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.WaybillCargoSupport;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
@@ -89,8 +91,8 @@ import java.util.stream.Collectors;
|
||||
* 列表与详情与司机端同粒度:取数、状态聚合、接单需求合并都调共享运次读服务,
|
||||
* 本类只做字段裁剪与调度端独有的筛选(组织类型 / 异常 / 创建时间 / 待重新派单)。
|
||||
* <p>
|
||||
* 小程序调度账号(如「小程序管理」)组织常与运单业务组织不一致,故不做 dept 过滤,
|
||||
* 仅依赖租户隔离,口径接近后台 {@code /waybill-manage/list?allDept=1}。
|
||||
* 小程序调度端:所属组织 + 数据权限范围 + 数据层级范围 → 业务部门在范围内的项目 → 再取这些项目的运单;
|
||||
* 异常/风险与运单同一项目口径。系统用户仍按「所属组织 / 超管不过滤」。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -139,13 +141,14 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
private final IDriverWaybillService driverWaybillService;
|
||||
private final ITripQueryService tripQueryService;
|
||||
private final ILoadingManageService loadingManageService;
|
||||
private final MpUserDataScopeSupport mpUserDataScopeSupport;
|
||||
|
||||
@Override
|
||||
public AdminHomeStatsVO stats() {
|
||||
AdminHomeStatsVO vo = new AdminHomeStatsVO();
|
||||
// 三档状态按运次计:有配载单的一趟车算一个,与列表和司机端同一份聚合
|
||||
TripQueryVO query = TripQueryVO.forAdmin();
|
||||
query.setDeptIds(manageableDeptIds());
|
||||
applyAdminDataScope(query);
|
||||
TripTabCountsVO tripCounts = tripQueryService.tabCounts(query);
|
||||
vo.setPendingAccept(tripCounts.getPending());
|
||||
vo.setTransporting(tripCounts.getDoing());
|
||||
@@ -183,7 +186,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
// 组织类型 / 异常 / 创建时间都是子单行级条件,与状态、关键字、分页一起交给运次读服务,
|
||||
// 一次取数取完整趟车:分组、分档、筛选、排序与分页都在它那边算完
|
||||
TripQueryVO query = TripQueryVO.forAdmin();
|
||||
query.setDeptIds(manageableDeptIds());
|
||||
applyAdminDataScope(query);
|
||||
query.setStatus(toTripStatus(status));
|
||||
query.setKeyword(keyword);
|
||||
query.setTransportOrgType(transportType);
|
||||
@@ -231,7 +234,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
|
||||
Set<Long> exceptionWaybillIds = loadIncompleteExceptionWaybillIds();
|
||||
TripQueryVO query = TripQueryVO.forAdmin();
|
||||
query.setDeptIds(manageableDeptIds());
|
||||
applyAdminDataScope(query);
|
||||
query.setKeyword(keyword);
|
||||
// 待处理只要待接单与进行中两档;「未拒单」也是交给运次读服务按子单行筛
|
||||
query.setStatuses(List.of(0, 1));
|
||||
@@ -669,11 +672,8 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
SQL_MAX_REJECT_TIME + " AS driver_reject_time", SQL_MAX_CREATE_TIME + " AS create_time");
|
||||
wrapper.eq("driver_accept_status", ACCEPT_REJECTED);
|
||||
wrapper.in("business_status", STATUS_PENDING, STATUS_RUNNING);
|
||||
// 这条查询不经运次读服务,组织收口得自己带上,否则待重派列表与首页重派角标会出别的组织的单
|
||||
List<Long> deptIds = manageableDeptIds();
|
||||
if (Func.isNotEmpty(deptIds)) {
|
||||
wrapper.in("dept_id", deptIds);
|
||||
}
|
||||
// 这条查询不经运次读服务,数据权限收口得自己带上,否则待重派列表与首页重派角标会越权
|
||||
applyAdminDataScope(wrapper);
|
||||
wrapper.groupBy(TripAggregation.SQL_TRIP_KEY);
|
||||
return wrapper;
|
||||
}
|
||||
@@ -892,10 +892,50 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度端可见组织:普通用户(含各级角色)只出自己所属组织,超级管理员不过滤。
|
||||
* <p>
|
||||
* 小程序不校验「所属组织不能为空」:账号没有组织时不按组织收口,避免首页、待处理等接口直接报错。
|
||||
* 详情与重新派单本身不走这里。
|
||||
* 调度端数据权限落到运次查询条件:小程序按可见项目,系统用户按所属组织。
|
||||
*/
|
||||
private void applyAdminDataScope(TripQueryVO query) {
|
||||
if (query == null) {
|
||||
return;
|
||||
}
|
||||
if (MpUserAuth.isMpUser()) {
|
||||
query.setProjectIds(manageableProjectIds());
|
||||
return;
|
||||
}
|
||||
query.setDeptIds(manageableDeptIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* 待重派等直查运单表时的数据权限:小程序按 project_id,系统用户按 dept_id。
|
||||
*/
|
||||
private void applyAdminDataScope(QueryWrapper<Waybill> wrapper) {
|
||||
if (wrapper == null) {
|
||||
return;
|
||||
}
|
||||
if (MpUserAuth.isMpUser()) {
|
||||
List<Long> projectIds = manageableProjectIds();
|
||||
if (Func.isNotEmpty(projectIds)) {
|
||||
wrapper.in("project_id", projectIds);
|
||||
}
|
||||
return;
|
||||
}
|
||||
List<Long> deptIds = manageableDeptIds();
|
||||
if (Func.isNotEmpty(deptIds)) {
|
||||
wrapper.in("dept_id", deptIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序调度端可见项目:所属组织 + 数据权限范围 + 数据层级范围 → 业务部门在范围内的项目。
|
||||
* 空列表不过滤;[-1] 表示无权限。
|
||||
*/
|
||||
private List<Long> manageableProjectIds() {
|
||||
List<Long> projectIds = mpUserDataScopeSupport.resolveVisibleProjectIdsForQuery();
|
||||
return projectIds == null ? List.of() : projectIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统用户可见组织:普通账号只出所属组织,超管不过滤;组织为空不拦截。
|
||||
*/
|
||||
private List<Long> manageableDeptIds() {
|
||||
if (TransportBusinessSupport.isAdministrator()) {
|
||||
@@ -919,10 +959,12 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
}
|
||||
|
||||
private Set<Long> loadIncompleteExceptionWaybillIds() {
|
||||
List<ExceptionDisposal> list = exceptionDisposalService.list(Wrappers.<ExceptionDisposal>lambdaQuery()
|
||||
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
|
||||
.select(ExceptionDisposal::getWaybillId)
|
||||
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING)
|
||||
.isNotNull(ExceptionDisposal::getWaybillId));
|
||||
.isNotNull(ExceptionDisposal::getWaybillId);
|
||||
applyExceptionDeptScope(wrapper);
|
||||
List<ExceptionDisposal> list = exceptionDisposalService.list(wrapper);
|
||||
Set<Long> ids = new HashSet<>();
|
||||
for (ExceptionDisposal item : list) {
|
||||
if (item.getWaybillId() != null) {
|
||||
@@ -932,6 +974,38 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
return ids;
|
||||
}
|
||||
|
||||
/** 异常与运单同一套项目口径(小程序);系统用户仍按组织 */
|
||||
private void applyExceptionDeptScope(LambdaQueryWrapper<ExceptionDisposal> wrapper) {
|
||||
if (MpUserAuth.isMpUser()) {
|
||||
List<Long> projectIds = manageableProjectIds();
|
||||
if (Func.isNotEmpty(projectIds)) {
|
||||
wrapper.in(ExceptionDisposal::getProjectId, projectIds);
|
||||
}
|
||||
return;
|
||||
}
|
||||
List<Long> deptIds = manageableDeptIds();
|
||||
if (Func.isNotEmpty(deptIds)) {
|
||||
wrapper.in(ExceptionDisposal::getDeptId, deptIds);
|
||||
}
|
||||
}
|
||||
|
||||
/** 风险无项目字段:小程序按关联运单的项目收口;系统用户按组织 */
|
||||
private void applyRiskDeptScope(LambdaQueryWrapper<RiskDisposal> wrapper) {
|
||||
if (MpUserAuth.isMpUser()) {
|
||||
List<Long> projectIds = manageableProjectIds();
|
||||
if (Func.isNotEmpty(projectIds)) {
|
||||
String inList = projectIds.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||
wrapper.inSql(RiskDisposal::getWaybillId,
|
||||
"SELECT id FROM blade_waybill WHERE is_deleted = 0 AND project_id IN (" + inList + ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
List<Long> deptIds = manageableDeptIds();
|
||||
if (Func.isNotEmpty(deptIds)) {
|
||||
wrapper.in(RiskDisposal::getDeptId, deptIds);
|
||||
}
|
||||
}
|
||||
|
||||
private AdminWaybillCardVO toCard(Waybill waybill, boolean hasException) {
|
||||
AdminWaybillCardVO card = new AdminWaybillCardVO();
|
||||
card.setId(waybill.getId());
|
||||
@@ -1062,13 +1136,17 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
}
|
||||
|
||||
private long countIncompleteExceptions() {
|
||||
return exceptionDisposalService.count(Wrappers.<ExceptionDisposal>lambdaQuery()
|
||||
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING));
|
||||
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
|
||||
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING);
|
||||
applyExceptionDeptScope(wrapper);
|
||||
return exceptionDisposalService.count(wrapper);
|
||||
}
|
||||
|
||||
private long countPendingRisks() {
|
||||
return riskDisposalService.count(Wrappers.<RiskDisposal>lambdaQuery()
|
||||
.eq(RiskDisposal::getDisposalStatus, RISK_PENDING));
|
||||
LambdaQueryWrapper<RiskDisposal> wrapper = Wrappers.<RiskDisposal>lambdaQuery()
|
||||
.eq(RiskDisposal::getDisposalStatus, RISK_PENDING);
|
||||
applyRiskDeptScope(wrapper);
|
||||
return riskDisposalService.count(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1110,10 +1188,12 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
}
|
||||
|
||||
private List<TimedTodo> buildExceptionFeed() {
|
||||
List<ExceptionDisposal> list = exceptionDisposalService.list(Wrappers.<ExceptionDisposal>lambdaQuery()
|
||||
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
|
||||
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING)
|
||||
.orderByDesc(ExceptionDisposal::getReportTime)
|
||||
.last("LIMIT " + FEED_LIMIT));
|
||||
.last("LIMIT " + FEED_LIMIT);
|
||||
applyExceptionDeptScope(wrapper);
|
||||
List<ExceptionDisposal> list = exceptionDisposalService.list(wrapper);
|
||||
return list.stream()
|
||||
.map(disposal -> new TimedTodo(toTodoItem(disposal), exceptionTimeOf(disposal)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
+6
-58
@@ -2131,21 +2131,18 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
if (settlements.isEmpty()) return;
|
||||
settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId()));
|
||||
boolean customAmounts = settlements.size() > 1 && lines != null && !lines.isEmpty();
|
||||
boolean customAmounts = this.useSpecifiedLineAmounts(lines);
|
||||
BigDecimal totalBase = settlements.stream()
|
||||
.map(item -> payableOfFormalSettlement(item.getId(), item.getSettlementAmount(), entity.getId()))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal remainingApplied = money(entity.getAppliedAmount());
|
||||
BigDecimal remainingPaid = money(entity.getPaidAmount());
|
||||
if (customAmounts) {
|
||||
BigDecimal lineTotal = BigDecimal.ZERO;
|
||||
for (FormalSettlement settlement : settlements) {
|
||||
BigDecimal payable = payableOfFormalSettlement(settlement.getId(), settlement.getSettlementAmount(), entity.getId());
|
||||
BigDecimal applied = this.specifiedAppliedAmount(lines, settlement.getId(), null);
|
||||
this.assertAppliedWithinPayable(settlement.getFormalSettlementNo(), applied, payable);
|
||||
lineTotal = lineTotal.add(applied);
|
||||
}
|
||||
this.assertLineTotalMatches(entity, lineTotal);
|
||||
}
|
||||
for (int index = 0; index < settlements.size(); index++) {
|
||||
FormalSettlement settlement = settlements.get(index);
|
||||
@@ -2243,20 +2240,18 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return;
|
||||
}
|
||||
// 单选/多选均落关联行,生成即占用可付款额度(删除草稿或审核不通过后释放)
|
||||
boolean customAmounts = targetSettlements.size() > 1 && lines != null && !lines.isEmpty();
|
||||
// 结算行付款金额独立于付款基本信息中的申请付款金额,有明细时按行保存
|
||||
boolean customAmounts = this.useSpecifiedLineAmounts(lines);
|
||||
BigDecimal totalBase = targetSettlements.stream()
|
||||
.map(item -> payableOfPreSettlement(item.getId(), item.getSettlementAmount(), entity.getId()))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal remainingApplied = money(entity.getAppliedAmount());
|
||||
if (customAmounts) {
|
||||
BigDecimal lineTotal = BigDecimal.ZERO;
|
||||
for (PreSettlement settlement : targetSettlements) {
|
||||
BigDecimal payable = payableOfPreSettlement(settlement.getId(), settlement.getSettlementAmount(), entity.getId());
|
||||
BigDecimal applied = this.specifiedAppliedAmount(lines, null, settlement.getId());
|
||||
this.assertAppliedWithinPayable(settlement.getPreSettlementNo(), applied, payable);
|
||||
lineTotal = lineTotal.add(applied);
|
||||
}
|
||||
this.assertLineTotalMatches(entity, lineTotal);
|
||||
}
|
||||
for (int index = 0; index < targetSettlements.size(); index++) {
|
||||
PreSettlement settlement = targetSettlements.get(index);
|
||||
@@ -2307,11 +2302,9 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
}
|
||||
|
||||
private void assertLineTotalMatches(PaymentApplication entity, BigDecimal lineTotal) {
|
||||
BigDecimal appliedAmount = money(entity.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP);
|
||||
if (lineTotal.setScale(2, RoundingMode.HALF_UP).compareTo(appliedAmount) != 0) {
|
||||
throw new ServiceException("申请付款金额须等于各结算单付款金额合计");
|
||||
}
|
||||
/** 前端传入结算行付款金额时,按行落库,不再要求合计等于申请付款金额。 */
|
||||
private boolean useSpecifiedLineAmounts(List<PaymentApplicationSettlementLineRequest> lines) {
|
||||
return lines != null && !lines.isEmpty();
|
||||
}
|
||||
|
||||
private void validateQuota(PaymentApplication entity) {
|
||||
@@ -2563,44 +2556,11 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
private void validateBill(BillLedger ledger, Long deptId, BigDecimal amount, boolean checkMaturity,
|
||||
Long paymentApplicationId, Long settlementId, Long preSettlementId) {
|
||||
if (ledger == null || Objects.equals(ledger.getIsDeleted(), 1)) throw new ServiceException("所选汇票台账不存在");
|
||||
boolean partyMatched = settlementPartiesMatch(ledger, settlementId, preSettlementId);
|
||||
if (!"received".equals(ledger.getBillType()) && !partyMatched) {
|
||||
throw new ServiceException("汇票付款仅可选择收票类型的汇票,或出票单位与收票单位均为当前结算收付款方的汇票");
|
||||
}
|
||||
if (checkMaturity && (ledger.getMaturityDate() == null || ledger.getMaturityDate().isBefore(LocalDate.now()))) {
|
||||
throw new ServiceException("所选汇票已到期");
|
||||
}
|
||||
BigDecimal available = money(ledger.getAvailableBalance()).add(occupiedAmount(paymentApplicationId, ledger.getId()));
|
||||
if (money(amount).compareTo(available) > 0) throw new ServiceException("申请付款金额超过汇票可用余额");
|
||||
if (!partyMatched && !departmentAvailable(ledger, deptId)) throw new ServiceException("当前使用部门不在汇票可用部门范围内");
|
||||
}
|
||||
|
||||
/** 出票单位、收票单位与结算付款方、收款方一致(顺序不限) */
|
||||
private boolean settlementPartiesMatch(BillLedger ledger, Long settlementId, Long preSettlementId) {
|
||||
String payerName = null;
|
||||
String payeeName = null;
|
||||
if (settlementId != null) {
|
||||
FormalSettlement settlement = formalSettlementMapper.selectById(settlementId);
|
||||
if (settlement != null && !Objects.equals(settlement.getIsDeleted(), 1)) {
|
||||
payerName = settlement.getPayerName();
|
||||
payeeName = settlement.getPayeeName();
|
||||
}
|
||||
}
|
||||
if ((payerName == null || payeeName == null) && preSettlementId != null) {
|
||||
PreSettlement settlement = preSettlementMapper.selectById(preSettlementId);
|
||||
if (settlement != null && !Objects.equals(settlement.getIsDeleted(), 1)) {
|
||||
payerName = settlement.getPayerName();
|
||||
payeeName = settlement.getPayeeName();
|
||||
}
|
||||
}
|
||||
String issuer = ledger.getIssuerName() == null ? "" : ledger.getIssuerName().trim();
|
||||
String receiver = ledger.getReceiverName() == null ? "" : ledger.getReceiverName().trim();
|
||||
String payer = payerName == null ? "" : payerName.trim();
|
||||
String payee = payeeName == null ? "" : payeeName.trim();
|
||||
if (issuer.isEmpty() || receiver.isEmpty() || payer.isEmpty() || payee.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return (issuer.equals(payer) && receiver.equals(payee)) || (issuer.equals(payee) && receiver.equals(payer));
|
||||
}
|
||||
|
||||
private void useBillBalance(PaymentApplication entity) {
|
||||
@@ -2756,18 +2716,6 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return entity;
|
||||
}
|
||||
|
||||
private boolean departmentAvailable(BillLedger ledger, Long deptId) {
|
||||
if (Func.isEmpty(ledger.getAvailableDeptIdsJson())) return false;
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(ledger.getAvailableDeptIdsJson(), List.class);
|
||||
if (!(parsed instanceof List<?> values)) return false;
|
||||
if (values.stream().anyMatch(value -> "all".equals(String.valueOf(value)))) return true;
|
||||
return deptId != null && values.stream().anyMatch(value -> String.valueOf(deptId).equals(String.valueOf(value)));
|
||||
} catch (Exception exception) {
|
||||
throw new ServiceException("汇票可用部门配置不正确");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBillPayment(String paymentMethod) {
|
||||
if (Func.isEmpty(paymentMethod)) return false;
|
||||
|
||||
+23
-8
@@ -203,13 +203,15 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
vo.setAdvances(advances);
|
||||
vo.setProjectAdvanceWriteOffs(projectAdvanceWriteOffService.listBySettlement(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_PRE, id));
|
||||
vo.setChangeRecords(changeRecordMapper.selectList(Wrappers.<PreSettlementChangeRecord>lambdaQuery()
|
||||
.eq(PreSettlementChangeRecord::getPreSettlementId, id)
|
||||
.eq(PreSettlementChangeRecord::getIsDeleted, 0)
|
||||
.and(wrapper -> wrapper.isNull(PreSettlementChangeRecord::getChangeContent)
|
||||
.or()
|
||||
.ne(PreSettlementChangeRecord::getChangeContent, "预结算审批通过"))
|
||||
.orderByDesc(PreSettlementChangeRecord::getChangeTime)));
|
||||
List<PreSettlementChangeRecord> changeRecords = changeRecordMapper.selectList(
|
||||
Wrappers.<PreSettlementChangeRecord>lambdaQuery()
|
||||
.eq(PreSettlementChangeRecord::getPreSettlementId, id)
|
||||
.eq(PreSettlementChangeRecord::getIsDeleted, 0)
|
||||
.orderByDesc(PreSettlementChangeRecord::getChangeTime));
|
||||
// 审批通过/驳回类不展示在变更记录中
|
||||
vo.setChangeRecords(changeRecords.stream()
|
||||
.filter(record -> !isApprovalChangeContent(record.getChangeContent()))
|
||||
.toList());
|
||||
vo.setPrintTemplates(resolvePrintTemplates(settlement));
|
||||
return vo;
|
||||
}
|
||||
@@ -349,6 +351,8 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
settlement.setLocalCurrency(LOCAL_CURRENCY);
|
||||
}
|
||||
fillContract(settlement, contract);
|
||||
settlement.setBusinessModel(limitText(request.getBusinessModel(), 64, "业务模式"));
|
||||
settlement.setTransportType(limitText(request.getTransportType(), 64, "运输方式"));
|
||||
settlement.setExchangeRateDate(request.getExchangeRateDate());
|
||||
settlement.setExchangeRate(request.getExchangeRate());
|
||||
settlement.setAttachmentsJson(request.getAttachmentsJson());
|
||||
@@ -441,7 +445,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
settlement.setCurrentNode("已驳回");
|
||||
settlement.setCurrentProcessor(AuthUtil.getUserName());
|
||||
updateById(settlement);
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "预结算审批驳回", reason);
|
||||
// 审批驳回不落变更记录
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1614,6 +1618,14 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
return source + " " + name;
|
||||
}
|
||||
|
||||
/** 审批通过、驳回等审批流转不落变更记录。 */
|
||||
private boolean isApprovalChangeContent(String content) {
|
||||
if (Func.isEmpty(content)) {
|
||||
return false;
|
||||
}
|
||||
return content.contains("审批通过") || content.contains("审批驳回") || "已驳回".equals(content);
|
||||
}
|
||||
|
||||
private void saveChange(Long settlementId, String changeType, Integer lineNo, String operationType,
|
||||
String content, String reason) {
|
||||
saveChange(settlementId, changeType, lineNo, operationType, content, reason, null, null);
|
||||
@@ -1621,6 +1633,9 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
|
||||
private void saveChange(Long settlementId, String changeType, Integer lineNo, String operationType,
|
||||
String content, String reason, Map<String, Object> beforeData, Map<String, Object> afterData) {
|
||||
if (isApprovalChangeContent(content)) {
|
||||
return;
|
||||
}
|
||||
PreSettlementChangeRecord record = new PreSettlementChangeRecord();
|
||||
record.setPreSettlementId(settlementId);
|
||||
record.setChangeType(changeType);
|
||||
|
||||
+45
-3
@@ -34,6 +34,7 @@ import org.springblade.core.tenant.annotation.TenantIgnore;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.transport.excel.ProjectApplyExcel;
|
||||
@@ -488,8 +489,7 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
|
||||
projectApply.setUndertakeDeptName(dept.getDeptName());
|
||||
}
|
||||
if (Func.isEmpty(projectApply.getBusinessDeptId())) {
|
||||
projectApply.setBusinessDeptId(firstUndertakeDeptId(projectApply.getUndertakeDeptId()));
|
||||
projectApply.setBusinessDeptName(firstUndertakeDeptName(projectApply.getUndertakeDeptName()));
|
||||
fillDefaultBusinessDept(projectApply);
|
||||
}
|
||||
if (Func.isEmpty(projectApply.getHandlerUserId())) {
|
||||
projectApply.setHandlerUserId(AuthUtil.getUserId());
|
||||
@@ -748,7 +748,7 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
|
||||
private void validateSubmit(ProjectApply projectApply) {
|
||||
validateDraft(projectApply);
|
||||
TransportBusinessSupport.validateRequired(projectApply.getProjectShortName(), "请输入项目简称");
|
||||
TransportBusinessSupport.validateRequired(projectApply.getBusinessDeptName(), "请选择业务部门");
|
||||
validateBusinessDept(projectApply);
|
||||
TransportBusinessSupport.validateRequired(projectApply.getUndertakeDeptName(), "请选择平台公司");
|
||||
TransportBusinessSupport.validateRequired(projectApply.getBusinessMode(), "请选择业务模式");
|
||||
TransportBusinessSupport.validateRequired(projectApply.getCustomerNames(), "请选择客户");
|
||||
@@ -1066,6 +1066,48 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
|
||||
return exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增时默认业务部门:取当前用户所属部门中第一个「是否业务部门=是」的组织。
|
||||
*/
|
||||
private void fillDefaultBusinessDept(ProjectApply projectApply) {
|
||||
List<Long> deptIds = Func.toLongList(AuthUtil.getDeptId());
|
||||
if (Func.isEmpty(deptIds)) {
|
||||
return;
|
||||
}
|
||||
for (Long deptId : deptIds) {
|
||||
if (Func.isEmpty(deptId) || deptId <= 0) {
|
||||
continue;
|
||||
}
|
||||
Dept dept = SysCache.getDept(deptId);
|
||||
if (dept != null && Objects.equals(dept.getIsBusinessDept(), 1)) {
|
||||
projectApply.setBusinessDeptId(dept.getId());
|
||||
projectApply.setBusinessDeptName(dept.getDeptName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验业务部门必须是组织中标记为「是否业务部门=是」的部门。
|
||||
*/
|
||||
private void validateBusinessDept(ProjectApply projectApply) {
|
||||
Long businessDeptId = projectApply.getBusinessDeptId();
|
||||
if (Func.isEmpty(businessDeptId)) {
|
||||
throw new ServiceException("请选择业务部门");
|
||||
}
|
||||
Dept dept = SysCache.getDept(businessDeptId);
|
||||
if (dept == null || Objects.equals(dept.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("业务部门不存在");
|
||||
}
|
||||
if (!Objects.equals(dept.getIsBusinessDept(), 1)) {
|
||||
throw new ServiceException("请选择组织中标记为业务部门的部门");
|
||||
}
|
||||
if (Func.isNotEmpty(dept.getDeptName())) {
|
||||
projectApply.setBusinessDeptName(dept.getDeptName());
|
||||
}
|
||||
TransportBusinessSupport.validateRequired(projectApply.getBusinessDeptName(), "请选择业务部门");
|
||||
}
|
||||
|
||||
private void normalizeUndertakeDept(ProjectApply projectApply) {
|
||||
if (projectApply == null) {
|
||||
return;
|
||||
|
||||
+4
-2
@@ -672,8 +672,10 @@ public class ReceivablePayableDetailServiceImpl
|
||||
throw new ServiceException("费用调整试算数据不能为空");
|
||||
}
|
||||
ReceivablePayableDetail detail = getExisting(request.getDetailId());
|
||||
if (!"pending".equals(detail.getSettlementStatus())) {
|
||||
throw new ServiceException("仅待结算明细允许调整试算");
|
||||
// 试算不落库,预结算/正式结算调整明细也需按合同计费方案重算运费及其他按量费用
|
||||
String settlementStatus = Func.toStr(detail.getSettlementStatus(), "pending");
|
||||
if (!List.of("pending", "pre_settled", "formal_settled").contains(settlementStatus)) {
|
||||
throw new ServiceException("当前结算状态不允许费用试算");
|
||||
}
|
||||
ReceivablePayableCargoFee fee = cargoFeeMapper.selectOne(
|
||||
Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
|
||||
|
||||
+9
-4
@@ -3,9 +3,9 @@ package org.springblade.transport.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
@@ -16,6 +16,7 @@ import org.springblade.transport.pojo.entity.RiskDisposal;
|
||||
import org.springblade.transport.pojo.vo.RiskDisposalBatchResultVO;
|
||||
import org.springblade.transport.pojo.vo.RiskDisposalVO;
|
||||
import org.springblade.transport.service.IRiskDisposalService;
|
||||
import org.springblade.transport.support.MpUserDataScopeSupport;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -25,12 +26,15 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RiskDisposalServiceImpl extends BaseServiceImpl<RiskDisposalMapper, RiskDisposal> implements IRiskDisposalService {
|
||||
|
||||
private static final String STATUS_PENDING = "pending";
|
||||
private static final String STATUS_PROCESSED = "processed";
|
||||
private static final String STATUS_IGNORED = "ignored";
|
||||
|
||||
private final MpUserDataScopeSupport mpUserDataScopeSupport;
|
||||
|
||||
@Override
|
||||
public IPage<RiskDisposalVO> selectPage(IPage<RiskDisposal> page, RiskDisposalVO query) {
|
||||
IPage<RiskDisposal> entityPage = page(page, buildQuery(query));
|
||||
@@ -84,15 +88,16 @@ public class RiskDisposalServiceImpl extends BaseServiceImpl<RiskDisposalMapper,
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<RiskDisposal> buildQuery(RiskDisposalVO query) {
|
||||
return Wrappers.<RiskDisposal>lambdaQuery()
|
||||
LambdaQueryWrapper<RiskDisposal> wrapper = Wrappers.<RiskDisposal>lambdaQuery()
|
||||
.eq(RiskDisposal::getIsDeleted, 0)
|
||||
.like(Func.isNotEmpty(query.getRiskNo()), RiskDisposal::getRiskNo, query.getRiskNo())
|
||||
.like(Func.isNotEmpty(query.getRuleName()), RiskDisposal::getRuleName, query.getRuleName())
|
||||
.like(Func.isNotEmpty(query.getWaybillNo()), RiskDisposal::getWaybillNo, query.getWaybillNo())
|
||||
.like(Func.isNotEmpty(query.getProjectName()), RiskDisposal::getProjectName, query.getProjectName())
|
||||
.eq(Func.isNotEmpty(query.getRiskLevel()), RiskDisposal::getRiskLevel, query.getRiskLevel())
|
||||
.eq(Func.isNotEmpty(query.getDisposalStatus()), RiskDisposal::getDisposalStatus, query.getDisposalStatus())
|
||||
.orderByDesc(RiskDisposal::getCreateTime);
|
||||
.eq(Func.isNotEmpty(query.getDisposalStatus()), RiskDisposal::getDisposalStatus, query.getDisposalStatus());
|
||||
mpUserDataScopeSupport.applyWaybillProjectScopeIfMpDispatcher(wrapper, RiskDisposal::getWaybillId);
|
||||
return wrapper.orderByDesc(RiskDisposal::getCreateTime);
|
||||
}
|
||||
|
||||
private RiskDisposalVO toVO(RiskDisposal entity) {
|
||||
|
||||
+2
-7
@@ -119,8 +119,9 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
|
||||
.orderByDesc(FormalSettlement::getCreateTime));
|
||||
Set<Long> tailPaymentSettlementIds = tailPaymentSettlementIds(rows.stream()
|
||||
.map(FormalSettlement::getId).filter(Objects::nonNull).toList());
|
||||
// 仅排除已完成收/付款、已发起尾款付款的正式结算单。
|
||||
// 注意:sourceType=「预结算合并」是由预结算转正式的常规来源,不是尾款单,不能据此过滤。
|
||||
rows = rows.stream().filter(row -> !isSettlementCompleted(row)
|
||||
&& !isTailSettlement(row)
|
||||
&& !tailPaymentSettlementIds.contains(row.getId())).toList();
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (FormalSettlement row : rows) {
|
||||
@@ -467,9 +468,6 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
|
||||
if (isSettlementCompleted(formal)) {
|
||||
throw new ServiceException("该正式结算单已完成收/付款,不能进行结算调整");
|
||||
}
|
||||
if (isTailSettlement(formal)) {
|
||||
throw new ServiceException("尾款结算正式结算单不能进行结算调整");
|
||||
}
|
||||
if (hasTailPayment(formal.getId())) {
|
||||
throw new ServiceException("该正式结算单已发起尾款结算,不能进行结算调整");
|
||||
}
|
||||
@@ -480,9 +478,6 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
|
||||
|| (money(formal.getSettlementAmount()).signum() > 0
|
||||
&& money(formal.getPaidAmount()).compareTo(money(formal.getSettlementAmount())) >= 0);
|
||||
}
|
||||
private boolean isTailSettlement(FormalSettlement formal) {
|
||||
return formal != null && "预结算合并".equals(formal.getSourceType());
|
||||
}
|
||||
private boolean hasTailPayment(Long formalSettlementId) {
|
||||
return formalSettlementId != null && tailPaymentSettlementIds(List.of(formalSettlementId))
|
||||
.contains(formalSettlementId);
|
||||
|
||||
+71
-24
@@ -76,6 +76,7 @@ import org.springblade.transport.service.IReceivablePayableDetailService;
|
||||
import org.springblade.transport.service.ISettlementImportBatchService;
|
||||
import org.springblade.transport.service.IWaybillImportBatchService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -220,12 +221,14 @@ public class SettlementImportBatchServiceImpl
|
||||
rows = buildRowsFromWaybills(listBatchWaybills(batch));
|
||||
}
|
||||
vo.setRows(rows);
|
||||
// 费用列名优先取库字段;若历史数据解析失败或缺失,从明细 feeItems 回补,避免草稿再打开丢列
|
||||
vo.setFeeItemNames(mergeFeeItemNames(vo.getFeeItemNames(), rows));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportTemplate(HttpServletResponse response) {
|
||||
SettlementImportTemplateExporter.export(response, loadTransportTypeLabels(), loadEnabledFeeItemNames());
|
||||
SettlementImportTemplateExporter.export(response, loadTransportTypeLabels());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -867,21 +870,56 @@ public class SettlementImportBatchServiceImpl
|
||||
}
|
||||
|
||||
private List<String> resolveFeeItemNames(SettlementImportBatchRequest request) {
|
||||
LinkedHashSet<String> names = new LinkedHashSet<>();
|
||||
if (request.getFeeItemNames() != null) {
|
||||
return request.getFeeItemNames().stream().filter(Func::isNotEmpty).distinct().toList();
|
||||
request.getFeeItemNames().stream()
|
||||
.filter(Func::isNotEmpty)
|
||||
.map(String::trim)
|
||||
.filter(Func::isNotEmpty)
|
||||
.forEach(names::add);
|
||||
}
|
||||
Set<String> names = new LinkedHashSet<>();
|
||||
if (Func.isNotEmpty(request.getRows())) {
|
||||
for (Map<String, Object> row : request.getRows()) {
|
||||
Object feeItems = row.get("feeItems");
|
||||
if (feeItems instanceof Map<?, ?> raw) {
|
||||
raw.keySet().stream().filter(Objects::nonNull).map(String::valueOf).forEach(names::add);
|
||||
}
|
||||
collectFeeItemNamesFromRow(row, names);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(names);
|
||||
}
|
||||
|
||||
/** 合并请求/库中的费用列名与明细行 feeItems 的键,保持插入顺序。 */
|
||||
private List<String> mergeFeeItemNames(List<String> preferred, List<Map<String, Object>> rows) {
|
||||
LinkedHashSet<String> names = new LinkedHashSet<>();
|
||||
if (Func.isNotEmpty(preferred)) {
|
||||
preferred.stream()
|
||||
.filter(Func::isNotEmpty)
|
||||
.map(String::trim)
|
||||
.filter(Func::isNotEmpty)
|
||||
.forEach(names::add);
|
||||
}
|
||||
if (Func.isNotEmpty(rows)) {
|
||||
for (Map<String, Object> row : rows) {
|
||||
collectFeeItemNamesFromRow(row, names);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(names);
|
||||
}
|
||||
|
||||
private void collectFeeItemNamesFromRow(Map<String, Object> row, Set<String> names) {
|
||||
if (row == null || names == null) {
|
||||
return;
|
||||
}
|
||||
Object feeItems = row.get("feeItems");
|
||||
if (!(feeItems instanceof Map<?, ?> raw)) {
|
||||
return;
|
||||
}
|
||||
raw.keySet().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::valueOf)
|
||||
.map(String::trim)
|
||||
.filter(Func::isNotEmpty)
|
||||
.forEach(names::add);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildFeeRows(List<Map<String, Object>> rows, List<Waybill> waybills) {
|
||||
List<Map<String, Object>> primaryRows = primaryRows(rows);
|
||||
List<Map<String, Object>> feeRows = new ArrayList<>();
|
||||
@@ -1061,12 +1099,35 @@ public class SettlementImportBatchServiceImpl
|
||||
vo.setSettlementTypeName(settlementTypeName(batch.getSettlementType()));
|
||||
vo.setSettlementBillTypeName(settlementBillTypeName(batch.getSettlementBillType()));
|
||||
vo.setWaybillModeName(waybillModeName(batch.getWaybillMode()));
|
||||
if (Func.isNotEmpty(batch.getFeeItemNamesJson())) {
|
||||
vo.setFeeItemNames(JsonUtil.parseArray(batch.getFeeItemNamesJson(), String.class));
|
||||
}
|
||||
vo.setFeeItemNames(parseFeeItemNamesJson(batch.getFeeItemNamesJson()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析费用列名 JSON。
|
||||
* 注意:不能用 JsonUtil.parseArray(..., String.class),其内部按 List<Map> 解析,字符串数组会失败导致草稿再打开丢费用列。
|
||||
*/
|
||||
private List<String> parseFeeItemNamesJson(String feeItemNamesJson) {
|
||||
if (Func.isEmpty(feeItemNamesJson)) {
|
||||
return List.of();
|
||||
}
|
||||
try {
|
||||
List<String> names = JsonUtil.parse(feeItemNamesJson, new TypeReference<List<String>>() {
|
||||
});
|
||||
if (names == null || names.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return names.stream()
|
||||
.filter(Func::isNotEmpty)
|
||||
.map(String::trim)
|
||||
.filter(Func::isNotEmpty)
|
||||
.distinct()
|
||||
.toList();
|
||||
} catch (Exception exception) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
private String importStatusName(String status) {
|
||||
if (STATUS_DRAFT.equals(status)) {
|
||||
return "草稿";
|
||||
@@ -1350,20 +1411,6 @@ public class SettlementImportBatchServiceImpl
|
||||
return new ArrayList<>(labels);
|
||||
}
|
||||
|
||||
private List<String> loadEnabledFeeItemNames() {
|
||||
R<List<FeeItem>> response = sysClient.getFeeItems(false);
|
||||
if (response == null || !response.isSuccess() || response.getData() == null) {
|
||||
return List.of();
|
||||
}
|
||||
return response.getData().stream()
|
||||
.filter(item -> item != null && (item.getStatus() == null || Objects.equals(item.getStatus(), 1)))
|
||||
.map(FeeItem::getName)
|
||||
.filter(Func::isNotEmpty)
|
||||
.map(String::trim)
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/** 历史已完成批次无 rows_json 时,按关联运单回填查看明细。 */
|
||||
private List<Map<String, Object>> buildRowsFromWaybills(List<Waybill> waybills) {
|
||||
if (Func.isEmpty(waybills)) {
|
||||
|
||||
+7
@@ -601,6 +601,13 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
waybill.setContractId(plan.getContractId());
|
||||
waybill.setContractName(plan.getContractName());
|
||||
waybill.setCustomerName(plan.getCustomerName());
|
||||
// 计划单若来自总单,调度运单须继承总单号与段号,供总单进度汇总
|
||||
if (Func.isEmpty(waybill.getMasterNo()) && Func.isNotEmpty(plan.getMasterNo())) {
|
||||
waybill.setMasterNo(plan.getMasterNo());
|
||||
}
|
||||
if (Func.isEmpty(waybill.getRelationNo()) && Func.isNotEmpty(plan.getRelationNo())) {
|
||||
waybill.setRelationNo(plan.getRelationNo());
|
||||
}
|
||||
if (Func.isEmpty(waybill.getDepartureAddress())) {
|
||||
waybill.setDepartureAddressId(plan.getDepartureAddressId());
|
||||
waybill.setDepartureName(plan.getDepartureName());
|
||||
|
||||
+62
-44
@@ -205,6 +205,8 @@ public class TransportReconciliationServiceImpl
|
||||
}
|
||||
List<TransportReconciliationInternal> internals = internalRows(id);
|
||||
List<TransportReconciliationExternal> externals = externalRows(id);
|
||||
internals.forEach(this::clearOptionalDecimalSentinels);
|
||||
externals.forEach(this::normalizeExternalOptionalDecimals);
|
||||
vo.setInternalDetails(internals);
|
||||
vo.setExternalDetails(externals);
|
||||
vo.setFeeSummary(feeSummary(vo.getFormalSettlementId(), externals));
|
||||
@@ -298,6 +300,7 @@ public class TransportReconciliationServiceImpl
|
||||
List<TransportReconciliationExternal> validExternals = new ArrayList<>();
|
||||
for (int index = 0; index < rows.size(); index++) {
|
||||
VehicleReconciliationExcel row = rows.get(index);
|
||||
normalizeImportVehicleDecimals(row);
|
||||
List<String> validationErrors = validateImportVehicle(row);
|
||||
if (Func.isNotEmpty(validationErrors)) {
|
||||
// 原因同时写回原行:异常携带的是原表 rows,
|
||||
@@ -342,6 +345,7 @@ public class TransportReconciliationServiceImpl
|
||||
external.setExternalLineNo(index + 1);
|
||||
external.setActualDepartureTime(parseTime(row.getActualDepartureTime(), "实际发货时间"));
|
||||
external.setActualCompletionTime(parseTimeNullable(row.getActualCompletionTime(), "实际完成时间"));
|
||||
normalizeExternalOptionalDecimals(external);
|
||||
Map<String, BigDecimal> feeItems = row.getFeeItems();
|
||||
if (feeItems == null || feeItems.isEmpty()) {
|
||||
feeItems = new HashMap<>();
|
||||
@@ -427,6 +431,7 @@ public class TransportReconciliationServiceImpl
|
||||
List<TransportReconciliationExternal> validExternals = new ArrayList<>();
|
||||
for (int index = 0; index < rows.size(); index++) {
|
||||
CargoReconciliationExcel row = rows.get(index);
|
||||
normalizeImportCargoDecimals(row);
|
||||
List<String> validationErrors = validateImportCargo(row);
|
||||
if (Func.isNotEmpty(validationErrors)) {
|
||||
// 原因同时写回原行:异常携带的是原表 rows,
|
||||
@@ -471,6 +476,7 @@ public class TransportReconciliationServiceImpl
|
||||
external.setExternalLineNo(index + 1);
|
||||
external.setActualDepartureTime(parseTime(row.getActualDepartureTime(), "实际发货时间"));
|
||||
external.setActualCompletionTime(parseTimeNullable(row.getActualCompletionTime(), "实际完成时间"));
|
||||
normalizeExternalOptionalDecimals(external);
|
||||
Map<String, BigDecimal> feeItems = row.getFeeItems();
|
||||
if (feeItems == null || feeItems.isEmpty()) {
|
||||
feeItems = new LinkedHashMap<>();
|
||||
@@ -729,10 +735,12 @@ public class TransportReconciliationServiceImpl
|
||||
target.setModel(source.getModel());
|
||||
target.setTransportQuantity(nonNegative(source.getTransportQuantity(), "运输量"));
|
||||
target.setQuantityUnit(source.getQuantityUnit());
|
||||
target.setMileage(source.getMileage());
|
||||
target.setMileage(normalizeOptionalDecimal(source.getMileage()));
|
||||
target.setBatchNo(source.getBatchNo());
|
||||
target.setUnitPrice(source.getUnitPrice() == null ? null : nonNegative(source.getUnitPrice(), "运输单价"));
|
||||
target.setFreightAmount(source.getFreightAmount() == null ? null : nonNegative(source.getFreightAmount(), "运输费"));
|
||||
BigDecimal unitPrice = normalizeOptionalDecimal(source.getUnitPrice());
|
||||
target.setUnitPrice(unitPrice == null ? null : nonNegative(unitPrice, "运输单价"));
|
||||
BigDecimal freightAmount = normalizeOptionalDecimal(source.getFreightAmount());
|
||||
target.setFreightAmount(freightAmount == null ? null : nonNegative(freightAmount, "运输费"));
|
||||
target.setFeeItemsJson(source.getFeeItemsJson());
|
||||
target.setSettlementAmount(nonNegative(source.getSettlementAmount(), "结算费用合计"));
|
||||
target.setMatchedInternalId(source.getMatchedInternalId());
|
||||
@@ -824,7 +832,6 @@ public class TransportReconciliationServiceImpl
|
||||
applyAmount(bill, internal, external);
|
||||
copyExternalToInternal(internal, external);
|
||||
internalMapper.updateById(internal);
|
||||
updateWaybill(internal, external);
|
||||
}
|
||||
refreshFormalSummaryFees(bill.getFormalSettlementId());
|
||||
recalculateSettlement(bill);
|
||||
@@ -867,50 +874,14 @@ public class TransportReconciliationServiceImpl
|
||||
internal.setModel(external.getModel());
|
||||
internal.setTransportQuantity(external.getTransportQuantity());
|
||||
internal.setQuantityUnit(external.getQuantityUnit());
|
||||
internal.setMileage(external.getMileage());
|
||||
internal.setMileage(normalizeOptionalDecimal(external.getMileage()));
|
||||
internal.setBatchNo(external.getBatchNo());
|
||||
internal.setUnitPrice(external.getUnitPrice());
|
||||
internal.setFreightAmount(external.getFreightAmount());
|
||||
internal.setUnitPrice(normalizeOptionalDecimal(external.getUnitPrice()));
|
||||
internal.setFreightAmount(normalizeOptionalDecimal(external.getFreightAmount()));
|
||||
internal.setFeeItemsJson(external.getFeeItemsJson());
|
||||
internal.setSettlementAmount(external.getSettlementAmount());
|
||||
}
|
||||
|
||||
private void updateWaybill(TransportReconciliationInternal internal,
|
||||
TransportReconciliationExternal external) {
|
||||
Long waybillId = null;
|
||||
FormalSettlementDetail detail = internal.getFormalSettlementDetailId() == null ? null
|
||||
: formalDetailMapper.selectById(internal.getFormalSettlementDetailId());
|
||||
if (detail != null) waybillId = detail.getWaybillId();
|
||||
if (waybillId == null && internal.getSourceDetailId() != null) {
|
||||
ReceivablePayableDetail source = receivablePayableMapper.selectById(internal.getSourceDetailId());
|
||||
if (source != null) waybillId = source.getWaybillId();
|
||||
}
|
||||
Waybill waybill = waybillId == null ? null : waybillMapper.selectById(waybillId);
|
||||
if (waybill == null && Func.isNotEmpty(internal.getWaybillNo())) {
|
||||
waybill = waybillMapper.selectOne(Wrappers.<Waybill>lambdaQuery()
|
||||
.eq(Waybill::getWaybillNo, internal.getWaybillNo()));
|
||||
}
|
||||
if (waybill == null) return;
|
||||
waybill.setVehicleNo(external.getVehicleNo());
|
||||
waybill.setDepartureAddress(external.getDepartureAddress());
|
||||
waybill.setArrivalAddress(external.getArrivalAddress());
|
||||
waybill.setStartDate(external.getActualDepartureTime() == null ? null : external.getActualDepartureTime().toLocalDate());
|
||||
waybill.setEndDate(external.getActualCompletionTime() == null ? null : external.getActualCompletionTime().toLocalDate());
|
||||
waybill.setTransportType(external.getTransportType());
|
||||
waybill.setCargoName(external.getCargoName());
|
||||
waybill.setCargoType(external.getCargoType());
|
||||
waybill.setSpecification(external.getSpecification());
|
||||
waybill.setModel(external.getModel());
|
||||
waybill.setQuantity(external.getTransportQuantity());
|
||||
waybill.setQuantityUnit(external.getQuantityUnit());
|
||||
waybill.setMileage(external.getMileage());
|
||||
waybill.setBatchNo(external.getBatchNo());
|
||||
waybill.setUnitPrice(external.getUnitPrice());
|
||||
waybill.setFreightJson(external.getFeeItemsJson());
|
||||
waybill.setOtherFeeTotal(money(external.getSettlementAmount()).subtract(money(external.getFreightAmount())).max(BigDecimal.ZERO));
|
||||
waybillMapper.updateById(waybill);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void complete(Long id) {
|
||||
@@ -1309,7 +1280,8 @@ public class TransportReconciliationServiceImpl
|
||||
bill.setInternalBillCount(internals.size()); bill.setExternalBillCount(externals.size());
|
||||
int internalUnmatched = (int) internals.stream().filter(item -> !MATCHED.equals(item.getMatchResult())).count();
|
||||
int externalUnmatched = (int) externals.stream().filter(item -> !MATCHED.equals(item.getMatchStatus())).count();
|
||||
bill.setMatchedCount(matched); bill.setUnmatchedCount(internalUnmatched + externalUnmatched);
|
||||
// 无法匹配按内部账单条数统计,不叠加外部未匹配数
|
||||
bill.setMatchedCount(matched); bill.setUnmatchedCount(internalUnmatched);
|
||||
bill.setDifferenceCount(Math.abs(internals.size() - externals.size()) + Math.min(internalUnmatched, externalUnmatched));
|
||||
bill.setInternalQuantity(sumInternalQuantity(internals)); bill.setExternalQuantity(sumExternalQuantity(externals));
|
||||
bill.setDifferenceQuantity(bill.getInternalQuantity().subtract(bill.getExternalQuantity()).abs());
|
||||
@@ -1507,6 +1479,52 @@ public class TransportReconciliationServiceImpl
|
||||
}
|
||||
private BigDecimal money(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
|
||||
private BigDecimal nonNegative(BigDecimal value, String field) { if (value == null || value.compareTo(BigDecimal.ZERO) < 0) throw new ServiceException(field + "不能小于0"); return value; }
|
||||
|
||||
/**
|
||||
* BladeX 数字空值哨兵 -1 与 Excel 未填统一归一为 null,避免导入后单价显示成 -1。
|
||||
*/
|
||||
private BigDecimal normalizeOptionalDecimal(BigDecimal value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return value.compareTo(BigDecimal.ONE.negate()) == 0 ? null : value;
|
||||
}
|
||||
|
||||
private void normalizeImportVehicleDecimals(VehicleReconciliationExcel row) {
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
row.setUnitPrice(normalizeOptionalDecimal(row.getUnitPrice()));
|
||||
row.setMileage(normalizeOptionalDecimal(row.getMileage()));
|
||||
row.setFreightAmount(normalizeOptionalDecimal(row.getFreightAmount()));
|
||||
}
|
||||
|
||||
private void normalizeImportCargoDecimals(CargoReconciliationExcel row) {
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
row.setUnitPrice(normalizeOptionalDecimal(row.getUnitPrice()));
|
||||
row.setMileage(normalizeOptionalDecimal(row.getMileage()));
|
||||
row.setFreightAmount(normalizeOptionalDecimal(row.getFreightAmount()));
|
||||
}
|
||||
|
||||
private void normalizeExternalOptionalDecimals(TransportReconciliationExternal external) {
|
||||
if (external == null) {
|
||||
return;
|
||||
}
|
||||
external.setUnitPrice(normalizeOptionalDecimal(external.getUnitPrice()));
|
||||
external.setMileage(normalizeOptionalDecimal(external.getMileage()));
|
||||
external.setFreightAmount(normalizeOptionalDecimal(external.getFreightAmount()));
|
||||
}
|
||||
|
||||
private void clearOptionalDecimalSentinels(TransportReconciliationInternal internal) {
|
||||
if (internal == null) {
|
||||
return;
|
||||
}
|
||||
internal.setUnitPrice(normalizeOptionalDecimal(internal.getUnitPrice()));
|
||||
internal.setMileage(normalizeOptionalDecimal(internal.getMileage()));
|
||||
internal.setFreightAmount(normalizeOptionalDecimal(internal.getFreightAmount()));
|
||||
}
|
||||
private String limit(String value, int max) { if (value != null && value.length() > max) throw new ServiceException("备注不能超过" + max + "个字"); return value; }
|
||||
private BigDecimal sumInternalQuantity(List<TransportReconciliationInternal> rows) { return rows.stream().map(TransportReconciliationInternal::getTransportQuantity).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||
private BigDecimal sumExternalQuantity(List<TransportReconciliationExternal> rows) { return rows.stream().map(TransportReconciliationExternal::getTransportQuantity).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||
|
||||
+14
@@ -248,6 +248,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
.or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED));
|
||||
applyScope(wrapper, condition);
|
||||
applyDeptScope(wrapper, condition);
|
||||
applyProjectScope(wrapper, condition);
|
||||
applyOrgTypeFilter(wrapper, condition);
|
||||
applyKeyword(wrapper, condition.getKeyword());
|
||||
List<Waybill> rows = waybillService.list(wrapper);
|
||||
@@ -268,6 +269,18 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
wrapper.in(Waybill::getDeptId, condition.getDeptIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目收口:条件里给了项目 ID 集合时只出这些项目的运单行,空集合表示不过滤。
|
||||
* <p>
|
||||
* 小程序调度端先按用户数据权限解析可见项目(业务部门在范围内),再把项目 ID 传进来。
|
||||
*/
|
||||
private void applyProjectScope(LambdaQueryWrapper<Waybill> wrapper, TripQueryVO condition) {
|
||||
if (Func.isEmpty(condition.getProjectIds())) {
|
||||
return;
|
||||
}
|
||||
wrapper.in(Waybill::getProjectId, condition.getProjectIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* 运输组织类型按子单行筛:它在同一趟车里必然是同一个值(配载的每张子单带同一个配载单号,
|
||||
* 未配载的每张自成一趟车),所以这一层不会把一趟车拆开。
|
||||
@@ -807,6 +820,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
copy.setDriverPhone(source.getDriverPhone());
|
||||
copy.setAllDrivers(source.isAllDrivers());
|
||||
copy.setDeptIds(source.getDeptIds());
|
||||
copy.setProjectIds(source.getProjectIds());
|
||||
copy.setKeyword(source.getKeyword());
|
||||
copy.setTransportOrgType(source.getTransportOrgType());
|
||||
copy.setException(source.getException());
|
||||
|
||||
+57
-3
@@ -36,7 +36,10 @@ import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.wrapper.VoucherManageWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.GetPresignedObjectUrlArgs;
|
||||
@@ -86,6 +89,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
private final LoadingManageMapper loadingManageMapper;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final ObjectProvider<MinioClient> minioClientProvider;
|
||||
private final TransactionTemplate requiresNewTransactionTemplate;
|
||||
private final String minioBucketName;
|
||||
private final String minioRootDirectory;
|
||||
private final String minioTransformEndpoint;
|
||||
@@ -98,6 +102,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
VoucherImageMapper voucherImageMapper, VoucherFileMapper voucherFileMapper,
|
||||
LoadingManageMapper loadingManageMapper,
|
||||
ApplicationEventPublisher eventPublisher, ObjectProvider<MinioClient> minioClientProvider,
|
||||
PlatformTransactionManager transactionManager,
|
||||
@Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}") String minioBucketName,
|
||||
@Value("${file.storage.minio.root-directory:${minio.root-directory:}}") String minioRootDirectory,
|
||||
@Value("${file.storage.minio.transform-endpoint:${minio.transform-endpoint:}}") String minioTransformEndpoint,
|
||||
@@ -111,6 +116,9 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
this.loadingManageMapper = loadingManageMapper;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.minioClientProvider = minioClientProvider;
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
this.requiresNewTransactionTemplate = transactionTemplate;
|
||||
this.minioBucketName = minioBucketName;
|
||||
this.minioRootDirectory = minioRootDirectory;
|
||||
this.minioTransformEndpoint = minioTransformEndpoint;
|
||||
@@ -1156,8 +1164,10 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
int imageCount = 0;
|
||||
int matchedImageCount = 0;
|
||||
Set<Long> relatedWaybillIds = new HashSet<>();
|
||||
assertParsableZipArchive(voucher.getFileName(), voucher.getFileUrl());
|
||||
Path archivePath = downloadSourceFile(voucher.getFileUrl());
|
||||
try {
|
||||
assertZipMagic(archivePath);
|
||||
Charset archiveCharset = detectArchiveCharset(archivePath);
|
||||
log.info("[凭证处理] 已识别压缩包文件名编码 voucherId={}, charset={}", voucherId, archiveCharset.name());
|
||||
try (InputStream source = Files.newInputStream(archivePath);
|
||||
@@ -1235,6 +1245,9 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fileCount == 0) {
|
||||
throw new ServiceException("压缩包无法解析或内没有找到有效文件,请检查后重新上传");
|
||||
}
|
||||
log.info("[凭证处理] 进度 90%:文件解压上传完成 voucherId={}, fileCount={}, imageCount={}, matchedImageCount={}",
|
||||
voucherId, fileCount, imageCount, matchedImageCount);
|
||||
VoucherManage update = new VoucherManage();
|
||||
@@ -1256,12 +1269,53 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
deleteTempArchive(archivePath);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
// 独立事务落「处理失败」,避免本方法事务回滚把失败状态冲掉后一直卡在「处理中」。
|
||||
markProcessFailed(voucher.getId());
|
||||
log.error("凭证压缩包处理失败 voucherId:{}", voucherId, exception);
|
||||
// 不向上抛:解析失败属业务终态,MQ 消费确认后不再重试。
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在新事务中写入处理失败,确保即使当前解析事务回滚,列表也能看到失败态并允许重新上传。
|
||||
*/
|
||||
private void markProcessFailed(Long voucherId) {
|
||||
if (voucherId == null) {
|
||||
return;
|
||||
}
|
||||
requiresNewTransactionTemplate.executeWithoutResult(status -> {
|
||||
VoucherManage update = new VoucherManage();
|
||||
update.setId(voucher.getId());
|
||||
update.setId(voucherId);
|
||||
update.setProcessStatus("处理失败");
|
||||
updateById(update);
|
||||
log.error("凭证压缩包处理失败 voucherId:{}", voucherId, exception);
|
||||
throw new ServiceException("凭证压缩包处理失败");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传后缀为 7z 或非 zip 时直接判失败:后台仅支持标准 zip 解压。
|
||||
*/
|
||||
private void assertParsableZipArchive(String fileName, String fileUrl) {
|
||||
String lowerName = Func.toStr(fileName, "").trim().toLowerCase(Locale.ROOT);
|
||||
String lowerUrl = Func.toStr(fileUrl, "").trim().toLowerCase(Locale.ROOT);
|
||||
if (lowerName.endsWith(".7z") || lowerUrl.contains(".7z")) {
|
||||
throw new ServiceException("暂不支持 7z 压缩包,请转换为 zip 后重新上传");
|
||||
}
|
||||
boolean looksLikeZip = lowerName.endsWith(".zip") || lowerUrl.contains(".zip");
|
||||
if (!looksLikeZip) {
|
||||
throw new ServiceException("仅支持 zip 压缩包,当前文件无法解析");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验本地临时文件是否为 zip(PK 头),排除随意改后缀的文档。
|
||||
*/
|
||||
private void assertZipMagic(Path archivePath) throws Exception {
|
||||
byte[] header = new byte[4];
|
||||
try (InputStream inputStream = Files.newInputStream(archivePath)) {
|
||||
int read = inputStream.read(header);
|
||||
if (read < 4 || header[0] != 'P' || header[1] != 'K') {
|
||||
throw new ServiceException("文件不是有效的 zip 压缩包,无法解析");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -1084,8 +1084,9 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
waybill.setProcessJson(oldRecord.getProcessJson());
|
||||
}
|
||||
} else {
|
||||
// 新建手工运单不应带配载号;总单/计划调度写入的 masterNo、relationNo 必须保留,
|
||||
// 否则总单进度按 masterNo 汇总会一直显示「已调度 0」。
|
||||
waybill.setLoadingNo(null);
|
||||
waybill.setMasterNo(null);
|
||||
waybill.setMileageRemark(null);
|
||||
}
|
||||
// 新建若已带业务状态(如复制提交已完成)则在过程校正后回写,保证与原运单一致;
|
||||
@@ -1623,7 +1624,12 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
BusinessRemoveResultVO result = new BusinessRemoveResultVO();
|
||||
for (Waybill waybill : listByIds(idList)) {
|
||||
try {
|
||||
complete(waybill.getId());
|
||||
// 待回单确认走确认回单校验(打卡节点 + 未了结异常);其它状态仍走普通完成
|
||||
if (WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM.equals(waybill.getBusinessStatus())) {
|
||||
confirmReturnReceipt(waybill.getId());
|
||||
} else {
|
||||
complete(waybill.getId());
|
||||
}
|
||||
result.setSuccessCount(result.getSuccessCount() + 1);
|
||||
} catch (Exception exception) {
|
||||
log.error("批量完成运单失败,waybillId:{}, waybillNo:{}, failureReason:{}",
|
||||
|
||||
+13
-10
@@ -29,7 +29,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 可配载准入判据:公路 + 待执行 + 未关联其它配载单。
|
||||
* 可配载准入判据:公路 + 待执行/进行中 + 未关联其它配载单。
|
||||
* <p>
|
||||
* 接单状态不进判据:拒单票照样可配载。配载提交会把它的接单事实清成待接单,
|
||||
* 否则新司机看不到这张票——司机端入口一律排除已拒绝的运单。
|
||||
@@ -38,8 +38,11 @@ import java.util.Locale;
|
||||
*/
|
||||
public final class LoadableAdmissionSupport {
|
||||
|
||||
/** 可配载只放行待执行:已完成、已取消、草稿与调度中间态一律不可配载 */
|
||||
public static final String LOADABLE_BUSINESS_STATUS = WaybillProcessSupport.STATUS_PENDING;
|
||||
/** 可配载业务状态:待执行、进行中;已完成、已取消、草稿与调度中间态一律不可配载 */
|
||||
public static final List<String> LOADABLE_BUSINESS_STATUSES = List.of(
|
||||
WaybillProcessSupport.STATUS_PENDING,
|
||||
WaybillProcessSupport.STATUS_RUNNING
|
||||
);
|
||||
|
||||
/**
|
||||
* 公路运输方式的同义写法,按「包含」比对,与前端运单管理页的识别口径一致。
|
||||
@@ -56,7 +59,7 @@ public final class LoadableAdmissionSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* 不可配载的原因,按判定顺序排列:公路 → 待执行 → 未关联其它配载单。
|
||||
* 不可配载的原因,按判定顺序排列:公路 → 待执行/进行中 → 未关联其它配载单。
|
||||
* <p>
|
||||
* 判据只产出原因,中文文案由调用方映射——面向用户的措辞不进这一层。
|
||||
*/
|
||||
@@ -65,8 +68,8 @@ public final class LoadableAdmissionSupport {
|
||||
/** 运输方式不是公路 */
|
||||
NOT_ROAD,
|
||||
|
||||
/** 业务状态不是待执行 */
|
||||
NOT_PENDING,
|
||||
/** 业务状态不是待执行或进行中 */
|
||||
NOT_LOADABLE_STATUS,
|
||||
|
||||
/** 已关联其它配载单 */
|
||||
ON_OTHER_MANIFEST
|
||||
@@ -119,8 +122,8 @@ public final class LoadableAdmissionSupport {
|
||||
if (waybill == null || !isRoadTransport(waybill.getTransportType())) {
|
||||
return UnloadableReason.NOT_ROAD;
|
||||
}
|
||||
if (!LOADABLE_BUSINESS_STATUS.equals(text(waybill.getBusinessStatus()))) {
|
||||
return UnloadableReason.NOT_PENDING;
|
||||
if (!LOADABLE_BUSINESS_STATUSES.contains(text(waybill.getBusinessStatus()))) {
|
||||
return UnloadableReason.NOT_LOADABLE_STATUS;
|
||||
}
|
||||
String loadingNo = text(waybill.getLoadingNo());
|
||||
if (loadingNo.isEmpty() || loadingNo.equals(text(ownLoadingNo))) {
|
||||
@@ -148,13 +151,13 @@ public final class LoadableAdmissionSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* 把整条可配载判据加到运单查询上:公路 + 待执行 + 未配载。
|
||||
* 把整条可配载判据加到运单查询上:公路 + 待执行/进行中 + 未配载。
|
||||
*
|
||||
* @param wrapper 运单列表查询条件
|
||||
*/
|
||||
public static void applyLoadableConditions(LambdaQueryWrapper<Waybill> wrapper) {
|
||||
applyRoadTransportCondition(wrapper);
|
||||
wrapper.eq(Waybill::getBusinessStatus, LOADABLE_BUSINESS_STATUS);
|
||||
wrapper.in(Waybill::getBusinessStatus, LOADABLE_BUSINESS_STATUSES);
|
||||
wrapper.and(nested -> nested.isNull(Waybill::getLoadingNo).or().eq(Waybill::getLoadingNo, ""));
|
||||
}
|
||||
|
||||
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* 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.support;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springblade.system.pojo.vo.MpUserDataScopeVO;
|
||||
import org.springblade.system.utils.MpUserAuth;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springblade.transport.service.IProjectApplyService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 小程序调度端数据权限。
|
||||
* <p>
|
||||
* 口径:所属组织 + 数据权限范围 + 数据层级范围 → 可见组织 →
|
||||
* 业务部门落在可见组织内的项目 → 再按这些项目收口运单/异常/风险。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MpUserDataScopeSupport {
|
||||
|
||||
/**
|
||||
* 无权限时的占位 ID:查询 in(-1) 保证查不到数据(空列表在运次查询里表示不过滤)。
|
||||
*/
|
||||
private static final Long NONE_ID = -1L;
|
||||
|
||||
private final IMpUserClient mpUserClient;
|
||||
private final IProjectApplyService projectApplyService;
|
||||
|
||||
/**
|
||||
* 当前登录是否为小程序调度端用户(含 admin 身份)
|
||||
*/
|
||||
public boolean isMpDispatcher() {
|
||||
return MpUserAuth.isMpUser()
|
||||
&& MpUserIdentity.has(AuthUtil.getUserRole(), MpUserIdentity.ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前小程序用户可见组织;非小程序用户返回 null(调用方走原逻辑)。
|
||||
*/
|
||||
public MpUserDataScopeVO resolveCurrentMpUserScope() {
|
||||
if (!MpUserAuth.isMpUser()) {
|
||||
return null;
|
||||
}
|
||||
return resolveByUserId(AuthUtil.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析指定小程序用户可见组织;Feign 失败时按无权限处理。
|
||||
*/
|
||||
public MpUserDataScopeVO resolveByUserId(Long userId) {
|
||||
if (Func.isEmpty(userId) || userId <= 0) {
|
||||
return MpUserDataScopeVO.empty();
|
||||
}
|
||||
R<MpUserDataScopeVO> result = mpUserClient.resolveDataScope(userId);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
||||
return MpUserDataScopeVO.empty();
|
||||
}
|
||||
return result.getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为查询用的组织 ID 约定:空=不过滤;非空=in;无权限=[-1]。
|
||||
*/
|
||||
public List<Long> toQueryDeptIds(MpUserDataScopeVO scope) {
|
||||
if (scope == null || scope.isAllDept()) {
|
||||
return List.of();
|
||||
}
|
||||
if (Func.isEmpty(scope.getDeptIds())) {
|
||||
return List.of(NONE_ID);
|
||||
}
|
||||
return scope.getDeptIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前小程序用户可见项目 ID(业务部门落在数据权限组织范围内)。
|
||||
* <ul>
|
||||
* <li>空列表 = 全部组织 / 不过滤</li>
|
||||
* <li>非空 = 仅这些项目</li>
|
||||
* <li>无权限或范围内无项目 = [-1]</li>
|
||||
* </ul>
|
||||
* 非小程序用户返回 null。
|
||||
*/
|
||||
public List<Long> resolveVisibleProjectIdsForQuery() {
|
||||
MpUserDataScopeVO scope = resolveCurrentMpUserScope();
|
||||
if (scope == null) {
|
||||
return null;
|
||||
}
|
||||
return resolveProjectIds(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按数据权限结果解析可见项目 ID。
|
||||
*/
|
||||
public List<Long> resolveProjectIds(MpUserDataScopeVO scope) {
|
||||
if (scope == null || scope.isAllDept()) {
|
||||
return List.of();
|
||||
}
|
||||
List<Long> deptIds = scope.getDeptIds();
|
||||
if (Func.isEmpty(deptIds)) {
|
||||
return List.of(NONE_ID);
|
||||
}
|
||||
List<ProjectApply> projectList = projectApplyService.list(Wrappers.<ProjectApply>lambdaQuery()
|
||||
.select(ProjectApply::getId)
|
||||
.in(ProjectApply::getBusinessDeptId, deptIds));
|
||||
if (Func.isEmpty(projectList)) {
|
||||
return List.of(NONE_ID);
|
||||
}
|
||||
List<Long> projectIds = projectList.stream()
|
||||
.map(ProjectApply::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
return Func.isEmpty(projectIds) ? List.of(NONE_ID) : projectIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前为小程序调度用户时,按可见项目写入 project 列;非调度用户不改动。
|
||||
*/
|
||||
public <T> void applyProjectScopeIfMpDispatcher(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> projectColumn) {
|
||||
if (!isMpDispatcher() || wrapper == null || projectColumn == null) {
|
||||
return;
|
||||
}
|
||||
List<Long> projectIds = resolveVisibleProjectIdsForQuery();
|
||||
if (Func.isEmpty(projectIds)) {
|
||||
return;
|
||||
}
|
||||
wrapper.in(projectColumn, projectIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 风险等无 projectId 字段时:按关联运单的项目收口。
|
||||
*/
|
||||
public <T> void applyWaybillProjectScopeIfMpDispatcher(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> waybillIdColumn) {
|
||||
if (!isMpDispatcher() || wrapper == null || waybillIdColumn == null) {
|
||||
return;
|
||||
}
|
||||
List<Long> projectIds = resolveVisibleProjectIdsForQuery();
|
||||
if (Func.isEmpty(projectIds)) {
|
||||
return;
|
||||
}
|
||||
String inList = projectIds.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||
wrapper.inSql(waybillIdColumn,
|
||||
"SELECT id FROM blade_waybill WHERE is_deleted = 0 AND project_id IN (" + inList + ")");
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -229,7 +229,7 @@ public final class WaybillProcessSupport {
|
||||
* <p>
|
||||
* 需要接单(接单节点 confirmMode=yes)且尚未接单(未响应 / 已拒绝)→ pending(待执行);
|
||||
* 已接单 → running(进行中);不需要接单 → running(仅当当前为空或 pending 时提升)。
|
||||
* draft / completed / cancelled 等终态或调度中间态不改动。
|
||||
* draft / completed / cancelled / pending_return 等终态或调度中间态不改动。
|
||||
*/
|
||||
public static String normalizeBusinessStatus(String businessStatus, String processJson) {
|
||||
return normalizeBusinessStatus(businessStatus, processJson, null);
|
||||
|
||||
+25
@@ -10,6 +10,9 @@ package org.springblade.transport.wrapper;
|
||||
|
||||
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.DictBizCache;
|
||||
import org.springblade.system.cache.DictCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
import org.springblade.transport.pojo.vo.FormalSettlementVO;
|
||||
@@ -46,6 +49,28 @@ public class FormalSettlementWrapper extends BaseEntityWrapper<FormalSettlement,
|
||||
default -> entity.getApprovalStatus();
|
||||
});
|
||||
vo.setAdjustStatusName("adjusted".equals(entity.getAdjustStatus()) ? "已调整" : null);
|
||||
vo.setBusinessModelName(dictBizName("business_model", entity.getBusinessModel()));
|
||||
vo.setTransportTypeName(dictBizName("transport_type", entity.getTransportType()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
private String dictBizName(String code, String value) {
|
||||
if (Func.isEmpty(value)) {
|
||||
return value;
|
||||
}
|
||||
String name = "";
|
||||
try {
|
||||
name = DictBizCache.getValue(code, value);
|
||||
} catch (Exception exception) {
|
||||
name = "";
|
||||
}
|
||||
if (Func.isEmpty(name)) {
|
||||
try {
|
||||
name = DictCache.getValue(code, value);
|
||||
} catch (Exception exception) {
|
||||
name = "";
|
||||
}
|
||||
}
|
||||
return Func.isEmpty(name) ? value : name;
|
||||
}
|
||||
}
|
||||
|
||||
+25
@@ -10,6 +10,9 @@ package org.springblade.transport.wrapper;
|
||||
|
||||
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.DictBizCache;
|
||||
import org.springblade.system.cache.DictCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.pojo.entity.PreSettlement;
|
||||
import org.springblade.transport.pojo.vo.PreSettlementVO;
|
||||
@@ -34,6 +37,8 @@ public class PreSettlementWrapper extends BaseEntityWrapper<PreSettlement, PreSe
|
||||
vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser()));
|
||||
vo.setApprovalStatusName(approvalStatusName(entity.getApprovalStatus()));
|
||||
vo.setSettlementTypeName("receivable".equals(entity.getSettlementType()) ? "应收" : "应付");
|
||||
vo.setBusinessModelName(dictBizName("business_model", entity.getBusinessModel()));
|
||||
vo.setTransportTypeName(dictBizName("transport_type", entity.getTransportType()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -48,4 +53,24 @@ public class PreSettlementWrapper extends BaseEntityWrapper<PreSettlement, PreSe
|
||||
};
|
||||
}
|
||||
|
||||
private String dictBizName(String code, String value) {
|
||||
if (Func.isEmpty(value)) {
|
||||
return value;
|
||||
}
|
||||
String name = "";
|
||||
try {
|
||||
name = DictBizCache.getValue(code, value);
|
||||
} catch (Exception exception) {
|
||||
name = "";
|
||||
}
|
||||
if (Func.isEmpty(name)) {
|
||||
try {
|
||||
name = DictCache.getValue(code, value);
|
||||
} catch (Exception exception) {
|
||||
name = "";
|
||||
}
|
||||
}
|
||||
return Func.isEmpty(name) ? value : name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -85,7 +85,8 @@ class ExceptionDisposalDetailPayloadTest {
|
||||
waybillService = mock(IWaybillService.class);
|
||||
loadingManageMapper = mock(LoadingManageMapper.class);
|
||||
service = new ExceptionDisposalServiceImpl(
|
||||
followRecordMapper, waybillService, mock(IDriverWaybillService.class), loadingManageMapper);
|
||||
followRecordMapper, waybillService, mock(IDriverWaybillService.class), loadingManageMapper,
|
||||
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
|
||||
inject("baseMapper", disposalMapper);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -66,11 +66,14 @@ class ExceptionDisposalListQueryTest {
|
||||
disposalMapper = mock(ExceptionDisposalMapper.class);
|
||||
waybillService = mock(IWaybillService.class);
|
||||
loadingManageMapper = mock(LoadingManageMapper.class);
|
||||
org.springblade.transport.support.MpUserDataScopeSupport scopeSupport =
|
||||
mock(org.springblade.transport.support.MpUserDataScopeSupport.class);
|
||||
service = new ExceptionDisposalServiceImpl(
|
||||
mock(ExceptionDisposalFollowRecordMapper.class),
|
||||
waybillService,
|
||||
mock(IDriverWaybillService.class),
|
||||
loadingManageMapper);
|
||||
loadingManageMapper,
|
||||
scopeSupport);
|
||||
inject("baseMapper", disposalMapper);
|
||||
when(disposalMapper.selectPage(any(), any())).thenReturn(new Page<ExceptionDisposal>(1, 10));
|
||||
}
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ class ExceptionDisposalRejectRevokeTest {
|
||||
followRecordMapper = mock(ExceptionDisposalFollowRecordMapper.class);
|
||||
service = new ExceptionDisposalServiceImpl(
|
||||
followRecordMapper, mock(IWaybillService.class), mock(IDriverWaybillService.class),
|
||||
mock(LoadingManageMapper.class));
|
||||
mock(LoadingManageMapper.class), mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
|
||||
inject("baseMapper", disposalMapper);
|
||||
when(disposalMapper.updateById(any(ExceptionDisposal.class))).thenReturn(1);
|
||||
when(followRecordMapper.insert(any(ExceptionDisposalFollowRecord.class))).thenReturn(1);
|
||||
|
||||
+2
-1
@@ -73,7 +73,8 @@ class ExceptionDisposalReportGateTest {
|
||||
driverWaybillService = mock(IDriverWaybillService.class);
|
||||
service = new ExceptionDisposalServiceImpl(
|
||||
mock(ExceptionDisposalFollowRecordMapper.class), waybillService, driverWaybillService,
|
||||
mock(LoadingManageMapper.class));
|
||||
mock(LoadingManageMapper.class),
|
||||
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
|
||||
inject("baseMapper", disposalMapper);
|
||||
|
||||
when(waybillService.getById(WAYBILL_ID)).thenReturn(waybill());
|
||||
|
||||
+2
-1
@@ -87,7 +87,8 @@ class ExceptionDisposalRouteAdjustTest {
|
||||
waybillService = mock(IWaybillService.class);
|
||||
loadingManageMapper = mock(LoadingManageMapper.class);
|
||||
service = new ExceptionDisposalServiceImpl(
|
||||
followRecordMapper, waybillService, mock(IDriverWaybillService.class), loadingManageMapper);
|
||||
followRecordMapper, waybillService, mock(IDriverWaybillService.class), loadingManageMapper,
|
||||
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
|
||||
inject("baseMapper", disposalMapper);
|
||||
when(disposalMapper.updateById(any(ExceptionDisposal.class))).thenReturn(1);
|
||||
when(followRecordMapper.insert(any(ExceptionDisposalFollowRecord.class))).thenReturn(1);
|
||||
|
||||
+11
-3
@@ -139,13 +139,14 @@ class LoadingManageChildStatusWriteTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("不需接单的项目:提交时子单直接落进行中,不留待执行等读路径去提升")
|
||||
@DisplayName("不需接单的项目:提交时配载单与子单均为进行中")
|
||||
void submitWritesRunningForProjectWithoutAcceptNode() {
|
||||
stubWaybills(waybill(NO_ACCEPT_WAYBILL_ID, NO_ACCEPT_PROJECT_ID));
|
||||
stubConfigs(config(NO_ACCEPT_PROJECT_ID, NO_ACCEPT_JSON));
|
||||
|
||||
assertTrue(service.submit(loadingForm(NO_ACCEPT_WAYBILL_ID)));
|
||||
|
||||
assertEquals("running", capturedLoadingStatus());
|
||||
CapturedUpdate statusUpdate = statusSyncUpdates().get(0);
|
||||
assertEquals("running", statusUpdate.setValue("business_status"));
|
||||
assertEquals(List.of(NO_ACCEPT_WAYBILL_ID), statusUpdate.whereIds());
|
||||
@@ -153,13 +154,14 @@ class LoadingManageChildStatusWriteTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("需接单的项目:提交时子单落待执行,等新司机接单")
|
||||
@DisplayName("需接单的项目:提交时配载单与子单均为待执行,等新司机接单")
|
||||
void submitWritesPendingForProjectWithAcceptNode() {
|
||||
stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID));
|
||||
stubConfigs(config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON));
|
||||
|
||||
assertTrue(service.submit(loadingForm(NEED_ACCEPT_WAYBILL_ID)));
|
||||
|
||||
assertEquals("pending", capturedLoadingStatus());
|
||||
assertEquals("pending", statusSyncUpdates().get(0).setValue("business_status"));
|
||||
}
|
||||
|
||||
@@ -367,12 +369,18 @@ class LoadingManageChildStatusWriteTest {
|
||||
waybill.setProjectId(projectId);
|
||||
waybill.setProcessJson(processJson);
|
||||
waybill.setDeptId(DEPT_ID_LONG);
|
||||
// 可配载只放行公路 + 待执行 + 未配载,夹具的票都是这种票
|
||||
// 可配载放行公路 + 待执行/进行中 + 未配载,夹具默认待执行
|
||||
waybill.setTransportType("公路运输");
|
||||
waybill.setBusinessStatus("pending");
|
||||
return waybill;
|
||||
}
|
||||
|
||||
private String capturedLoadingStatus() {
|
||||
ArgumentCaptor<LoadingManage> captor = ArgumentCaptor.forClass(LoadingManage.class);
|
||||
verify(service, atLeastOnce()).saveOrUpdate(captor.capture());
|
||||
return captor.getValue().getBusinessStatus();
|
||||
}
|
||||
|
||||
private void stubWaybills(Waybill... waybills) {
|
||||
when(waybillMapper.selectList(ArgumentMatchers.<Wrapper<Waybill>>any())).thenReturn(List.of(waybills));
|
||||
}
|
||||
|
||||
+2
-1
@@ -84,7 +84,8 @@ class ManageAdminHomeFeedTest {
|
||||
mock(IRiskDisposalService.class),
|
||||
mock(IDriverWaybillService.class),
|
||||
mock(ITripQueryService.class),
|
||||
mock(ILoadingManageService.class));
|
||||
mock(ILoadingManageService.class),
|
||||
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
|
||||
when(exceptionService.list(ArgumentMatchers.<Wrapper<ExceptionDisposal>>any()))
|
||||
.thenReturn(new ArrayList<>());
|
||||
when(waybillService.list(ArgumentMatchers.<Wrapper<Waybill>>any()))
|
||||
|
||||
+2
-1
@@ -101,7 +101,8 @@ class ManageTripListDetailTest {
|
||||
mock(IRiskDisposalService.class),
|
||||
driverWaybillService,
|
||||
tripQueryService,
|
||||
loadingManageService);
|
||||
loadingManageService,
|
||||
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
|
||||
when(exceptionDisposalService.list(ArgumentMatchers.<Wrapper<ExceptionDisposal>>any()))
|
||||
.thenReturn(new ArrayList<>());
|
||||
when(waybillService.list(ArgumentMatchers.<Wrapper<Waybill>>any())).thenReturn(new ArrayList<>());
|
||||
|
||||
+2
-1
@@ -65,7 +65,8 @@ class ManageWaybillBusinessStatusTest {
|
||||
mock(IRiskDisposalService.class),
|
||||
mock(IDriverWaybillService.class),
|
||||
mock(ITripQueryService.class),
|
||||
mock(ILoadingManageService.class));
|
||||
mock(ILoadingManageService.class),
|
||||
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
|
||||
when(exceptionDisposalService.list(anyWrapper())).thenReturn(new ArrayList<>());
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -29,6 +29,7 @@ import org.springblade.transport.service.ILoadingManageService;
|
||||
import org.springblade.transport.service.IRiskDisposalService;
|
||||
import org.springblade.transport.service.ITripQueryService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.MpUserDataScopeSupport;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -80,6 +81,8 @@ class ManageWaybillOrgScopeTest {
|
||||
waybillService = mock(IWaybillService.class);
|
||||
tripQueryService = mock(ITripQueryService.class);
|
||||
IExceptionDisposalService exceptionDisposalService = mock(IExceptionDisposalService.class);
|
||||
MpUserDataScopeSupport mpUserDataScopeSupport = mock(MpUserDataScopeSupport.class);
|
||||
when(mpUserDataScopeSupport.resolveVisibleProjectIdsForQuery()).thenReturn(null);
|
||||
service = new ManageWaybillServiceImpl(
|
||||
waybillService,
|
||||
mock(IDriverService.class),
|
||||
@@ -87,7 +90,8 @@ class ManageWaybillOrgScopeTest {
|
||||
mock(IRiskDisposalService.class),
|
||||
mock(IDriverWaybillService.class),
|
||||
tripQueryService,
|
||||
mock(ILoadingManageService.class));
|
||||
mock(ILoadingManageService.class),
|
||||
mpUserDataScopeSupport);
|
||||
when(exceptionDisposalService.list(ArgumentMatchers.<Wrapper<ExceptionDisposal>>any()))
|
||||
.thenReturn(new ArrayList<>());
|
||||
when(waybillService.list(ArgumentMatchers.<Wrapper<Waybill>>any())).thenReturn(new ArrayList<>());
|
||||
|
||||
+3
-5
@@ -68,7 +68,7 @@ class WaybillLoadableQueryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("开关开启:公路词表、待执行、未配载一次全部进 wrapper,接单状态不参与")
|
||||
@DisplayName("开关开启:公路词表、待执行/进行中、未配载一次全部进 wrapper,接单状态不参与")
|
||||
void loadableSwitchAppliesWholePredicate() {
|
||||
LambdaQueryWrapper<Waybill> wrapper = capturedQuery(loadableQuery());
|
||||
String sql = String.valueOf(wrapper.getTargetSql());
|
||||
@@ -79,10 +79,8 @@ class WaybillLoadableQueryTest {
|
||||
assertTrue(params.contains("%" + keyword + "%"),
|
||||
"公路词表要整份进条件,只写 road 会漏掉中文取值的运单:" + params);
|
||||
}
|
||||
assertTrue(sql.contains("business_status =") && params.contains("pending"),
|
||||
"只放行待执行:" + sql + " " + params);
|
||||
assertFalse(params.contains("running"),
|
||||
"进行中一律不可配载,不能沿用「待执行与进行中都可配载」的旧口径:" + params);
|
||||
assertTrue(sql.contains("business_status") && params.contains("pending") && params.contains("running"),
|
||||
"放行待执行与进行中:" + sql + " " + params);
|
||||
assertTrue(sql.contains("loading_no IS NULL") && sql.contains("loading_no ="),
|
||||
"未配载要同时覆盖 NULL 与空串:" + sql);
|
||||
assertFalse(sql.contains("driver_accept_status"),
|
||||
|
||||
+9
-8
@@ -50,10 +50,12 @@ class LoadableAdmissionSupportTest {
|
||||
// ------------------------------------------------------------ 单张运单准入
|
||||
|
||||
@Test
|
||||
@DisplayName("公路 + 待执行 + 未配载才可配载")
|
||||
@DisplayName("公路 + 待执行/进行中 + 未配载才可配载")
|
||||
void loadableOnlyWhenAllConditionsHold() {
|
||||
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("road", "pending", null, "pending"), null));
|
||||
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("公路运输", "pending", "", null), null));
|
||||
assertTrue(LoadableAdmissionSupport.isLoadable(waybill("road", "running", null, null), null),
|
||||
"进行中也可配载");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,12 +66,10 @@ class LoadableAdmissionSupportTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("非公路、非待执行、已关联配载单各自单独即可拒绝")
|
||||
@DisplayName("非公路、非待执行/进行中、已关联配载单各自单独即可拒绝")
|
||||
void eachConditionRejectsOnItsOwn() {
|
||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("railway", "pending", null, "pending"), null),
|
||||
"非公路不可配载");
|
||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "running", null, null), null),
|
||||
"进行中不可配载:不需要接单的项目保存后即为进行中,那是过程配置的产物,不代表可配载");
|
||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "draft", null, null), null));
|
||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "completed", null, null), null));
|
||||
assertFalse(LoadableAdmissionSupport.isLoadable(waybill("road", "cancelled", null, null), null));
|
||||
@@ -79,18 +79,19 @@ class LoadableAdmissionSupportTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("不可配载原因按 公路 → 待执行 → 未关联其它配载单 取第一条")
|
||||
@DisplayName("不可配载原因按 公路 → 待执行/进行中 → 未关联其它配载单 取第一条")
|
||||
void unloadableReasonFollowsPredicateOrder() {
|
||||
assertEquals(LoadableAdmissionSupport.UnloadableReason.NOT_ROAD,
|
||||
LoadableAdmissionSupport.unloadableReason(
|
||||
waybill("railway", "running", "PZ20260101-002", null), "PZ20260101-001"));
|
||||
assertEquals(LoadableAdmissionSupport.UnloadableReason.NOT_PENDING,
|
||||
waybill("railway", "completed", "PZ20260101-002", null), "PZ20260101-001"));
|
||||
assertEquals(LoadableAdmissionSupport.UnloadableReason.NOT_LOADABLE_STATUS,
|
||||
LoadableAdmissionSupport.unloadableReason(
|
||||
waybill("road", "running", "PZ20260101-002", null), "PZ20260101-001"));
|
||||
waybill("road", "completed", "PZ20260101-002", null), "PZ20260101-001"));
|
||||
assertEquals(LoadableAdmissionSupport.UnloadableReason.ON_OTHER_MANIFEST,
|
||||
LoadableAdmissionSupport.unloadableReason(
|
||||
waybill("road", "pending", "PZ20260101-002", null), "PZ20260101-001"));
|
||||
assertNull(LoadableAdmissionSupport.unloadableReason(waybill("road", "pending", null, null), null));
|
||||
assertNull(LoadableAdmissionSupport.unloadableReason(waybill("road", "running", null, null), null));
|
||||
assertNull(LoadableAdmissionSupport.unloadableReason(
|
||||
waybill("road", "pending", "PZ20260101-001", null), "PZ20260101-001"));
|
||||
}
|
||||
|
||||
@@ -311,6 +311,7 @@ CREATE TABLE `blade_dept` (
|
||||
`mnemonic_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码',
|
||||
`carrier_customer_id` bigint NULL DEFAULT NULL COMMENT '承运商客商档案主键',
|
||||
`is_platform_company` tinyint NOT NULL DEFAULT 0 COMMENT '是否平台公司:0否,1是',
|
||||
`is_business_dept` tinyint NOT NULL DEFAULT 0 COMMENT '是否业务部门:0否,1是',
|
||||
`oa_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA主键',
|
||||
`oa_sup_sub_com_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA上级主键',
|
||||
`sort` int NULL DEFAULT NULL COMMENT '排序',
|
||||
|
||||
@@ -8,6 +8,7 @@ CREATE TABLE `blade_credit_score_quantification` (
|
||||
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '评定表名称',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`standard_description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标准说明',
|
||||
`max_minus_score` decimal(10,2) NULL DEFAULT NULL COMMENT '减分项目最高扣分限制',
|
||||
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
-- ============================================================================
|
||||
-- 评分量化表跨环境复制:按 ID 生成 INSERT 脚本
|
||||
-- ============================================================================
|
||||
-- 用法(在 DEV 库执行):
|
||||
-- 1. 修改下方 @quantification_id 为要复制的评分量化表主键
|
||||
-- 2. 整段执行,结果集中每一行是一条可直接在 UAT/PROD 执行的 SQL
|
||||
-- 3. 先复制「清理」语句,再复制各表 INSERT,按结果顺序在目标库执行
|
||||
--
|
||||
-- 覆盖表(按外键依赖顺序):
|
||||
-- blade_credit_score_quantification
|
||||
-- blade_credit_score_category
|
||||
-- blade_credit_score_item
|
||||
-- blade_credit_score_item_option
|
||||
-- blade_credit_rating_standard
|
||||
--
|
||||
-- 说明:
|
||||
-- - 保留原主键与关联 ID,便于 UAT/PROD 与 DEV 配置一致
|
||||
-- - 仅导出 is_deleted = 0 的有效数据
|
||||
-- - 目标库若已存在同名评定表(tenant_id + name + is_deleted 唯一),请先处理冲突
|
||||
-- ============================================================================
|
||||
|
||||
-- >>> 请修改为要导出的评分量化表 ID <<<
|
||||
SET @quantification_id := 0;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 0. 导出前校验:确认主表存在及子表数量
|
||||
-- ----------------------------------------------------------------------------
|
||||
SELECT
|
||||
q.id,
|
||||
q.tenant_id,
|
||||
q.name,
|
||||
q.status,
|
||||
q.max_minus_score,
|
||||
(SELECT COUNT(1) FROM blade_credit_score_category c
|
||||
WHERE c.quantification_id = q.id AND c.is_deleted = 0) AS category_count,
|
||||
(SELECT COUNT(1) FROM blade_credit_score_item i
|
||||
WHERE i.quantification_id = q.id AND i.is_deleted = 0) AS item_count,
|
||||
(SELECT COUNT(1) FROM blade_credit_score_item_option o
|
||||
WHERE o.quantification_id = q.id AND o.is_deleted = 0) AS option_count,
|
||||
(SELECT COUNT(1) FROM blade_credit_rating_standard s
|
||||
WHERE s.quantification_id = q.id AND s.is_deleted = 0) AS standard_count
|
||||
FROM blade_credit_score_quantification q
|
||||
WHERE q.id = @quantification_id
|
||||
AND q.is_deleted = 0;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 1. 目标库清理语句(先删子表再删主表;结果复制到 UAT/PROD 优先执行)
|
||||
-- ----------------------------------------------------------------------------
|
||||
SELECT CONCAT(
|
||||
'-- ========== 目标库清理(quantification_id=', @quantification_id, ') ==========\n',
|
||||
'DELETE FROM `blade_credit_score_item_option` WHERE `quantification_id` = ', @quantification_id, ';\n',
|
||||
'DELETE FROM `blade_credit_score_item` WHERE `quantification_id` = ', @quantification_id, ';\n',
|
||||
'DELETE FROM `blade_credit_score_category` WHERE `quantification_id` = ', @quantification_id, ';\n',
|
||||
'DELETE FROM `blade_credit_rating_standard` WHERE `quantification_id` = ', @quantification_id, ';\n',
|
||||
'DELETE FROM `blade_credit_score_quantification` WHERE `id` = ', @quantification_id, ';\n',
|
||||
'-- 若目标库存在同名不同 ID 的评定表,可按名称清理(按需取消注释):\n',
|
||||
'-- DELETE o FROM `blade_credit_score_item_option` o\n',
|
||||
'-- INNER JOIN `blade_credit_score_quantification` q ON q.id = o.quantification_id\n',
|
||||
'-- WHERE q.tenant_id = ', IFNULL(QUOTE((SELECT tenant_id FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND q.name = ', IFNULL(QUOTE((SELECT name FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND q.is_deleted = 0;\n',
|
||||
'-- DELETE i FROM `blade_credit_score_item` i\n',
|
||||
'-- INNER JOIN `blade_credit_score_quantification` q ON q.id = i.quantification_id\n',
|
||||
'-- WHERE q.tenant_id = ', IFNULL(QUOTE((SELECT tenant_id FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND q.name = ', IFNULL(QUOTE((SELECT name FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND q.is_deleted = 0;\n',
|
||||
'-- DELETE c FROM `blade_credit_score_category` c\n',
|
||||
'-- INNER JOIN `blade_credit_score_quantification` q ON q.id = c.quantification_id\n',
|
||||
'-- WHERE q.tenant_id = ', IFNULL(QUOTE((SELECT tenant_id FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND q.name = ', IFNULL(QUOTE((SELECT name FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND q.is_deleted = 0;\n',
|
||||
'-- DELETE s FROM `blade_credit_rating_standard` s\n',
|
||||
'-- INNER JOIN `blade_credit_score_quantification` q ON q.id = s.quantification_id\n',
|
||||
'-- WHERE q.tenant_id = ', IFNULL(QUOTE((SELECT tenant_id FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND q.name = ', IFNULL(QUOTE((SELECT name FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND q.is_deleted = 0;\n',
|
||||
'-- DELETE FROM `blade_credit_score_quantification`\n',
|
||||
'-- WHERE tenant_id = ', IFNULL(QUOTE((SELECT tenant_id FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND name = ', IFNULL(QUOTE((SELECT name FROM blade_credit_score_quantification WHERE id = @quantification_id LIMIT 1)), 'NULL'),
|
||||
' AND is_deleted = 0;'
|
||||
) AS sql_text;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 2. 主表 blade_credit_score_quantification
|
||||
-- ----------------------------------------------------------------------------
|
||||
SELECT CONCAT(
|
||||
'INSERT INTO `blade_credit_score_quantification` (',
|
||||
'`id`, `tenant_id`, `name`, `remark`, `standard_description`, `max_minus_score`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(name), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(remark), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(standard_description), 'NULL'), ', ',
|
||||
IFNULL(CAST(max_minus_score AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
) AS sql_text
|
||||
FROM blade_credit_score_quantification
|
||||
WHERE id = @quantification_id
|
||||
AND is_deleted = 0;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 3. 分类 blade_credit_score_category
|
||||
-- ----------------------------------------------------------------------------
|
||||
SELECT CONCAT(
|
||||
'INSERT INTO `blade_credit_score_category` (',
|
||||
'`id`, `tenant_id`, `quantification_id`, `category_code`, `category_name`, `sort`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
quantification_id, ', ',
|
||||
IFNULL(QUOTE(category_code), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(category_name), 'NULL'), ', ',
|
||||
IFNULL(CAST(sort AS CHAR), '0'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
) AS sql_text
|
||||
FROM blade_credit_score_category
|
||||
WHERE quantification_id = @quantification_id
|
||||
AND is_deleted = 0
|
||||
ORDER BY sort, id;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 4. 评分项目 blade_credit_score_item
|
||||
-- ----------------------------------------------------------------------------
|
||||
SELECT CONCAT(
|
||||
'INSERT INTO `blade_credit_score_item` (',
|
||||
'`id`, `tenant_id`, `quantification_id`, `category_id`, `category_code`, `item_name`, ',
|
||||
'`option_type`, `base_value`, `score`, `score_description`, `option_description`, `sort`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
quantification_id, ', ',
|
||||
category_id, ', ',
|
||||
IFNULL(QUOTE(category_code), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(item_name), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(option_type), 'NULL'), ', ',
|
||||
IFNULL(CAST(base_value AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(score AS CHAR), '0.00'), ', ',
|
||||
IFNULL(QUOTE(score_description), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(option_description), 'NULL'), ', ',
|
||||
IFNULL(CAST(sort AS CHAR), '0'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
) AS sql_text
|
||||
FROM blade_credit_score_item
|
||||
WHERE quantification_id = @quantification_id
|
||||
AND is_deleted = 0
|
||||
ORDER BY sort, id;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 5. 评分项目选项 blade_credit_score_item_option
|
||||
-- ----------------------------------------------------------------------------
|
||||
SELECT CONCAT(
|
||||
'INSERT INTO `blade_credit_score_item_option` (',
|
||||
'`id`, `tenant_id`, `quantification_id`, `item_id`, `option_name`, ',
|
||||
'`change_type`, `change_value`, `change_unit`, `score_type`, `score`, `sort`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
quantification_id, ', ',
|
||||
item_id, ', ',
|
||||
IFNULL(QUOTE(option_name), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(change_type), 'NULL'), ', ',
|
||||
IFNULL(CAST(change_value AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(change_unit), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(score_type), 'NULL'), ', ',
|
||||
IFNULL(CAST(score AS CHAR), '0.00'), ', ',
|
||||
IFNULL(CAST(sort AS CHAR), '0'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
) AS sql_text
|
||||
FROM blade_credit_score_item_option
|
||||
WHERE quantification_id = @quantification_id
|
||||
AND is_deleted = 0
|
||||
ORDER BY sort, id;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 6. 信用等级评估标准 blade_credit_rating_standard
|
||||
-- ----------------------------------------------------------------------------
|
||||
SELECT CONCAT(
|
||||
'INSERT INTO `blade_credit_rating_standard` (',
|
||||
'`id`, `tenant_id`, `quantification_id`, `credit_level`, ',
|
||||
'`score_rate_lower`, `score_rate_upper`, `score_rate_increase`, ',
|
||||
'`credit_limit_lower`, `credit_limit_upper`, `credit_limit_increase`, ',
|
||||
'`standard_description`, `sort`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
quantification_id, ', ',
|
||||
IFNULL(QUOTE(credit_level), 'NULL'), ', ',
|
||||
IFNULL(CAST(score_rate_lower AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(score_rate_upper AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(score_rate_increase AS CHAR), '1'), ', ',
|
||||
IFNULL(CAST(credit_limit_lower AS CHAR), '0.00'), ', ',
|
||||
IFNULL(CAST(credit_limit_upper AS CHAR), '0.00'), ', ',
|
||||
IFNULL(CAST(credit_limit_increase AS CHAR), '0.00'), ', ',
|
||||
IFNULL(QUOTE(standard_description), 'NULL'), ', ',
|
||||
IFNULL(CAST(sort AS CHAR), '0'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
) AS sql_text
|
||||
FROM blade_credit_rating_standard
|
||||
WHERE quantification_id = @quantification_id
|
||||
AND is_deleted = 0
|
||||
ORDER BY sort, id;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 7. 汇总:一次性输出全部 SQL(便于整段复制;MySQL 客户端需允许 GROUP_CONCAT 长度)
|
||||
-- ----------------------------------------------------------------------------
|
||||
SET SESSION group_concat_max_len = 1024 * 1024 * 16;
|
||||
|
||||
SELECT CONCAT_WS(
|
||||
'\n',
|
||||
-- 清理
|
||||
CONCAT(
|
||||
'-- ========== 目标库清理(quantification_id=', @quantification_id, ') ==========\n',
|
||||
'DELETE FROM `blade_credit_score_item_option` WHERE `quantification_id` = ', @quantification_id, ';\n',
|
||||
'DELETE FROM `blade_credit_score_item` WHERE `quantification_id` = ', @quantification_id, ';\n',
|
||||
'DELETE FROM `blade_credit_score_category` WHERE `quantification_id` = ', @quantification_id, ';\n',
|
||||
'DELETE FROM `blade_credit_rating_standard` WHERE `quantification_id` = ', @quantification_id, ';\n',
|
||||
'DELETE FROM `blade_credit_score_quantification` WHERE `id` = ', @quantification_id, ';'
|
||||
),
|
||||
'',
|
||||
'-- ========== 主表 ==========',
|
||||
(SELECT GROUP_CONCAT(
|
||||
CONCAT(
|
||||
'INSERT INTO `blade_credit_score_quantification` (',
|
||||
'`id`, `tenant_id`, `name`, `remark`, `standard_description`, `max_minus_score`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(name), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(remark), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(standard_description), 'NULL'), ', ',
|
||||
IFNULL(CAST(max_minus_score AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
)
|
||||
SEPARATOR '\n'
|
||||
)
|
||||
FROM blade_credit_score_quantification
|
||||
WHERE id = @quantification_id AND is_deleted = 0),
|
||||
'',
|
||||
'-- ========== 分类 ==========',
|
||||
IFNULL((SELECT GROUP_CONCAT(
|
||||
CONCAT(
|
||||
'INSERT INTO `blade_credit_score_category` (',
|
||||
'`id`, `tenant_id`, `quantification_id`, `category_code`, `category_name`, `sort`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
quantification_id, ', ',
|
||||
IFNULL(QUOTE(category_code), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(category_name), 'NULL'), ', ',
|
||||
IFNULL(CAST(sort AS CHAR), '0'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
)
|
||||
ORDER BY sort, id
|
||||
SEPARATOR '\n'
|
||||
)
|
||||
FROM blade_credit_score_category
|
||||
WHERE quantification_id = @quantification_id AND is_deleted = 0), ''),
|
||||
'',
|
||||
'-- ========== 评分项目 ==========',
|
||||
IFNULL((SELECT GROUP_CONCAT(
|
||||
CONCAT(
|
||||
'INSERT INTO `blade_credit_score_item` (',
|
||||
'`id`, `tenant_id`, `quantification_id`, `category_id`, `category_code`, `item_name`, ',
|
||||
'`option_type`, `base_value`, `score`, `score_description`, `option_description`, `sort`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
quantification_id, ', ',
|
||||
category_id, ', ',
|
||||
IFNULL(QUOTE(category_code), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(item_name), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(option_type), 'NULL'), ', ',
|
||||
IFNULL(CAST(base_value AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(score AS CHAR), '0.00'), ', ',
|
||||
IFNULL(QUOTE(score_description), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(option_description), 'NULL'), ', ',
|
||||
IFNULL(CAST(sort AS CHAR), '0'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
)
|
||||
ORDER BY sort, id
|
||||
SEPARATOR '\n'
|
||||
)
|
||||
FROM blade_credit_score_item
|
||||
WHERE quantification_id = @quantification_id AND is_deleted = 0), ''),
|
||||
'',
|
||||
'-- ========== 评分项目选项 ==========',
|
||||
IFNULL((SELECT GROUP_CONCAT(
|
||||
CONCAT(
|
||||
'INSERT INTO `blade_credit_score_item_option` (',
|
||||
'`id`, `tenant_id`, `quantification_id`, `item_id`, `option_name`, ',
|
||||
'`change_type`, `change_value`, `change_unit`, `score_type`, `score`, `sort`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
quantification_id, ', ',
|
||||
item_id, ', ',
|
||||
IFNULL(QUOTE(option_name), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(change_type), 'NULL'), ', ',
|
||||
IFNULL(CAST(change_value AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(change_unit), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(score_type), 'NULL'), ', ',
|
||||
IFNULL(CAST(score AS CHAR), '0.00'), ', ',
|
||||
IFNULL(CAST(sort AS CHAR), '0'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
)
|
||||
ORDER BY sort, id
|
||||
SEPARATOR '\n'
|
||||
)
|
||||
FROM blade_credit_score_item_option
|
||||
WHERE quantification_id = @quantification_id AND is_deleted = 0), ''),
|
||||
'',
|
||||
'-- ========== 信用等级评估标准 ==========',
|
||||
IFNULL((SELECT GROUP_CONCAT(
|
||||
CONCAT(
|
||||
'INSERT INTO `blade_credit_rating_standard` (',
|
||||
'`id`, `tenant_id`, `quantification_id`, `credit_level`, ',
|
||||
'`score_rate_lower`, `score_rate_upper`, `score_rate_increase`, ',
|
||||
'`credit_limit_lower`, `credit_limit_upper`, `credit_limit_increase`, ',
|
||||
'`standard_description`, `sort`, ',
|
||||
'`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`, `status`, `is_deleted`',
|
||||
') VALUES (',
|
||||
id, ', ',
|
||||
IFNULL(QUOTE(tenant_id), 'NULL'), ', ',
|
||||
quantification_id, ', ',
|
||||
IFNULL(QUOTE(credit_level), 'NULL'), ', ',
|
||||
IFNULL(CAST(score_rate_lower AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(score_rate_upper AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(score_rate_increase AS CHAR), '1'), ', ',
|
||||
IFNULL(CAST(credit_limit_lower AS CHAR), '0.00'), ', ',
|
||||
IFNULL(CAST(credit_limit_upper AS CHAR), '0.00'), ', ',
|
||||
IFNULL(CAST(credit_limit_increase AS CHAR), '0.00'), ', ',
|
||||
IFNULL(QUOTE(standard_description), 'NULL'), ', ',
|
||||
IFNULL(CAST(sort AS CHAR), '0'), ', ',
|
||||
IFNULL(CAST(create_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(create_dept AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(update_user AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(QUOTE(DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s')), 'NULL'), ', ',
|
||||
IFNULL(CAST(status AS CHAR), 'NULL'), ', ',
|
||||
IFNULL(CAST(is_deleted AS CHAR), '0'),
|
||||
');'
|
||||
)
|
||||
ORDER BY sort, id
|
||||
SEPARATOR '\n'
|
||||
)
|
||||
FROM blade_credit_rating_standard
|
||||
WHERE quantification_id = @quantification_id AND is_deleted = 0), '')
|
||||
) AS full_export_sql;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- 评分量化表:减分项目最高扣分限制
|
||||
-- 客商自评/复评时,所有减分项合计扣分不得超过该值;超出则按该上限计分
|
||||
|
||||
ALTER TABLE `blade_credit_score_quantification`
|
||||
ADD COLUMN `max_minus_score` decimal(10,2) NULL DEFAULT NULL COMMENT '减分项目最高扣分限制' AFTER `standard_description`;
|
||||
@@ -18,21 +18,24 @@ CREATE TABLE `blade_customer_archive` (
|
||||
`registered_region_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '注册地址行政区划',
|
||||
`registered_detail_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '注册地址详细地址',
|
||||
`legal_person` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '法人/负责人',
|
||||
`contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '联系电话',
|
||||
`contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',
|
||||
`dept_id` bigint NULL DEFAULT NULL COMMENT '所属组织ID',
|
||||
`dept_ids` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所属组织ID集合',
|
||||
`dept_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所属组织',
|
||||
`invoice_tax_rate` decimal(6,2) NULL DEFAULT NULL COMMENT '开票税点',
|
||||
`business_scope` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '经营范围',
|
||||
`business_scope` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '经营范围',
|
||||
`network_freight_platform` tinyint NULL DEFAULT NULL COMMENT '网络货运平台:0否,1是',
|
||||
`business_term_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '营业期限类型',
|
||||
`business_start_date` date NULL DEFAULT NULL COMMENT '营业期限开始日',
|
||||
`business_end_date` date NULL DEFAULT NULL COMMENT '营业期限截止日',
|
||||
`registered_capital` decimal(18,2) NULL DEFAULT NULL COMMENT '注册资金(万元)',
|
||||
`paid_in_capital` decimal(18,2) NULL DEFAULT NULL COMMENT '实缴资本(万元)',
|
||||
`principal` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客商负责人',
|
||||
`mnemonic_code` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码',
|
||||
`customer_level` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户等级',
|
||||
`max_credit_limit` decimal(18,2) NULL DEFAULT NULL COMMENT '最大资金使用额度(万元)',
|
||||
`apply_credit_limit` decimal(18,2) NULL DEFAULT NULL COMMENT '申请总资金使用额度(万元)',
|
||||
`used_credit_limit` decimal(18,2) NULL DEFAULT NULL COMMENT '已使用额度(万元)',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`qualification_attachments` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '客商材料JSON',
|
||||
`access_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'temporary' COMMENT '准入类型:temporary临时,formal正式',
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- 客商档案:实缴资本、营业期限开始日、已使用额度;经营范围加长;联系电话改为可空(改由联系人维护)
|
||||
ALTER TABLE `blade_customer_archive`
|
||||
ADD COLUMN `business_start_date` date NULL DEFAULT NULL COMMENT '营业期限开始日' AFTER `business_term_type`,
|
||||
ADD COLUMN `paid_in_capital` decimal(18,2) NULL DEFAULT NULL COMMENT '实缴资本(万元)' AFTER `registered_capital`,
|
||||
ADD COLUMN `used_credit_limit` decimal(18,2) NULL DEFAULT NULL COMMENT '已使用额度(万元)' AFTER `apply_credit_limit`,
|
||||
MODIFY COLUMN `business_scope` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '经营范围',
|
||||
MODIFY COLUMN `contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话';
|
||||
@@ -0,0 +1,5 @@
|
||||
-- 机构表:新增「是否业务部门」字段,默认否
|
||||
-- 全量 OA 同步时须保留本地已配置值,不得覆盖
|
||||
|
||||
ALTER TABLE `blade_dept`
|
||||
ADD COLUMN `is_business_dept` tinyint NOT NULL DEFAULT 0 COMMENT '是否业务部门:0否,1是' AFTER `is_platform_company`;
|
||||
@@ -9,6 +9,7 @@ CREATE TABLE IF NOT EXISTS `blade_formal_settlement` (
|
||||
`dept_id` bigint(20) DEFAULT NULL, `dept_name` varchar(100) DEFAULT NULL, `contract_id` bigint(20) NOT NULL,
|
||||
`contract_no` varchar(100) DEFAULT NULL, `contract_name` varchar(100) NOT NULL, `payer_name` varchar(200) DEFAULT NULL,
|
||||
`payee_name` varchar(200) DEFAULT NULL, `currency` varchar(20) NOT NULL DEFAULT 'RMB', `settlement_amount` decimal(18,2) NOT NULL DEFAULT '0.00',
|
||||
`business_model` varchar(64) DEFAULT NULL COMMENT '业务模式', `transport_type` varchar(64) DEFAULT NULL COMMENT '运输方式',
|
||||
`local_currency` varchar(20) NOT NULL DEFAULT 'RMB', `local_settlement_amount` decimal(18,2) DEFAULT NULL,
|
||||
`applied_payment_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '申请付款金额含预付',
|
||||
`paid_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '已收已付合计含预付',
|
||||
|
||||
@@ -18,6 +18,8 @@ CREATE TABLE IF NOT EXISTS `blade_mp_user` (
|
||||
`openid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信小程序 openid',
|
||||
`identities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '小程序身份,逗号分隔:driver,admin',
|
||||
`dept_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门id',
|
||||
`data_scope_range` tinyint NOT NULL DEFAULT 1 COMMENT '数据权限范围:1所属组织,2全部组织,3自定义',
|
||||
`data_level_range` tinyint NOT NULL DEFAULT 1 COMMENT '数据层级范围:1包含下级,2仅本级',
|
||||
`post_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '岗位id',
|
||||
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
-- 小程序用户:数据权限范围、数据层级范围、自定义组织数据权限
|
||||
ALTER TABLE `blade_mp_user`
|
||||
ADD COLUMN `data_scope_range` tinyint NOT NULL DEFAULT 1 COMMENT '数据权限范围:1所属组织,2全部组织,3自定义' AFTER `dept_id`,
|
||||
ADD COLUMN `data_level_range` tinyint NOT NULL DEFAULT 1 COMMENT '数据层级范围:1包含下级,2仅本级' AFTER `data_scope_range`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `blade_mp_user_data_scope` (
|
||||
`id` bigint NOT NULL COMMENT '主键',
|
||||
`user_id` bigint NOT NULL COMMENT '小程序用户主键',
|
||||
`dept_id` bigint NOT NULL COMMENT '组织主键',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_blade_mp_user_data_scope_user_dept` (`user_id`, `dept_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='小程序用户组织数据权限';
|
||||
@@ -24,6 +24,8 @@ CREATE TABLE IF NOT EXISTS `blade_pre_settlement` (
|
||||
`payee_name` varchar(200) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '收款方',
|
||||
`currency` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'RMB' COMMENT '结算币种',
|
||||
`settlement_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '结算金额',
|
||||
`business_model` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '业务模式',
|
||||
`transport_type` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '运输方式',
|
||||
`local_currency` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'RMB' COMMENT '本位币',
|
||||
`local_settlement_amount` decimal(18,2) DEFAULT NULL COMMENT '本位币合计',
|
||||
`exchange_rate_date` date DEFAULT NULL COMMENT '汇率日期',
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
-- 预结算单、正式结算单:业务模式、运输方式(主表头字段)
|
||||
ALTER TABLE `blade_pre_settlement`
|
||||
ADD COLUMN `business_model` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '业务模式' AFTER `settlement_amount`,
|
||||
ADD COLUMN `transport_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '运输方式' AFTER `business_model`;
|
||||
|
||||
ALTER TABLE `blade_formal_settlement`
|
||||
ADD COLUMN `business_model` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '业务模式' AFTER `settlement_amount`,
|
||||
ADD COLUMN `transport_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '运输方式' AFTER `business_model`;
|
||||
@@ -7,8 +7,12 @@
|
||||
-- 3. 只统计各功能列表对应的主表。随主单保存的明细、联系人、附件、变更流水不重复计数。
|
||||
-- 4. 含已逻辑删除的数据,因为这些记录确实被创建过。
|
||||
-- 5. 客商档案菜单挂在「车/船务管理」下,这里按统计口径单独计入「客商管理」。
|
||||
-- 6. 未纳入:结算管理、首付款管理、在途管理,以及系统管理下的开票项目。
|
||||
-- 6. 未纳入:在途管理,以及系统管理下的开票项目。
|
||||
-- 7. 导入运单产生的运单计入「业务模块-运单管理」,不另计导入批次。
|
||||
-- 导入结算单产生的正式结算单计入「结算管理」的付款结算单或收款结算单,不另计导入批次。
|
||||
-- 8. 付款结算单、收款结算单共用 blade_formal_settlement,按 settlement_type 区分(payable/receivable)。
|
||||
-- 应收、应付共用 blade_receivable_payable_detail,同样按 settlement_type 区分。
|
||||
-- 9. 菜单名称为「收付款管理」,统计模块名与菜单一致。
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
@@ -396,7 +400,124 @@ SELECT t.create_user, 8, '运力模块', 4, '车辆调度', 'blade_transport_veh
|
||||
FROM blade_transport_vehicle_dispatch t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user;
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 9, '结算管理', 1, '应收', 'blade_receivable_payable_detail', COUNT(*)
|
||||
FROM blade_receivable_payable_detail t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
AND t.settlement_type = 'receivable'
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 9, '结算管理', 2, '应付', 'blade_receivable_payable_detail', COUNT(*)
|
||||
FROM blade_receivable_payable_detail t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
AND t.settlement_type = 'payable'
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 9, '结算管理', 3, '预付结算单', 'blade_pre_settlement', COUNT(*)
|
||||
FROM blade_pre_settlement t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 9, '结算管理', 4, '付款结算单', 'blade_formal_settlement', COUNT(*)
|
||||
FROM blade_formal_settlement t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
AND t.settlement_type = 'payable'
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 9, '结算管理', 5, '收款结算单', 'blade_formal_settlement', COUNT(*)
|
||||
FROM blade_formal_settlement t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
AND t.settlement_type = 'receivable'
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 9, '结算管理', 6, '运输对账', 'blade_transport_reconciliation', COUNT(*)
|
||||
FROM blade_transport_reconciliation t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 9, '结算管理', 7, '结算调整单', 'blade_settlement_adjustment', COUNT(*)
|
||||
FROM blade_settlement_adjustment t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 10, '收付款管理', 1, '付款管理', 'blade_payment_application', COUNT(*)
|
||||
FROM blade_payment_application t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 10, '收付款管理', 2, '开票管理', 'blade_invoice_application', COUNT(*)
|
||||
FROM blade_invoice_application t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 10, '收付款管理', 3, '收票管理', 'blade_invoice_receipt', COUNT(*)
|
||||
FROM blade_invoice_receipt t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 10, '收付款管理', 4, '收款流水', 'blade_kingdee_receipt_flow', COUNT(*)
|
||||
FROM blade_kingdee_receipt_flow t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 10, '收付款管理', 5, '认领记录', 'blade_receipt_claim', COUNT(*)
|
||||
FROM blade_receipt_claim t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 10, '收付款管理', 6, '汇票台账', 'blade_bill_ledger', COUNT(*)
|
||||
FROM blade_bill_ledger t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
INSERT INTO tmp_stat_detail (create_user, module_sort, module_name, biz_sort, biz_name, table_name, cnt)
|
||||
SELECT t.create_user, 10, '收付款管理', 7, '汇票付款', 'blade_bill_payment', COUNT(*)
|
||||
FROM blade_bill_payment t
|
||||
INNER JOIN tmp_stat_user u ON u.id = t.create_user
|
||||
WHERE t.create_time >= @stat_start AND t.create_time < @stat_end_exclusive
|
||||
GROUP BY t.create_user
|
||||
;
|
||||
|
||||
-- 结果 1:账号匹配情况。用户 ID 为空表示库中没有该账号。
|
||||
SELECT
|
||||
@@ -422,6 +543,8 @@ SELECT
|
||||
IFNULL(SUM(CASE WHEN d.module_name = '业务模块' THEN d.cnt ELSE 0 END), 0) AS `业务模块`,
|
||||
IFNULL(SUM(CASE WHEN d.module_name = '车/船务模块' THEN d.cnt ELSE 0 END), 0) AS `车/船务模块`,
|
||||
IFNULL(SUM(CASE WHEN d.module_name = '运力模块' THEN d.cnt ELSE 0 END), 0) AS `运力模块`,
|
||||
IFNULL(SUM(CASE WHEN d.module_name = '结算管理' THEN d.cnt ELSE 0 END), 0) AS `结算管理`,
|
||||
IFNULL(SUM(CASE WHEN d.module_name = '收付款管理' THEN d.cnt ELSE 0 END), 0) AS `收付款管理`,
|
||||
IFNULL(SUM(d.cnt), 0) AS `合计`
|
||||
FROM tmp_stat_account a
|
||||
LEFT JOIN tmp_stat_user u ON u.account = a.account
|
||||
|
||||
Reference in New Issue
Block a user