✨ 新增导入结算单,并修复运单超管组织过滤与计量单位软删恢复
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -36,3 +36,5 @@ Thumbs.db
|
||||
/.codex-work/
|
||||
.serena/
|
||||
.workbuddy/
|
||||
/data/
|
||||
/output/
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 导入结算单请求(列表筛选 + 确认/草稿入参)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "导入结算单请求")
|
||||
public class SettlementImportBatchRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Schema(description = "项目ID")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "结算类型 receivable/payable")
|
||||
private String settlementType;
|
||||
|
||||
@Schema(description = "结算单类型 formal/pre,应收仅 formal")
|
||||
private String settlementBillType;
|
||||
|
||||
@Schema(description = "客户合同ID")
|
||||
private Long customerContractId;
|
||||
|
||||
@Schema(description = "客户合同名称")
|
||||
private String customerContractName;
|
||||
|
||||
@Schema(description = "客户名称")
|
||||
private String customerName;
|
||||
|
||||
@Schema(description = "承运商合同ID")
|
||||
private Long carrierContractId;
|
||||
|
||||
@Schema(description = "承运商合同名称")
|
||||
private String carrierContractName;
|
||||
|
||||
@Schema(description = "承运商名称")
|
||||
private String carrierName;
|
||||
|
||||
@Schema(description = "运单模式:new=新运单,history=历史运单")
|
||||
private String waybillMode;
|
||||
|
||||
@Schema(description = "导入状态")
|
||||
private String importStatus;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "动态费用列名")
|
||||
private List<String> feeItemNames;
|
||||
|
||||
@Schema(description = "导入明细行")
|
||||
private List<Map<String, Object>> rows;
|
||||
|
||||
@Schema(description = "分批追加")
|
||||
private Boolean append;
|
||||
|
||||
@Schema(description = "列表筛选:项目名称")
|
||||
private String projectNameLike;
|
||||
|
||||
@Schema(description = "列表筛选:创建人")
|
||||
private Long createUser;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 导入结算单作废请求
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "导入结算单作废请求")
|
||||
public class SettlementImportVoidRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "批次主键")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "作废原因")
|
||||
private String reason;
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 导入结算单批次
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_settlement_import_batch")
|
||||
@Schema(description = "导入结算单批次")
|
||||
public class SettlementImportBatch extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Schema(description = "项目ID")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "结算类型 receivable/payable")
|
||||
private String settlementType;
|
||||
|
||||
@Schema(description = "结算单类型 formal/pre")
|
||||
private String settlementBillType;
|
||||
|
||||
@Schema(description = "客户合同ID")
|
||||
private Long customerContractId;
|
||||
|
||||
@Schema(description = "客户合同名称")
|
||||
private String customerContractName;
|
||||
|
||||
@Schema(description = "客户名称")
|
||||
private String customerName;
|
||||
|
||||
@Schema(description = "承运商合同ID")
|
||||
private Long carrierContractId;
|
||||
|
||||
@Schema(description = "承运商合同名称")
|
||||
private String carrierContractName;
|
||||
|
||||
@Schema(description = "承运商名称")
|
||||
private String carrierName;
|
||||
|
||||
@Schema(description = "运单模式 new")
|
||||
private String waybillMode;
|
||||
|
||||
@Schema(description = "导入状态 draft/completed/voided")
|
||||
private String importStatus;
|
||||
|
||||
@Schema(description = "运单数量")
|
||||
private Integer waybillCount;
|
||||
|
||||
@Schema(description = "明细数量")
|
||||
private Integer detailCount;
|
||||
|
||||
@Schema(description = "正式结算单ID")
|
||||
private Long formalSettlementId;
|
||||
|
||||
@Schema(description = "正式结算单号")
|
||||
private String formalSettlementNo;
|
||||
|
||||
@Schema(description = "预结算单ID")
|
||||
private Long preSettlementId;
|
||||
|
||||
@Schema(description = "预结算单号")
|
||||
private String preSettlementNo;
|
||||
|
||||
@Schema(description = "动态费用列名JSON")
|
||||
private String feeItemNamesJson;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "关联运单导入批次ID")
|
||||
private Long waybillImportBatchId;
|
||||
}
|
||||
+3
@@ -249,6 +249,9 @@ public class Waybill extends TenantEntity {
|
||||
@Schema(description = "导入批次ID")
|
||||
private Long importBatchId;
|
||||
|
||||
@Schema(description = "导入结算单批次ID")
|
||||
private Long settlementImportBatchId;
|
||||
|
||||
@Schema(description = "关联单号")
|
||||
private String relationNo;
|
||||
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.transport.pojo.entity.SettlementImportBatch;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 导入结算单批次视图
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "导入结算单批次视图")
|
||||
public class SettlementImportBatchVO extends SettlementImportBatch {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@Schema(description = "更新人姓名")
|
||||
private String updateUserName;
|
||||
|
||||
@Schema(description = "导入状态显示名")
|
||||
private String importStatusName;
|
||||
|
||||
@Schema(description = "结算类型显示名")
|
||||
private String settlementTypeName;
|
||||
|
||||
@Schema(description = "结算单类型显示名")
|
||||
private String settlementBillTypeName;
|
||||
|
||||
@Schema(description = "运单模式显示名")
|
||||
private String waybillModeName;
|
||||
|
||||
@Schema(description = "动态费用列名")
|
||||
private List<String> feeItemNames;
|
||||
}
|
||||
+20
@@ -34,6 +34,26 @@ import java.util.List;
|
||||
*/
|
||||
public interface MeasurementUnitMapper extends BaseMapper<MeasurementUnit> {
|
||||
|
||||
/**
|
||||
* 按编码查询计量单位(包含逻辑删除记录,用于新增恢复)。
|
||||
*/
|
||||
MeasurementUnit selectByUnitCodeIncludingDeleted(@Param("unitCode") String unitCode);
|
||||
|
||||
/**
|
||||
* 按名称查询计量单位(包含逻辑删除记录,用于新增恢复)。
|
||||
*/
|
||||
MeasurementUnit selectByUnitNameIncludingDeleted(@Param("unitName") String unitName);
|
||||
|
||||
/**
|
||||
* 恢复逻辑删除计量单位。
|
||||
*/
|
||||
int restoreById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 释放已软删除记录占用的单位名称,避免唯一索引冲突。
|
||||
*/
|
||||
int releaseUnitName(@Param("id") Long id, @Param("unitName") String unitName);
|
||||
|
||||
/**
|
||||
* 自定义分页
|
||||
*
|
||||
|
||||
+28
@@ -19,6 +19,34 @@
|
||||
<result column="remark" property="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByUnitCodeIncludingDeleted" resultType="org.springblade.system.pojo.entity.MeasurementUnit">
|
||||
SELECT *
|
||||
FROM blade_measurement_unit
|
||||
WHERE unit_code = #{unitCode}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="selectByUnitNameIncludingDeleted" resultType="org.springblade.system.pojo.entity.MeasurementUnit">
|
||||
SELECT *
|
||||
FROM blade_measurement_unit
|
||||
WHERE unit_name = #{unitName}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<update id="restoreById">
|
||||
UPDATE blade_measurement_unit
|
||||
SET is_deleted = 0
|
||||
WHERE id = #{id}
|
||||
AND is_deleted = 1
|
||||
</update>
|
||||
|
||||
<update id="releaseUnitName">
|
||||
UPDATE blade_measurement_unit
|
||||
SET unit_name = #{unitName}
|
||||
WHERE id = #{id}
|
||||
AND is_deleted = 1
|
||||
</update>
|
||||
|
||||
<select id="selectMeasurementUnitPage" resultMap="measurementUnitResultMap">
|
||||
SELECT
|
||||
mmu.id,
|
||||
|
||||
+32
@@ -62,10 +62,42 @@ public class MeasurementUnitServiceImpl extends BaseServiceImpl<MeasurementUnitM
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submit(MeasurementUnit measurementUnit) {
|
||||
prepare(measurementUnit);
|
||||
prepareSubmitTarget(measurementUnit);
|
||||
validate(measurementUnit);
|
||||
return saveOrUpdate(measurementUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码或名称已存在且为软删除时:取消软删除并复用原主键,后续按更新落库;
|
||||
* 存在且未删除时仍由唯一校验提示「已存在」。
|
||||
*/
|
||||
private void prepareSubmitTarget(MeasurementUnit measurementUnit) {
|
||||
if (Func.isNotEmpty(measurementUnit.getId())) {
|
||||
return;
|
||||
}
|
||||
MeasurementUnit existingByCode = baseMapper.selectByUnitCodeIncludingDeleted(measurementUnit.getUnitCode());
|
||||
MeasurementUnit existingByName = baseMapper.selectByUnitNameIncludingDeleted(measurementUnit.getUnitName());
|
||||
MeasurementUnit toRestore = null;
|
||||
if (existingByCode != null && Objects.equals(existingByCode.getIsDeleted(), 1)) {
|
||||
toRestore = existingByCode;
|
||||
} else if (existingByName != null && Objects.equals(existingByName.getIsDeleted(), 1)
|
||||
&& (existingByCode == null || Objects.equals(existingByCode.getId(), existingByName.getId()))) {
|
||||
toRestore = existingByName;
|
||||
}
|
||||
if (toRestore == null) {
|
||||
return;
|
||||
}
|
||||
// 复用编码恢复时,若另一条软删除记录占用了目标名称,先释放名称唯一索引
|
||||
if (existingByName != null && Objects.equals(existingByName.getIsDeleted(), 1)
|
||||
&& !Objects.equals(existingByName.getId(), toRestore.getId())) {
|
||||
String releasedName = existingByName.getUnitName() + "_DEL_" + existingByName.getId();
|
||||
baseMapper.releaseUnitName(existingByName.getId(), releasedName);
|
||||
}
|
||||
baseMapper.restoreById(toRestore.getId());
|
||||
measurementUnit.setId(toRestore.getId());
|
||||
measurementUnit.setIsDeleted(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean changeStatus(Long id, Integer status) {
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.pojo.dto.SettlementImportBatchRequest;
|
||||
import org.springblade.transport.pojo.dto.SettlementImportVoidRequest;
|
||||
import org.springblade.transport.pojo.vo.SettlementImportBatchVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillImportValidateResultVO;
|
||||
import org.springblade.transport.service.ISettlementImportBatchService;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 导入结算单控制器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@PreAuth(menu = "settlement_import")
|
||||
@RequestMapping("settlement-import")
|
||||
@Tag(name = "导入结算单", description = "导入结算单管理")
|
||||
public class SettlementImportController extends BladeController {
|
||||
|
||||
private final ISettlementImportBatchService settlementImportBatchService;
|
||||
|
||||
@GetMapping("/options")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "筛选项与表单选项")
|
||||
public R<Map<String, List<Map<String, Object>>>> options() {
|
||||
return R.data(settlementImportBatchService.options());
|
||||
}
|
||||
|
||||
@GetMapping("/next-code")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "下一个批次号")
|
||||
public R<String> nextCode() {
|
||||
return R.data(settlementImportBatchService.nextBatchNo());
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "导入结算单批次分页")
|
||||
public R<IPage<SettlementImportBatchVO>> list(SettlementImportBatchRequest request, Query query) {
|
||||
return R.data(settlementImportBatchService.page(Condition.getPage(query), request));
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "导入结算单批次详情")
|
||||
public R<SettlementImportBatchVO> detail(@RequestParam Long id) {
|
||||
return R.data(settlementImportBatchService.detail(id));
|
||||
}
|
||||
|
||||
@GetMapping("/export-template")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导出导入模板")
|
||||
public void exportTemplate(HttpServletResponse response) {
|
||||
settlementImportBatchService.exportTemplate(response);
|
||||
}
|
||||
|
||||
@PostMapping("/parse")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "解析导入 Excel")
|
||||
public R<Map<String, Object>> parse(@RequestPart("file") MultipartFile file) {
|
||||
return R.data(settlementImportBatchService.parse(file));
|
||||
}
|
||||
|
||||
@PostMapping("/validate")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "校验导入数据")
|
||||
public R<WaybillImportValidateResultVO> validate(@RequestBody SettlementImportBatchRequest request) {
|
||||
return R.data(settlementImportBatchService.validate(request));
|
||||
}
|
||||
|
||||
@PostMapping("/export-failures")
|
||||
@ApiOperationSupport(order = 8)
|
||||
@Operation(summary = "导出失败明细")
|
||||
public void exportFailures(@RequestBody SettlementImportBatchRequest request, HttpServletResponse response) {
|
||||
settlementImportBatchService.exportFailures(request, null, response);
|
||||
}
|
||||
|
||||
@PostMapping("/draft")
|
||||
@ApiOperationSupport(order = 9)
|
||||
@Operation(summary = "保存草稿")
|
||||
public R<SettlementImportBatchVO> draft(@RequestBody SettlementImportBatchRequest request) {
|
||||
return R.data(settlementImportBatchService.saveDraft(request));
|
||||
}
|
||||
|
||||
@PostMapping("/confirm")
|
||||
@ApiOperationSupport(order = 10)
|
||||
@Operation(summary = "确认导入结算单")
|
||||
public R<SettlementImportBatchVO> confirm(@RequestBody SettlementImportBatchRequest request) {
|
||||
return R.data(settlementImportBatchService.confirm(request));
|
||||
}
|
||||
|
||||
@PostMapping("/void")
|
||||
@ApiOperationSupport(order = 11)
|
||||
@Operation(summary = "作废导入结算单批次")
|
||||
public R voidBatch(@RequestBody SettlementImportVoidRequest request) {
|
||||
settlementImportBatchService.voidBatch(request);
|
||||
return R.success("作废成功");
|
||||
}
|
||||
}
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* 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.excel;
|
||||
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.springblade.common.excel.ImportCellReadException;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 导入结算单 Excel 读取器(Apache POI)。
|
||||
* <p>
|
||||
* 定位「*运输费」或「运输费」列,其后直到「结算金额」为动态费用列。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public final class SettlementImportFeeReader {
|
||||
|
||||
private static final DataFormatter DATA_FORMATTER = new DataFormatter(Locale.CHINA);
|
||||
|
||||
private SettlementImportFeeReader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析结果
|
||||
*/
|
||||
public static final class ParseResult {
|
||||
private final List<Map<String, Object>> rows;
|
||||
private final List<String> feeItemNames;
|
||||
private final List<String> warnings;
|
||||
|
||||
public ParseResult(List<Map<String, Object>> rows, List<String> feeItemNames, List<String> warnings) {
|
||||
this.rows = rows;
|
||||
this.feeItemNames = feeItemNames;
|
||||
this.warnings = warnings;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public List<String> getFeeItemNames() {
|
||||
return feeItemNames;
|
||||
}
|
||||
|
||||
public List<String> getWarnings() {
|
||||
return warnings;
|
||||
}
|
||||
}
|
||||
|
||||
public static ParseResult read(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new ServiceException("上传文件不能为空");
|
||||
}
|
||||
try (InputStream inputStream = file.getInputStream(); Workbook workbook = WorkbookFactory.create(inputStream)) {
|
||||
Sheet sheet = resolveDataSheet(workbook);
|
||||
Row headerRow = sheet.getRow(sheet.getFirstRowNum());
|
||||
if (headerRow == null) {
|
||||
throw new ServiceException("未识别到表头,请使用标准导入结算单模板");
|
||||
}
|
||||
Map<Integer, String> headers = readHeaderMap(headerRow);
|
||||
int transportFeeColumn = findTransportFeeColumn(headers);
|
||||
if (transportFeeColumn < 0) {
|
||||
throw new ServiceException("未找到「*运输费」或「运输费」列");
|
||||
}
|
||||
int settlementAmountColumn = findColumnExact(headers, "结算金额");
|
||||
List<Integer> feeColumns = new ArrayList<>();
|
||||
List<String> feeItemNames = new ArrayList<>();
|
||||
int endColumn = settlementAmountColumn > transportFeeColumn
|
||||
? settlementAmountColumn
|
||||
: headers.keySet().stream().mapToInt(Integer::intValue).max().orElse(transportFeeColumn) + 1;
|
||||
for (int column = transportFeeColumn + 1; column < endColumn; column++) {
|
||||
String name = headers.get(column);
|
||||
if (name == null || name.isBlank() || "结算金额".equals(name)) {
|
||||
continue;
|
||||
}
|
||||
feeColumns.add(column);
|
||||
feeItemNames.add(name);
|
||||
}
|
||||
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
int firstDataRow = sheet.getFirstRowNum() + 1;
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
for (int rowIndex = firstDataRow; rowIndex <= lastRow; rowIndex++) {
|
||||
Row row = sheet.getRow(rowIndex);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> mapped = mapRow(headers, feeColumns, row, rowIndex);
|
||||
if (mapped != null) {
|
||||
rows.add(mapped);
|
||||
}
|
||||
}
|
||||
return new ParseResult(rows, feeItemNames, List.of());
|
||||
} catch (ServiceException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
throw new ServiceException("读取导入结算单 Excel 失败:" + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 优先取含「*运输费/运输费」表头的工作表,否则取第一个非空表。 */
|
||||
private static Sheet resolveDataSheet(Workbook workbook) {
|
||||
Sheet fallback = null;
|
||||
for (int index = 0; index < workbook.getNumberOfSheets(); index++) {
|
||||
Sheet sheet = workbook.getSheetAt(index);
|
||||
if (sheet == null) {
|
||||
continue;
|
||||
}
|
||||
Row headerRow = sheet.getRow(sheet.getFirstRowNum());
|
||||
if (headerRow == null) {
|
||||
continue;
|
||||
}
|
||||
Map<Integer, String> headers = readHeaderMap(headerRow);
|
||||
if (findTransportFeeColumn(headers) >= 0) {
|
||||
return sheet;
|
||||
}
|
||||
if (fallback == null && !headers.isEmpty()) {
|
||||
fallback = sheet;
|
||||
}
|
||||
}
|
||||
if (fallback != null) {
|
||||
return fallback;
|
||||
}
|
||||
throw new ServiceException("Excel 中未找到有效工作表");
|
||||
}
|
||||
|
||||
private static Map<Integer, String> readHeaderMap(Row headerRow) {
|
||||
Map<Integer, String> headers = new LinkedHashMap<>();
|
||||
short lastCell = headerRow.getLastCellNum();
|
||||
for (int column = 0; column < lastCell; column++) {
|
||||
String value = normalizeHeader(cellString(headerRow.getCell(column)));
|
||||
if (value != null && !value.isBlank()) {
|
||||
headers.put(column, value);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/** 去掉 BOM/不可见空白,全角星号转半角,便于匹配「*运输费」。 */
|
||||
private static String normalizeHeader(String raw) {
|
||||
if (raw == null) {
|
||||
return null;
|
||||
}
|
||||
String text = raw.replace("\uFEFF", "")
|
||||
.replace('\u00A0', ' ')
|
||||
.replace('*', '*')
|
||||
.trim();
|
||||
return text.isEmpty() ? null : text;
|
||||
}
|
||||
|
||||
private static Map<String, Object> mapRow(Map<Integer, String> headers, List<Integer> feeColumns,
|
||||
Row row, int excelRowIndex) {
|
||||
Map<String, Object> mapped = new LinkedHashMap<>();
|
||||
Map<String, BigDecimal> feeItems = new LinkedHashMap<>();
|
||||
for (Map.Entry<Integer, String> entry : headers.entrySet()) {
|
||||
int column = entry.getKey();
|
||||
String trimmedHeader = entry.getValue();
|
||||
String cell = cellString(row.getCell(column));
|
||||
if (feeColumns.contains(column)) {
|
||||
feeItems.put(trimmedHeader, decimal(cell, trimmedHeader, excelRowIndex));
|
||||
continue;
|
||||
}
|
||||
String field = FIXED_HEADER_MAP.get(trimmedHeader);
|
||||
if (field == null) {
|
||||
continue;
|
||||
}
|
||||
if ("transportFee".equals(field) || "unitPrice".equals(field)
|
||||
|| "quantity".equals(field) || "mileage".equals(field)
|
||||
|| "settlementAmount".equals(field)) {
|
||||
mapped.put(field, decimal(cell, trimmedHeader, excelRowIndex));
|
||||
} else if ("serialNumber".equals(field)) {
|
||||
mapped.put(field, intValue(cell));
|
||||
} else {
|
||||
mapped.put(field, blankToNull(cell));
|
||||
}
|
||||
mapped.put(trimmedHeader, mapped.get(field));
|
||||
}
|
||||
|
||||
boolean hasBusinessValue = mapped.values().stream().anyMatch(value -> {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof String text) {
|
||||
return !text.isBlank();
|
||||
}
|
||||
return true;
|
||||
}) || feeItems.values().stream().anyMatch(amount -> amount != null && amount.compareTo(BigDecimal.ZERO) != 0);
|
||||
if (!hasBusinessValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BigDecimal transportFee = toDecimal(mapped.get("transportFee"));
|
||||
BigDecimal feeItemTotal = feeItems.values().stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal settlementAmount = transportFee.add(feeItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
mapped.put("settlementAmount", settlementAmount);
|
||||
mapped.put("结算金额", settlementAmount);
|
||||
mapped.put("feeItems", feeItems);
|
||||
mapped.put("freight", transportFee);
|
||||
mapped.put("运费", transportFee);
|
||||
mapped.put("*运输费", transportFee);
|
||||
mapped.put("运输费", transportFee);
|
||||
mapped.put("otherFeeTotal", feeItemTotal);
|
||||
mapped.put("其他费用合计", feeItemTotal);
|
||||
mapped.put("freightTotal", settlementAmount);
|
||||
mapped.put("运费合计", settlementAmount);
|
||||
if (mapped.get("unitPrice") != null) {
|
||||
mapped.put("单价", mapped.get("unitPrice"));
|
||||
mapped.put("运输单价", mapped.get("unitPrice"));
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
private static int findTransportFeeColumn(Map<Integer, String> headers) {
|
||||
int starred = findColumnExact(headers, "*运输费");
|
||||
if (starred >= 0) {
|
||||
return starred;
|
||||
}
|
||||
return findColumnExact(headers, "运输费");
|
||||
}
|
||||
|
||||
private static int findColumnExact(Map<Integer, String> headers, String header) {
|
||||
return headers.entrySet().stream()
|
||||
.filter(entry -> header.equals(entry.getValue()))
|
||||
.mapToInt(Map.Entry::getKey)
|
||||
.findFirst()
|
||||
.orElse(-1);
|
||||
}
|
||||
|
||||
private static String cellString(Cell cell) {
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
String text = DATA_FORMATTER.formatCellValue(cell);
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
text = text.trim();
|
||||
return text.isEmpty() ? null : text;
|
||||
}
|
||||
|
||||
private static BigDecimal decimal(String cell, String columnName, Integer excelRowIndex) {
|
||||
if (cell == null || cell.isBlank()) {
|
||||
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
String normalized = cell.trim().replace(",", "");
|
||||
try {
|
||||
return new BigDecimal(normalized).setScale(2, RoundingMode.HALF_UP);
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new ImportCellReadException(excelRowIndex, columnName, cell.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private static Integer intValue(String cell) {
|
||||
if (cell == null || cell.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(cell.trim().replace(",", "")).intValue();
|
||||
} catch (NumberFormatException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String blankToNull(String cell) {
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
String text = cell.trim();
|
||||
return text.isEmpty() ? null : text;
|
||||
}
|
||||
|
||||
private static BigDecimal toDecimal(Object value) {
|
||||
if (value == null) {
|
||||
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
if (value instanceof BigDecimal bigDecimal) {
|
||||
return bigDecimal;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(String.valueOf(value).trim()).setScale(2, RoundingMode.HALF_UP);
|
||||
} catch (NumberFormatException exception) {
|
||||
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, String> FIXED_HEADER_MAP = buildFixedHeaderMap();
|
||||
|
||||
private static Map<String, String> buildFixedHeaderMap() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("序号", "serialNumber");
|
||||
map.put("原始单号", "originalNo");
|
||||
map.put("配载标识号", "loadingIdentifier");
|
||||
map.put("*车牌号/船班号/船次号/班次号", "vehicleNo");
|
||||
map.put("车牌号/船班号/船次号/班次号", "vehicleNo");
|
||||
map.put("*车牌号/航班号/船号/班列号", "vehicleNo");
|
||||
map.put("车牌号/航班号/船号/班列号", "vehicleNo");
|
||||
map.put("*运输方式", "transportType");
|
||||
map.put("运输方式", "transportType");
|
||||
map.put("运输类型", "transportType");
|
||||
map.put("*运输类型", "transportType");
|
||||
map.put("司机/船长姓名", "driverName");
|
||||
map.put("司机/船长手机号", "driverPhone");
|
||||
map.put("*发货地址", "departureAddress");
|
||||
map.put("发货地址", "departureAddress");
|
||||
map.put("发货联系人", "departureContact");
|
||||
map.put("发货联系人电话", "departurePhone");
|
||||
map.put("*到货地址", "arrivalAddress");
|
||||
map.put("到货地址", "arrivalAddress");
|
||||
map.put("收货联系人", "arrivalContact");
|
||||
map.put("收货联系人电话", "arrivalPhone");
|
||||
map.put("*货物名称", "cargoName");
|
||||
map.put("货物名称", "cargoName");
|
||||
map.put("*货物类型", "cargoType");
|
||||
map.put("货物类型", "cargoType");
|
||||
map.put("包装", "packageType");
|
||||
map.put("*数量", "quantity");
|
||||
map.put("数量", "quantity");
|
||||
map.put("*数量单位", "quantityUnit");
|
||||
map.put("数量单位", "quantityUnit");
|
||||
map.put("规格", "specification");
|
||||
map.put("型号", "model");
|
||||
map.put("里程(km)", "mileage");
|
||||
map.put("里程", "mileage");
|
||||
map.put("运输单价", "unitPrice");
|
||||
map.put("单价", "unitPrice");
|
||||
map.put("*运输费", "transportFee");
|
||||
map.put("运输费", "transportFee");
|
||||
map.put("结算金额", "settlementAmount");
|
||||
map.put("*实际发货时间", "actualStartDate");
|
||||
map.put("实际发货时间", "actualStartDate");
|
||||
map.put("*实际完成时间", "actualEndDate");
|
||||
map.put("实际完成时间", "actualEndDate");
|
||||
map.put("预计发货时间", "planStartDate");
|
||||
map.put("预计完成时间", "planEndDate");
|
||||
map.put("备注", "remark");
|
||||
map.put("同一运单标识号", "waybillIdentifier");
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* 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.excel;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnore;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.idev.excel.annotation.write.style.ColumnWidth;
|
||||
import cn.idev.excel.annotation.write.style.ContentRowHeight;
|
||||
import cn.idev.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 导入结算单 Excel 模板(固定列 + 运输单价 + *运输费 + 示例费用项)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ColumnWidth(22)
|
||||
@HeadRowHeight(28)
|
||||
@ContentRowHeight(18)
|
||||
public class SettlementImportTemplateExcel implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty("序号")
|
||||
private Integer serialNumber;
|
||||
@ExcelProperty("原始单号")
|
||||
private String originalNo;
|
||||
@ExcelProperty("配载标识号")
|
||||
private String loadingIdentifier;
|
||||
@ExcelProperty("*车牌号/航班号/船号/班列号")
|
||||
private String vehicleNo;
|
||||
@ExcelProperty("*运输方式")
|
||||
private String transportType;
|
||||
@ExcelProperty("司机/船长姓名")
|
||||
private String driverName;
|
||||
@ExcelProperty("司机/船长手机号")
|
||||
private String driverPhone;
|
||||
@ExcelProperty("*发货地址")
|
||||
private String departureAddress;
|
||||
@ExcelProperty("发货联系人")
|
||||
private String departureContact;
|
||||
@ExcelProperty("发货联系人电话")
|
||||
private String departurePhone;
|
||||
@ExcelProperty("*到货地址")
|
||||
private String arrivalAddress;
|
||||
@ExcelProperty("收货联系人")
|
||||
private String arrivalContact;
|
||||
@ExcelProperty("收货联系人电话")
|
||||
private String arrivalPhone;
|
||||
@ExcelProperty("*货物名称")
|
||||
private String cargoName;
|
||||
@ExcelProperty("*货物类型")
|
||||
private String cargoType;
|
||||
@ExcelProperty("包装")
|
||||
private String packageType;
|
||||
@ExcelProperty("*数量")
|
||||
private BigDecimal quantity;
|
||||
@ExcelProperty("*数量单位")
|
||||
private String quantityUnit;
|
||||
@ExcelProperty("规格")
|
||||
private String specification;
|
||||
@ExcelProperty("型号")
|
||||
private String model;
|
||||
@ExcelProperty("里程(km)")
|
||||
private BigDecimal mileage;
|
||||
@ExcelProperty("运输单价")
|
||||
private BigDecimal unitPrice;
|
||||
@ExcelProperty("*运输费")
|
||||
private BigDecimal transportFee;
|
||||
@ExcelProperty("装卸费")
|
||||
private BigDecimal loadingFee;
|
||||
@ExcelProperty("过路费")
|
||||
private BigDecimal tollFee;
|
||||
@ExcelProperty("结算金额")
|
||||
private BigDecimal settlementAmount;
|
||||
@ExcelProperty("*实际发货时间")
|
||||
private String actualStartDate;
|
||||
@ExcelProperty("*实际完成时间")
|
||||
private String actualEndDate;
|
||||
@ExcelProperty("预计发货时间")
|
||||
private String planStartDate;
|
||||
@ExcelProperty("预计完成时间")
|
||||
private String planEndDate;
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
@ExcelProperty("同一运单标识号")
|
||||
private String waybillIdentifier;
|
||||
|
||||
/** 导入失败原因(不导出到模板,仅用于失败明细) */
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
}
|
||||
+1
-2
@@ -101,8 +101,7 @@
|
||||
approved_time,
|
||||
kingdee_status,
|
||||
kingdee_fail_reason,
|
||||
kingdee_sync_time
|
||||
approved_time,
|
||||
kingdee_sync_time,
|
||||
(
|
||||
SELECT s.review_status
|
||||
FROM blade_customer_credit_score s
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.springblade.transport.pojo.entity.SettlementImportBatch;
|
||||
|
||||
/**
|
||||
* 导入结算单批次 Mapper
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Mapper
|
||||
public interface SettlementImportBatchMapper extends BaseMapper<SettlementImportBatch> {
|
||||
|
||||
/**
|
||||
* 查询指定租户、指定日期前缀下已使用的最大批次流水号。
|
||||
* 不过滤逻辑删除数据,避免唯一索引仍占用历史编号时重复生成。
|
||||
*/
|
||||
@Select("SELECT COALESCE(MAX(CAST(SUBSTRING(batch_no, CHAR_LENGTH(#{prefix}) + 1) AS UNSIGNED)), 0) "
|
||||
+ "FROM blade_settlement_import_batch WHERE tenant_id = #{tenantId} "
|
||||
+ "AND batch_no LIKE CONCAT(#{prefix}, '%')")
|
||||
Long selectMaxDailySequence(@Param("tenantId") String tenantId, @Param("prefix") String prefix);
|
||||
}
|
||||
+15
@@ -101,4 +101,19 @@ public interface IReceivablePayableDetailService extends BaseService<ReceivableP
|
||||
|
||||
/** 关闭总单调度后生成总单客户合同应收,并按所属运单记录的承运商合同生成应付。 */
|
||||
void generateForClosedMasterOrder(MasterOrder masterOrder);
|
||||
|
||||
/**
|
||||
* 结算导入:按导入单据构建/刷新「结算导入」来源的应收应付明细(运输费+费用项金额以导入数据为准)。
|
||||
* <p>
|
||||
* 历史运单若已有系统生成明细,不覆盖原明细,另建结算导入明细;运单已进入结算则拒绝重复导入。
|
||||
* feeRows 每项含 waybillId、transportFee、unitPrice、feeItems(Map)、remark。
|
||||
* </p>
|
||||
*
|
||||
* @param settlementType 结算类型 receivable/payable
|
||||
* @param contractId 结算合同ID
|
||||
* @param feeRows 费用行
|
||||
* @return 明细主键列表(新建或刷新后的结算导入明细)
|
||||
*/
|
||||
List<Long> createDetailsFromSettlementImport(String settlementType, Long contractId,
|
||||
List<Map<String, Object>> feeRows);
|
||||
}
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* 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.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.transport.pojo.dto.SettlementImportBatchRequest;
|
||||
import org.springblade.transport.pojo.dto.SettlementImportVoidRequest;
|
||||
import org.springblade.transport.pojo.entity.SettlementImportBatch;
|
||||
import org.springblade.transport.pojo.vo.SettlementImportBatchVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillImportValidateResultVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 导入结算单批次服务
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface ISettlementImportBatchService extends BaseService<SettlementImportBatch> {
|
||||
|
||||
Map<String, List<Map<String, Object>>> options();
|
||||
|
||||
String nextBatchNo();
|
||||
|
||||
IPage<SettlementImportBatchVO> page(IPage<SettlementImportBatch> page, SettlementImportBatchRequest request);
|
||||
|
||||
SettlementImportBatchVO detail(Long id);
|
||||
|
||||
void exportTemplate(HttpServletResponse response);
|
||||
|
||||
Map<String, Object> parse(MultipartFile file);
|
||||
|
||||
WaybillImportValidateResultVO validate(SettlementImportBatchRequest request);
|
||||
|
||||
void exportFailures(SettlementImportBatchRequest request, MultipartFile file, HttpServletResponse response);
|
||||
|
||||
SettlementImportBatchVO saveDraft(SettlementImportBatchRequest request);
|
||||
|
||||
SettlementImportBatchVO confirm(SettlementImportBatchRequest request);
|
||||
|
||||
void voidBatch(SettlementImportVoidRequest request);
|
||||
}
|
||||
+4
-2
@@ -437,9 +437,11 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
} else if (Func.isNotEmpty(contractManage.getOrganizationId())) {
|
||||
queryWrapper.eq(ContractManage::getOrganizationId, contractManage.getOrganizationId());
|
||||
}
|
||||
queryWrapper.like(Func.isNotEmpty(contractManage.getContractNo()), ContractManage::getContractNo, contractManage.getContractNo())
|
||||
queryWrapper.eq(Func.isNotEmpty(contractManage.getProjectId()), ContractManage::getProjectId, contractManage.getProjectId())
|
||||
.like(Func.isNotEmpty(contractManage.getContractNo()), ContractManage::getContractNo, contractManage.getContractNo())
|
||||
.like(Func.isNotEmpty(contractManage.getContractName()), ContractManage::getContractName, contractManage.getContractName())
|
||||
.like(Func.isNotEmpty(contractManage.getProjectName()), ContractManage::getProjectName, contractManage.getProjectName())
|
||||
.like(Func.isNotEmpty(contractManage.getProjectName()) && Func.isEmpty(contractManage.getProjectId()),
|
||||
ContractManage::getProjectName, contractManage.getProjectName())
|
||||
.like(Func.isNotEmpty(contractManage.getOrganizationName()), ContractManage::getOrganizationName, contractManage.getOrganizationName())
|
||||
.eq(Func.isNotEmpty(contractManage.getContractCategory()), ContractManage::getContractCategory, contractManage.getContractCategory())
|
||||
.eq(Func.isNotEmpty(contractManage.getSignType()), ContractManage::getSignType, contractManage.getSignType())
|
||||
|
||||
+180
@@ -847,6 +847,186 @@ public class ReceivablePayableDetailServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
private static final String SOURCE_SETTLEMENT_IMPORT = "结算导入";
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<Long> createDetailsFromSettlementImport(String settlementType, Long contractId,
|
||||
List<Map<String, Object>> feeRows) {
|
||||
if (Func.isEmpty(settlementType) || Func.isEmpty(contractId)) {
|
||||
throw new ServiceException("结算类型与合同不能为空");
|
||||
}
|
||||
if (Func.isEmpty(feeRows)) {
|
||||
return List.of();
|
||||
}
|
||||
ContractManage contract = contractManageService.getById(contractId);
|
||||
if (contract == null || Objects.equals(contract.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("合同不存在");
|
||||
}
|
||||
String targetSettlementType = settlementType(settlementType);
|
||||
Long currentUserId = AuthUtil.getUserId();
|
||||
Date generateTime = new Date();
|
||||
List<Long> createdIds = new ArrayList<>();
|
||||
for (Map<String, Object> feeRow : feeRows) {
|
||||
if (feeRow == null) {
|
||||
continue;
|
||||
}
|
||||
Long waybillId = toLong(feeRow.get("waybillId"));
|
||||
if (waybillId == null) {
|
||||
continue;
|
||||
}
|
||||
Waybill waybill = waybillService.getById(waybillId);
|
||||
if (waybill == null || Objects.equals(waybill.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("运单不存在:" + waybillId);
|
||||
}
|
||||
assertWaybillNotSettledForImport(waybill, targetSettlementType);
|
||||
|
||||
BigDecimal transportFee = money(decimal(feeRow.get("transportFee")));
|
||||
BigDecimal unitPrice = money(decimal(feeRow.get("unitPrice")));
|
||||
Map<String, BigDecimal> feeItems = normalizeFeeItems(toFeeItemMap(feeRow.get("feeItems")));
|
||||
BigDecimal feeItemTotal = feeItems.values().stream().map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal afterAmount = transportFee.add(feeItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
ReceivablePayableCargoFee cargoFee = buildSettlementImportCargoFee(
|
||||
waybill, transportFee, unitPrice, feeItems, afterAmount, stringValue(feeRow.get("remark")));
|
||||
|
||||
ReceivablePayableDetail importDetail = getOne(Wrappers.<ReceivablePayableDetail>lambdaQuery()
|
||||
.eq(ReceivablePayableDetail::getWaybillId, waybillId)
|
||||
.eq(ReceivablePayableDetail::getSettlementType, targetSettlementType)
|
||||
.eq(ReceivablePayableDetail::getSourceType, SOURCE_SETTLEMENT_IMPORT)
|
||||
.eq(ReceivablePayableDetail::getIsDeleted, 0)
|
||||
.orderByDesc(ReceivablePayableDetail::getCreateTime)
|
||||
.last("limit 1"), false);
|
||||
if (importDetail != null) {
|
||||
refreshSettlementImportDetail(importDetail, waybill, contract, targetSettlementType,
|
||||
cargoFee, unitPrice, stringValue(feeRow.get("remark")), currentUserId, generateTime);
|
||||
createdIds.add(importDetail.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
ReceivablePayableDetail detail = buildDetail(waybill, contract, targetSettlementType,
|
||||
List.of(cargoFee), unitPrice);
|
||||
detail.setSourceType(SOURCE_SETTLEMENT_IMPORT);
|
||||
detail.setSettlementStatus("pending");
|
||||
if (Func.isNotEmpty(stringValue(feeRow.get("remark")))) {
|
||||
detail.setRemark(stringValue(feeRow.get("remark")));
|
||||
}
|
||||
saveAutomaticDetail(detail, List.of(cargoFee), false, currentUserId, generateTime);
|
||||
createdIds.add(detail.getId());
|
||||
}
|
||||
return createdIds;
|
||||
}
|
||||
|
||||
/** 运单在该结算类型下已进入预/正式结算时,禁止再导入。 */
|
||||
private void assertWaybillNotSettledForImport(Waybill waybill, String settlementType) {
|
||||
List<ReceivablePayableDetail> details = list(Wrappers.<ReceivablePayableDetail>lambdaQuery()
|
||||
.eq(ReceivablePayableDetail::getWaybillId, waybill.getId())
|
||||
.eq(ReceivablePayableDetail::getSettlementType, settlementType)
|
||||
.eq(ReceivablePayableDetail::getIsDeleted, 0));
|
||||
for (ReceivablePayableDetail detail : details) {
|
||||
String settlementStatus = Func.toStr(detail.getSettlementStatus(), "pending");
|
||||
if (Func.isNotEmpty(detail.getFormalSettlementNo())
|
||||
|| Func.isNotEmpty(detail.getPreSettlementNo())
|
||||
|| List.of("pre_settled", "formal_settled", "closed").contains(settlementStatus)) {
|
||||
throw new ServiceException("运单" + Func.toStr(waybill.getWaybillNo(), String.valueOf(waybill.getId()))
|
||||
+ "已进入结算,无法重复导入");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ReceivablePayableCargoFee buildSettlementImportCargoFee(Waybill waybill, BigDecimal transportFee,
|
||||
BigDecimal unitPrice, Map<String, BigDecimal> feeItems,
|
||||
BigDecimal afterAmount, String remark) {
|
||||
ReceivablePayableCargoFee cargoFee = new ReceivablePayableCargoFee();
|
||||
cargoFee.setWaybillId(waybill.getId());
|
||||
cargoFee.setLineNo("0001");
|
||||
cargoFee.setDataSource(FEE_SOURCE_MANUAL);
|
||||
cargoFee.setCargoName(waybill.getCargoName());
|
||||
cargoFee.setCargoType(waybill.getCargoType());
|
||||
cargoFee.setSpecification(waybill.getSpecification());
|
||||
cargoFee.setModel(waybill.getModel());
|
||||
cargoFee.setBillingFactor("按重量");
|
||||
cargoFee.setBillingType("固定单价");
|
||||
cargoFee.setTransportQuantity(money(waybill.getQuantity()));
|
||||
cargoFee.setQuantityUnit(waybill.getQuantityUnit());
|
||||
cargoFee.setPriceUnit(waybill.getPriceUnit());
|
||||
cargoFee.setUnitPrice(unitPrice);
|
||||
cargoFee.setMileage(normalizeGeneratedMileage(waybill.getMileage()));
|
||||
cargoFee.setFreightAmount(transportFee);
|
||||
cargoFee.setFeeItemsJson(JsonUtil.toJson(feeItems));
|
||||
cargoFee.setOriginalAmount(afterAmount);
|
||||
cargoFee.setAdjustAmount(BigDecimal.ZERO.setScale(2));
|
||||
cargoFee.setAfterAmount(afterAmount);
|
||||
cargoFee.setRemark(remark);
|
||||
return cargoFee;
|
||||
}
|
||||
|
||||
/** 刷新已有结算导入明细:费用项目与金额始终覆盖为本次导入数据。 */
|
||||
private void refreshSettlementImportDetail(ReceivablePayableDetail detail, Waybill waybill, ContractManage contract,
|
||||
String settlementType, ReceivablePayableCargoFee cargoFee,
|
||||
BigDecimal unitPrice, String remark, Long currentUserId, Date generateTime) {
|
||||
ReceivablePayableDetail rebuilt = buildDetail(waybill, contract, settlementType, List.of(cargoFee), unitPrice);
|
||||
detail.setFreightAmount(rebuilt.getFreightAmount());
|
||||
detail.setOtherFeeAmount(rebuilt.getOtherFeeAmount());
|
||||
detail.setTotalAmount(rebuilt.getTotalAmount());
|
||||
detail.setUnitPrice(rebuilt.getUnitPrice());
|
||||
detail.setCurrency(rebuilt.getCurrency());
|
||||
detail.setFeeItemsJson(rebuilt.getFeeItemsJson());
|
||||
detail.setContractId(rebuilt.getContractId());
|
||||
detail.setContractNo(rebuilt.getContractNo());
|
||||
detail.setContractName(rebuilt.getContractName());
|
||||
detail.setCustomerName(rebuilt.getCustomerName());
|
||||
detail.setSourceType(SOURCE_SETTLEMENT_IMPORT);
|
||||
detail.setSettlementStatus("pending");
|
||||
if (Func.isNotEmpty(remark)) {
|
||||
detail.setRemark(remark);
|
||||
}
|
||||
updateById(detail);
|
||||
cargoFeeMapper.delete(Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
|
||||
.eq(ReceivablePayableCargoFee::getDetailId, detail.getId()));
|
||||
ReceivablePayableCargoFee copy = BeanUtil.copyProperties(cargoFee, ReceivablePayableCargoFee.class);
|
||||
copy.setId(null);
|
||||
copy.setDetailId(detail.getId());
|
||||
fillGeneratedAuditFields(copy, currentUserId, generateTime);
|
||||
cargoFeeMapper.insert(copy);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, BigDecimal> toFeeItemMap(Object value) {
|
||||
Map<String, BigDecimal> result = new LinkedHashMap<>();
|
||||
if (!(value instanceof Map<?, ?> raw)) {
|
||||
return result;
|
||||
}
|
||||
raw.forEach((key, amount) -> {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
result.put(String.valueOf(key), decimal(amount));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private Long toLong(Object value) {
|
||||
if (value == null || "".equals(value)) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Number number) {
|
||||
return number.longValue();
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(String.valueOf(value).trim());
|
||||
} catch (NumberFormatException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String stringValue(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String text = String.valueOf(value).trim();
|
||||
return text.isEmpty() ? null : text;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW,
|
||||
rollbackFor = Exception.class)
|
||||
|
||||
+1144
File diff suppressed because it is too large
Load Diff
+9
-10
@@ -1211,15 +1211,14 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
|
||||
@Override
|
||||
public List<WaybillExcel> exportWaybill(WaybillVO waybill, String ids) {
|
||||
TransportBusinessSupport.validateAllDept(waybill.getAllDept(), "运单管理");
|
||||
List<Long> idList = Func.toLongList(ids);
|
||||
LambdaQueryWrapper<Waybill> queryWrapper;
|
||||
if (Func.isNotEmpty(idList)) {
|
||||
queryWrapper = Wrappers.<Waybill>lambdaQuery().eq(Waybill::getIsDeleted, 0);
|
||||
if (!Objects.equals(waybill.getAllDept(), 1)) {
|
||||
queryWrapper.eq(Waybill::getDeptId, TransportBusinessSupport.currentDeptId("运单管理"));
|
||||
} else if (Func.isNotEmpty(waybill.getDeptId())) {
|
||||
queryWrapper.eq(Waybill::getDeptId, waybill.getDeptId());
|
||||
List<Long> deptIdList = TransportBusinessSupport.resolveDeptFilterIds(
|
||||
waybill.getAllDept(), waybill.getDeptId(), "运单管理");
|
||||
if (Func.isNotEmpty(deptIdList)) {
|
||||
queryWrapper.eq(Waybill::getDeptId, deptIdList.get(0));
|
||||
}
|
||||
queryWrapper.in(Waybill::getId, idList).orderByDesc(Waybill::getCreateTime);
|
||||
} else {
|
||||
@@ -1604,12 +1603,12 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<Waybill> buildQuery(WaybillVO waybill) {
|
||||
TransportBusinessSupport.validateAllDept(waybill.getAllDept(), "运单管理");
|
||||
LambdaQueryWrapper<Waybill> queryWrapper = Wrappers.<Waybill>lambdaQuery().eq(Waybill::getIsDeleted, 0);
|
||||
if (!Objects.equals(waybill.getAllDept(), 1)) {
|
||||
queryWrapper.eq(Waybill::getDeptId, TransportBusinessSupport.currentDeptId("运单管理"));
|
||||
} else if (Func.isNotEmpty(waybill.getDeptId())) {
|
||||
queryWrapper.eq(Waybill::getDeptId, waybill.getDeptId());
|
||||
// 与常用线路等模块一致:超管默认不按组织过滤;普通用户 allDept!=1 时强制当前组织
|
||||
List<Long> deptIdList = TransportBusinessSupport.resolveDeptFilterIds(
|
||||
waybill.getAllDept(), waybill.getDeptId(), "运单管理");
|
||||
if (Func.isNotEmpty(deptIdList)) {
|
||||
queryWrapper.eq(Waybill::getDeptId, deptIdList.get(0));
|
||||
}
|
||||
if (Func.isNotEmpty(waybill.getWaybillNo())) {
|
||||
queryWrapper.eq(Waybill::getWaybillNo, waybill.getWaybillNo());
|
||||
|
||||
@@ -9,8 +9,16 @@ ALTER TABLE `blade_customer_archive`
|
||||
|
||||
CREATE INDEX idx_kingdee_status ON blade_customer_archive (kingdee_status);
|
||||
|
||||
-- 列表「同步金蝶」按钮权限(审批通过后推送失败的重推入口;parent_id 通过菜单 code 关联,避免写死 id)
|
||||
-- 列表「同步金蝶」按钮权限(审批通过且未推送/推送失败时可点;parent_id 通过菜单 code 关联)
|
||||
INSERT INTO blade_menu (id, parent_id, code, name, alias, path, source, sort, category, action, is_open, component, remark, is_deleted)
|
||||
SELECT 2075431800000000001, m.id, 'customer_archive_sync', '同步金蝶', 'customer_archive_sync', '', '', 1, 2, 0, 1, '', '', 0
|
||||
SELECT 2077600000000000011, m.id, 'customer_archive_sync', '同步金蝶', 'customer_archive_sync', '', '', 10, 2, 0, 1, NULL, '', 0
|
||||
FROM blade_menu m
|
||||
WHERE m.code = 'customer_archive' AND m.is_deleted = 0;
|
||||
WHERE m.code = 'customer_archive' AND m.is_deleted = 0
|
||||
LIMIT 1
|
||||
ON DUPLICATE KEY UPDATE
|
||||
parent_id = VALUES(parent_id),
|
||||
name = VALUES(name),
|
||||
alias = VALUES(alias),
|
||||
sort = VALUES(sort),
|
||||
category = VALUES(category),
|
||||
is_deleted = 0;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 修复「同步金蝶」按钮菜单:按 code 幂等补齐/纠正,避免权限勾了但不生效
|
||||
-- 执行后需:角色重新勾选「同步金蝶」→ 用户重新登录
|
||||
|
||||
INSERT INTO blade_menu (id, parent_id, code, name, alias, path, source, sort, category, action, is_open, component, remark, is_deleted)
|
||||
SELECT 2077600000000000011, m.id, 'customer_archive_sync', '同步金蝶', 'customer_archive_sync', '', '', 10, 2, 0, 1, NULL, '', 0
|
||||
FROM blade_menu m
|
||||
WHERE m.code = 'customer_archive' AND m.is_deleted = 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM blade_menu x WHERE x.code = 'customer_archive_sync' AND x.is_deleted = 0
|
||||
)
|
||||
LIMIT 1;
|
||||
|
||||
UPDATE blade_menu child
|
||||
INNER JOIN blade_menu parent ON parent.code = 'customer_archive' AND parent.is_deleted = 0
|
||||
SET
|
||||
child.parent_id = parent.id,
|
||||
child.name = '同步金蝶',
|
||||
child.alias = 'customer_archive_sync',
|
||||
child.category = 2,
|
||||
child.sort = 10,
|
||||
child.is_deleted = 0
|
||||
WHERE child.code = 'customer_archive_sync';
|
||||
@@ -0,0 +1,60 @@
|
||||
-- 导入结算单批次表 + 菜单
|
||||
CREATE TABLE IF NOT EXISTS `blade_settlement_import_batch` (
|
||||
`id` bigint NOT NULL COMMENT '主键',
|
||||
`tenant_id` varchar(12) NOT NULL DEFAULT '000000' COMMENT '租户ID',
|
||||
`batch_no` varchar(64) NOT NULL COMMENT '批次号 DJS+yyyyMMdd+序号',
|
||||
`project_id` bigint DEFAULT NULL COMMENT '项目ID',
|
||||
`project_name` varchar(200) DEFAULT NULL COMMENT '项目名称',
|
||||
`settlement_type` varchar(30) NOT NULL COMMENT 'receivable/payable',
|
||||
`settlement_bill_type` varchar(20) NOT NULL DEFAULT 'formal' COMMENT '结算单类型 formal/pre',
|
||||
`customer_contract_id` bigint DEFAULT NULL COMMENT '客户合同ID',
|
||||
`customer_contract_name` varchar(200) DEFAULT NULL COMMENT '客户合同名称',
|
||||
`customer_name` varchar(200) DEFAULT NULL COMMENT '客户名称',
|
||||
`carrier_contract_id` bigint DEFAULT NULL COMMENT '承运商合同ID',
|
||||
`carrier_contract_name` varchar(200) DEFAULT NULL COMMENT '承运商合同名称',
|
||||
`carrier_name` varchar(200) DEFAULT NULL COMMENT '承运商名称',
|
||||
`waybill_mode` varchar(20) DEFAULT 'new' COMMENT '运单模式 new/history',
|
||||
`import_status` varchar(32) NOT NULL COMMENT 'draft/completed/voided',
|
||||
`waybill_count` int DEFAULT 0 COMMENT '运单数量',
|
||||
`detail_count` int DEFAULT 0 COMMENT '明细数量',
|
||||
`formal_settlement_id` bigint DEFAULT NULL COMMENT '正式结算单ID',
|
||||
`formal_settlement_no` varchar(100) DEFAULT NULL COMMENT '正式结算单号',
|
||||
`pre_settlement_id` bigint DEFAULT NULL COMMENT '预结算单ID',
|
||||
`pre_settlement_no` varchar(100) DEFAULT NULL COMMENT '预结算单号',
|
||||
`fee_item_names_json` longtext COMMENT '动态费用列名JSON数组',
|
||||
`remark` varchar(200) DEFAULT NULL COMMENT '备注',
|
||||
`waybill_import_batch_id` bigint DEFAULT NULL COMMENT '关联底层运单导入批次,可空',
|
||||
`create_user` bigint DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_user` bigint DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`status` int DEFAULT 1 COMMENT '业务状态',
|
||||
`is_deleted` int DEFAULT 0 COMMENT '是否已删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_settlement_import_batch_no` (`tenant_id`,`batch_no`),
|
||||
KEY `idx_settlement_import_project` (`project_id`),
|
||||
KEY `idx_settlement_import_formal` (`formal_settlement_id`),
|
||||
KEY `idx_settlement_import_pre` (`pre_settlement_id`),
|
||||
KEY `idx_settlement_import_waybill_batch` (`waybill_import_batch_id`),
|
||||
KEY `idx_settlement_import_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='导入结算单批次';
|
||||
|
||||
-- 已建表环境增量字段(列已存在可忽略报错)
|
||||
ALTER TABLE `blade_settlement_import_batch` ADD COLUMN `settlement_bill_type` varchar(20) NOT NULL DEFAULT 'formal' COMMENT '结算单类型 formal/pre' AFTER `settlement_type`;
|
||||
ALTER TABLE `blade_settlement_import_batch` ADD COLUMN `pre_settlement_id` bigint DEFAULT NULL COMMENT '预结算单ID' AFTER `formal_settlement_no`;
|
||||
ALTER TABLE `blade_settlement_import_batch` ADD COLUMN `pre_settlement_no` varchar(100) DEFAULT NULL COMMENT '预结算单号' AFTER `pre_settlement_id`;
|
||||
ALTER TABLE `blade_settlement_import_batch` ADD KEY `idx_settlement_import_pre` (`pre_settlement_id`);
|
||||
|
||||
-- 运单关联导入结算批次(可选,若列已存在可忽略报错)
|
||||
ALTER TABLE `blade_waybill` ADD COLUMN `settlement_import_batch_id` bigint DEFAULT NULL COMMENT '导入结算单批次ID';
|
||||
ALTER TABLE `blade_waybill` ADD KEY `idx_waybill_settlement_import_batch_id` (`settlement_import_batch_id`);
|
||||
|
||||
INSERT INTO `blade_menu` (`id`,`parent_id`,`code`,`name`,`alias`,`path`,`source`,`sort`,`category`,`action`,`is_open`,`component`,`remark`,`is_deleted`) VALUES
|
||||
(2090000000000002080,2090000000000001000,'settlement_import','导入结算单','settlement_import','/settlement/settlement-import','iconfont icon-caidanguanli',6,1,0,1,NULL,'',0),
|
||||
(2090000000000002081,2090000000000002080,'settlement_import_view','查看','settlement_import_view','','',1,2,0,1,NULL,'',0),
|
||||
(2090000000000002082,2090000000000002080,'settlement_import_add','新增','settlement_import_add','','',2,2,0,1,NULL,'',0),
|
||||
(2090000000000002083,2090000000000002080,'settlement_import_detail','详情','settlement_import_detail','','',3,2,0,1,NULL,'',0),
|
||||
(2090000000000002084,2090000000000002080,'settlement_import_void','作废','settlement_import_void','','',4,2,0,1,NULL,'',0),
|
||||
(2090000000000002085,2090000000000002080,'settlement_import_template','下载模板','settlement_import_template','','',5,2,0,1,NULL,'',0)
|
||||
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`),`path`=VALUES(`path`),`is_deleted`=0;
|
||||
Reference in New Issue
Block a user