✨ 合同结算币种贯通应收应付与结算,并完善合同编号与字段

合同结算币种写入应收应付与预结算;总单运费默认币种统一为 RMB;同步合同申请日期、主要内容、部门合同编号前缀及用印材料字典等能力。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-10 00:53:17 +08:00
parent ef9eab737f
commit e20afc0d55
18 changed files with 229 additions and 25 deletions
@@ -144,6 +144,12 @@ public class Dept extends TenantEntity {
@Schema(description = "金蝶部门代码")
private String kingdeeDeptCode;
/**
* 合同编码前缀(合同编号:前缀(年份)三位流水号)
*/
@Schema(description = "合同编码前缀")
private String contractCodePrefix;
/**
* 组织简称
*/
@@ -61,10 +61,10 @@ public class ContractManage extends TenantEntity {
@Schema(description = "所属项目")
private String projectName;
@Schema(description = "所属组织ID")
@Schema(description = "合同负责部门ID")
private Long organizationId;
@Schema(description = "所属组织")
@Schema(description = "合同负责部门")
private String organizationName;
@Schema(description = "合同类别")
@@ -100,6 +100,9 @@ public class ContractManage extends TenantEntity {
@Schema(description = "签订日期")
private LocalDate signDate;
@Schema(description = "申请日期(提交正式合同时写入)")
private LocalDate applyDate;
@Schema(description = "结算方式")
private String settlementMode;
@@ -107,6 +110,7 @@ public class ContractManage extends TenantEntity {
private String contractFormat;
@Schema(description = "是否需要加盖法人章")
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
private Integer legalSealFlag;
@Schema(description = "一式份数")
@@ -206,7 +210,10 @@ public class ContractManage extends TenantEntity {
@Schema(description = "终止原因")
private String terminateReason;
@Schema(description = "备注")
@Schema(description = "合同主要内容")
private String mainContent;
@Schema(description = "其他事项说明")
private String remark;
}
@@ -25,6 +25,8 @@ package org.springblade.transport.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -82,6 +84,7 @@ public class Waybill extends TenantEntity {
@Schema(description = "型号")
private String model;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "数量")
private BigDecimal quantity;
@@ -183,6 +186,7 @@ public class Waybill extends TenantEntity {
@Schema(description = "押运人手机号")
private String escortPhone;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "里程")
private BigDecimal mileage;
@@ -195,12 +199,14 @@ public class Waybill extends TenantEntity {
@Schema(description = "预计完成日期")
private LocalDate estimatedEndTime;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "单价")
private BigDecimal unitPrice;
@Schema(description = "计价单位")
private String priceUnit;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "其他费用合计")
private BigDecimal otherFeeTotal;
@@ -13,6 +13,7 @@
<result column="dept_category" property="deptCategory"/>
<result column="dept_code" property="deptCode"/>
<result column="kingdee_dept_code" property="kingdeeDeptCode"/>
<result column="contract_code_prefix" property="contractCodePrefix"/>
<result column="short_name" property="shortName"/>
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
<result column="mnemonic_code" property="mnemonicCode"/>
@@ -34,6 +35,7 @@
<result column="dept_category" property="deptCategory"/>
<result column="dept_code" property="deptCode"/>
<result column="kingdee_dept_code" property="kingdeeDeptCode"/>
<result column="contract_code_prefix" property="contractCodePrefix"/>
<result column="short_name" property="shortName"/>
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
<result column="mnemonic_code" property="mnemonicCode"/>
@@ -177,8 +177,16 @@ public class ContractManageController extends BladeController {
return R.data(contractManageService.expireStats(contractManage));
}
@GetMapping("/export-contract-manage")
@GetMapping("/next-contract-no")
@ApiOperationSupport(order = 16)
@Operation(summary = "预览下一合同编号", description = "按合同负责部门编码前缀+年份+三位流水预览")
public R<String> nextContractNo(
@Parameter(description = "合同负责部门ID", required = true) @RequestParam Long organizationId) {
return R.data(contractManageService.nextContractNo(organizationId));
}
@GetMapping("/export-contract-manage")
@ApiOperationSupport(order = 17)
@Operation(summary = "导出合同管理")
public void exportContractManage(ContractManageVO contractManage, @RequestParam(required = false) String ids, HttpServletResponse response) {
List<ContractManageExcel> list = contractManageService.exportContractManage(contractManage, ids);
@@ -56,7 +56,7 @@ public class ContractManageExcel implements Serializable {
private String contractName;
@ExcelProperty("所属项目")
private String projectName;
@ExcelProperty("所属组织")
@ExcelProperty("合同负责部门")
private String organizationName;
@ExcelProperty("合同类别")
private String contractCategory;
@@ -76,6 +76,10 @@ public class ContractManageExcel implements Serializable {
private LocalDate temporaryEndDate;
@ExcelProperty("经办人")
private String handlerUserName;
@ExcelProperty("签订日期")
private LocalDate signDate;
@ExcelProperty("申请日期")
private LocalDate applyDate;
@ExcelProperty("合同阶段")
private String contractStage;
@ExcelProperty("审核状态")
@@ -105,7 +109,9 @@ public class ContractManageExcel implements Serializable {
private String currentNode;
@ExcelProperty("当前处理人")
private String currentProcessor;
@ExcelProperty("备注")
@ExcelProperty("合同主要内容")
private String mainContent;
@ExcelProperty("其他事项说明")
private String remark;
@ExcelProperty("创建人")
private String createUserName;
@@ -64,4 +64,12 @@ public interface IContractManageService extends BaseService<ContractManage> {
Map<String, Long> expireStats(ContractManageVO contractManage);
List<ContractManageExcel> exportContractManage(ContractManageVO contractManage, String ids);
/**
* 预览下一合同编号(按合同负责部门前缀 + 年份 + 三位流水)
*
* @param organizationId 合同负责部门ID
* @return 预览编号
*/
String nextContractNo(Long organizationId);
}
@@ -32,7 +32,9 @@ 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.ContractManageExcel;
import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.pojo.entity.ContractManage;
@@ -55,6 +57,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 合同管理 服务实现类
@@ -190,6 +194,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
contractManage.setApprovalStatus(STATUS_REVIEWING);
contractManage.setCurrentNode("正式合同审批");
contractManage.setCurrentProcessor("待处理");
// 正式合同提交时刷新申请日期为当天
contractManage.setApplyDate(LocalDate.now());
return updateById(contractManage);
}
@@ -301,6 +307,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
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());
@@ -375,6 +382,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
target.setCurrentNode(null);
target.setCurrentProcessor(null);
target.setApprovedTime(null);
target.setApplyDate(null);
target.setChangeContent(null);
target.setChangeReason(null);
target.setChangeAttachmentsJson(null);
@@ -493,13 +501,12 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
private void prepareCreateOrUpdate(ContractManage contractManage) {
if (Func.isEmpty(contractManage.getId())) {
if (Func.isEmpty(contractManage.getContractNo())) {
contractManage.setContractNo("HT" + LocalDate.now().toString().replace("-", "") + String.format("%06d", count() + 1));
}
if (Func.isEmpty(contractManage.getOrganizationId())) {
contractManage.setOrganizationId(TransportBusinessSupport.currentDeptId("合同管理"));
contractManage.setOrganizationName(TransportBusinessSupport.currentDept("合同管理").getDeptName());
}
// 新增始终按部门规则生成合同编号,避免前端预览号并发冲突
contractManage.setContractNo(nextContractNo(contractManage.getOrganizationId()));
if (Func.isEmpty(contractManage.getHandlerUserId())) {
contractManage.setHandlerUserId(AuthUtil.getUserId());
contractManage.setHandlerUserName(AuthUtil.getUserName());
@@ -526,6 +533,48 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
}
}
@Override
public synchronized String nextContractNo(Long organizationId) {
if (Func.isEmpty(organizationId)) {
throw new ServiceException("请选择合同负责部门");
}
Dept dept = SysCache.getDept(organizationId);
if (dept == null) {
throw new ServiceException("合同负责部门不存在");
}
String prefix = TransportBusinessSupport.trimToNull(dept.getContractCodePrefix());
if (Func.isEmpty(prefix)) {
throw new ServiceException("请先在组织机构中配置该部门的合同编码前缀");
}
int year = LocalDate.now().getYear();
String head = prefix + "(" + year + ")";
List<ContractManage> latestList = list(Wrappers.<ContractManage>lambdaQuery()
.select(ContractManage::getContractNo)
.eq(ContractManage::getIsDeleted, 0)
.eq(ContractManage::getOrganizationId, organizationId)
.likeRight(ContractManage::getContractNo, head)
.orderByDesc(ContractManage::getContractNo)
.last("LIMIT 50"));
Pattern pattern = Pattern.compile("^" + Pattern.quote(prefix) + "(" + year + ")(\\d{3})号$");
int next = 1;
for (ContractManage item : latestList) {
String contractNo = TransportBusinessSupport.trimToNull(item.getContractNo());
if (Func.isEmpty(contractNo)) {
continue;
}
Matcher matcher = pattern.matcher(contractNo);
if (!matcher.matches()) {
continue;
}
next = Integer.parseInt(matcher.group(1)) + 1;
break;
}
if (next > 999) {
throw new ServiceException("当前部门本年合同流水号已达上限999");
}
return head + String.format("%03d", next) + "号";
}
private void refreshArchiveStatusOnApprove(ContractManage contractManage) {
if (hasSealArchiveFile(contractManage.getContractFileJson())) {
contractManage.setArchiveStatus(ARCHIVE_STATUS_ARCHIVED);
@@ -595,7 +644,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
}
for (Map.Entry<String, Map<String, Object>> entry : originalApproved.entrySet()) {
if (!requestKeys.contains(entry.getKey())) {
throw new ServiceException("已审核通过的合同文件不允许删除");
throw new ServiceException("已审核通过的用印材料不允许删除");
}
}
return merged.isEmpty() ? null : JsonUtil.toJson(merged);
@@ -671,7 +720,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
TransportBusinessSupport.validateRequired(contractManage.getContractName(), "请输入合同名称");
TransportBusinessSupport.validateRequired(contractManage.getContractCategory(), "请选择合同类别");
// 合同名称允许重复,唯一性由合同编号保证。
TransportBusinessSupport.validateLength(contractManage.getRemark(), 2000, "备注不能超过2000字");
TransportBusinessSupport.validateLength(contractManage.getMainContent(), 1000, "合同主要内容不能超过1000字");
TransportBusinessSupport.validateLength(contractManage.getRemark(), 2000, "其他事项说明不能超过2000字");
}
private void validateBillingPlans(String billingPlanJson) {
@@ -812,9 +862,10 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
TransportBusinessSupport.validateRequired(contractManage.getContractName(), "请输入合同名称");
TransportBusinessSupport.validateRequired(contractManage.getContractCategory(), "请选择合同类型");
TransportBusinessSupport.validateRequired(contractManage.getProjectName(), "请选择所属项目");
TransportBusinessSupport.validateRequired(contractManage.getOrganizationName(), "请选择所属组织");
TransportBusinessSupport.validateRequired(contractManage.getOrganizationName(), "请选择合同负责部门");
TransportBusinessSupport.validateRequired(contractManage.getPartyA(), "请选择甲方");
TransportBusinessSupport.validateRequired(contractManage.getPartyB(), "请选择乙方");
validateContractAmountRequired(contractManage);
}
/**
@@ -845,18 +896,26 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
TransportBusinessSupport.validateRequired(contractManage.getContractName(), "请输入合同名称");
TransportBusinessSupport.validateRequired(contractManage.getContractCategory(), "请选择合同类型");
TransportBusinessSupport.validateRequired(contractManage.getProjectName(), "请选择所属项目");
TransportBusinessSupport.validateRequired(contractManage.getOrganizationName(), "请选择所属组织");
TransportBusinessSupport.validateRequired(contractManage.getOrganizationName(), "请选择合同负责部门");
TransportBusinessSupport.validateRequired(contractManage.getSettlementCurrency(), "请选择结算币种");
TransportBusinessSupport.validateRequired(contractManage.getPartyA(), "请输入甲方");
TransportBusinessSupport.validateRequired(contractManage.getPartyB(), "请输入乙方");
TransportBusinessSupport.validateRequired(contractManage.getStartDate() == null ? null : contractManage.getStartDate().toString(), "请选择合同期限");
TransportBusinessSupport.validateRequired(contractManage.getSignDate() == null ? null : contractManage.getSignDate().toString(), "请选择签订日期");
TransportBusinessSupport.validateDateRange(contractManage.getStartDate(), contractManage.getEndDate(), "合同期限开始日期不能晚于结束日期");
TransportBusinessSupport.validateLength(contractManage.getRemark(), 2000, "备注不能超过2000字");
validateContractAmountRequired(contractManage);
TransportBusinessSupport.validateLength(contractManage.getMainContent(), 1000, "合同主要内容不能超过1000字");
TransportBusinessSupport.validateLength(contractManage.getRemark(), 2000, "其他事项说明不能超过2000字");
validateBillingPlans(contractManage.getBillingPlanJson());
validatePaymentRatios(contractManage.getPaymentRatioJson());
}
private void validateContractAmountRequired(ContractManage contractManage) {
if (contractManage.getContractAmount() == null) {
throw new ServiceException("请输入合同金额");
}
}
private ContractManage loadExists(Long id) {
ContractManage contractManage = getById(id);
if (Func.isEmpty(contractManage) || Objects.equals(contractManage.getIsDeleted(), 1)) {
@@ -969,6 +1028,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
data.put("templateFlag", contractManage.getTemplateFlag());
data.put("originalContractNo", contractManage.getOriginalContractNo());
data.put("electronicSealFlag", contractManage.getElectronicSealFlag());
data.put("mainContent", contractManage.getMainContent());
data.put("remark", contractManage.getRemark());
data.put("feeGenerationMode", contractManage.getFeeGenerationMode());
data.put("billingPlanJson", contractManage.getBillingPlanJson());
@@ -677,7 +677,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
freight.put("freightTotal", freightTotal);
freight.put("totalFreightAmount", freightTotal.add(Objects.requireNonNullElse(nullableDecimal(dispatch, "otherFeeTotal"), BigDecimal.ZERO)));
freight.put("otherFreightAmount", string(dispatch, "otherFeeTotal", ""));
freight.put("currency", string(dispatch, "currency", "CNY"));
freight.put("currency", string(dispatch, "currency", "RMB"));
return JsonUtil.toJson(freight);
}
private String waybillGroupKey(Map<String, Object> dispatch) { return String.join("\u0000", string(dispatch, "segmentNo", ""), string(dispatch, "carrierType", ""), string(dispatch, "carrierContractId", ""), string(dispatch, "carrierName", ""), string(dispatch, "driverName", ""), string(dispatch, "driverPhone", ""), string(dispatch, "vehicleNo", ""), string(dispatch, "captainName", ""), string(dispatch, "containerNo", ""), string(dispatch, "cabinNo", "")); }
@@ -319,7 +319,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
}
if (contractChanged) {
settlement.setSettlementType(contractSettlementType(contract));
settlement.setCurrency(LOCAL_CURRENCY);
settlement.setCurrency(resolveContractCurrency(contract));
summaryFeeMapper.delete(Wrappers.<PreSettlementSummaryFee>lambdaQuery()
.eq(PreSettlementSummaryFee::getPreSettlementId, settlement.getId()));
}
@@ -329,7 +329,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
settlement.setSettlementType(resolveSettlementType(request, contract));
settlement.setApprovalStatus(STATUS_DRAFT);
settlement.setCurrentNode("草稿");
settlement.setCurrency(LOCAL_CURRENCY);
settlement.setCurrency(resolveContractCurrency(contract));
settlement.setLocalCurrency(LOCAL_CURRENCY);
}
fillContract(settlement, contract);
@@ -1280,6 +1280,24 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
}
}
/**
* 预结算单结算币种取自合同结算币种,CNY 归一为 RMB。
*/
private String resolveContractCurrency(ContractManage contract) {
if (contract == null || Func.isEmpty(contract.getSettlementCurrency())) {
return LOCAL_CURRENCY;
}
String code = contract.getSettlementCurrency().trim().toUpperCase();
int separatorIndex = code.indexOf(" - ");
if (separatorIndex > 0) {
code = code.substring(0, separatorIndex).trim();
}
if ("CNY".equals(code)) {
return LOCAL_CURRENCY;
}
return Func.isEmpty(code) ? LOCAL_CURRENCY : code;
}
private String resolveSettlementType(PreSettlementSaveRequest request, ContractManage contract) {
if (Boolean.TRUE.equals(request.getAllowSourceMismatch()) && Func.isNotEmpty(request.getSettlementType())) {
validateSettlementType(request.getSettlementType());
@@ -80,6 +80,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -1468,7 +1469,7 @@ public class ReceivablePayableDetailServiceImpl
detail.setMileage(normalizeGeneratedMileage(waybill.getMileage()));
detail.setBatchNo(waybill.getBatchNo());
detail.setUnitPrice(unitPrice);
detail.setCurrency("RMB");
detail.setCurrency(resolveCurrency(contract, waybill));
detail.setFreightAmount(freight);
detail.setOtherFeeAmount(other);
detail.setTotalAmount(total);
@@ -2523,6 +2524,7 @@ public class ReceivablePayableDetailServiceImpl
detail.setOtherFeeAmount(total.subtract(freight));
detail.setTotalAmount(total);
detail.setUnitPrice(resolveContractUnitPrice(fees));
detail.setCurrency(resolveCurrency(contract, waybill));
detail.setFeeItemsJson(aggregateFeeItemsJson(fees));
updateById(detail);
}
@@ -2554,6 +2556,7 @@ public class ReceivablePayableDetailServiceImpl
detail.setOtherFeeAmount(total.subtract(freight));
detail.setTotalAmount(total);
detail.setUnitPrice(resolveContractUnitPrice(fees));
detail.setCurrency(resolveCurrency(contract, masterGoods.isEmpty() ? null : masterGoods.get(0)));
detail.setFeeItemsJson(aggregateFeeItemsJson(fees));
updateById(detail);
}
@@ -2588,6 +2591,7 @@ public class ReceivablePayableDetailServiceImpl
detail.setOtherFeeAmount(total.subtract(freight));
detail.setTotalAmount(total);
detail.setUnitPrice(resolveContractUnitPrice(fees));
detail.setCurrency(resolveCurrency(contract, waybills.get(0)));
detail.setFeeItemsJson(aggregateFeeItemsJson(fees));
updateById(detail);
}
@@ -2886,6 +2890,43 @@ public class ReceivablePayableDetailServiceImpl
return map;
}
/**
* 结算币种优先取合同 settlementCurrency,其次运单 freightJson.currency,默认 RMB。
*/
private String resolveCurrency(ContractManage contract, Waybill waybill) {
if (contract != null && Func.isNotEmpty(contract.getSettlementCurrency())) {
return normalizeCurrencyCode(contract.getSettlementCurrency());
}
String freightCurrency = extractFreightCurrency(waybill);
if (Func.isNotEmpty(freightCurrency)) {
return normalizeCurrencyCode(freightCurrency);
}
return "RMB";
}
private String extractFreightCurrency(Waybill waybill) {
if (waybill == null || Func.isEmpty(waybill.getFreightJson())) {
return null;
}
Object currency = parseMap(waybill.getFreightJson()).get("currency");
return currency == null ? null : String.valueOf(currency);
}
private String normalizeCurrencyCode(String currency) {
if (Func.isEmpty(currency)) {
return "RMB";
}
String code = currency.trim().toUpperCase(Locale.ROOT);
int separatorIndex = code.indexOf(" - ");
if (separatorIndex > 0) {
code = code.substring(0, separatorIndex).trim();
}
if ("CNY".equals(code)) {
return "RMB";
}
return code;
}
private Map<String, Object> parseMap(String json) {
if (Func.isEmpty(json)) {
return new LinkedHashMap<>();
@@ -1062,10 +1062,13 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
waybill.setMasterNo(null);
waybill.setMileageRemark(null);
}
// 新建若已带业务状态(如复制提交)则在过程校正后回写,保证与原运单一致
// 新建若已带业务状态(如复制提交已完成)则在过程校正后回写,保证与原运单一致
String preservedBusinessStatus = created
? TransportBusinessSupport.trimToNull(waybill.getBusinessStatus())
: null;
if (isCompletedBusinessStatus(preservedBusinessStatus)) {
preservedBusinessStatus = "completed";
}
// 无过程快照时按项目回填;再按接单设置决定 pending / running
fillProjectProcessConfig(waybill);
prepare(waybill);
@@ -1360,17 +1363,35 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
}
/**
* 新建/复制运单若业务状态为已完成,按合同系统计费模式生成应收、应付明细。
* <p>使用同事务入口,避免 REQUIRES_NEW 读不到尚未提交的新运单。</p>
* 新建/复制运单若业务状态为已完成,按与「完成运单」相同的合同系统计费规则生成应收、应付明细。
* <p>使用同事务入口 {@link IReceivablePayableDetailService#generateForImportedWaybills},
* 避免 {@code generateForCompletedWaybills} 的 REQUIRES_NEW 读不到尚未提交的新运单。</p>
*/
private void generateReceivablePayableIfCompleted(Waybill waybill) {
if (waybill == null || Func.isEmpty(waybill.getId())) {
return;
}
if (!"completed".equals(waybill.getBusinessStatus())) {
Waybill saved = getById(waybill.getId());
if (saved == null || Objects.equals(saved.getIsDeleted(), 1)) {
return;
}
receivablePayableDetailService.generateForImportedWaybills(List.of(waybill));
String businessStatus = TransportBusinessSupport.trimToNull(saved.getBusinessStatus());
if (Func.isEmpty(businessStatus)) {
businessStatus = TransportBusinessSupport.trimToNull(waybill.getBusinessStatus());
}
if (!isCompletedBusinessStatus(businessStatus)) {
return;
}
// 统一落库英文状态码,与完成运单、列表展示口径一致
if (!"completed".equals(businessStatus)) {
saved.setBusinessStatus("completed");
updateById(saved);
}
receivablePayableDetailService.generateForImportedWaybills(List.of(saved));
}
private static boolean isCompletedBusinessStatus(String businessStatus) {
return "completed".equals(businessStatus) || "已完成".equals(businessStatus);
}
@Override
@@ -304,6 +304,7 @@ CREATE TABLE `blade_dept` (
`full_name` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门全称',
`dept_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织编码',
`kingdee_dept_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '金蝶部门代码',
`contract_code_prefix` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '合同编码前缀',
`short_name` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织简称',
`pinyin_mnemonic` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拼音助记码',
`mnemonic_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码',
@@ -0,0 +1,3 @@
-- 合同管理新增申请日期:正式合同提交时写入当天日期
ALTER TABLE `blade_contract_manage`
ADD COLUMN `apply_date` date DEFAULT NULL COMMENT '申请日期(提交正式合同时写入)' AFTER `sign_date`;
@@ -0,0 +1,3 @@
-- 合同管理新增合同主要内容(非必填,最多1000字)
ALTER TABLE `blade_contract_manage`
ADD COLUMN `main_content` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '合同主要内容' AFTER `terminate_reason`;
@@ -0,0 +1,3 @@
-- 组织机构新增合同编码前缀:合同编号 = 前缀(年份)三位流水号,流水按部门独立
ALTER TABLE `blade_dept`
ADD COLUMN `contract_code_prefix` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '合同编码前缀' AFTER `kingdee_dept_code`;
@@ -0,0 +1,9 @@
-- 合同附件类型字典:合同文件 → 用印材料
-- 业务字典 code = contract_attachment_type
UPDATE blade_dict_biz
SET dict_key = '用印材料',
dict_value = '用印材料'
WHERE code = 'contract_attachment_type'
AND is_deleted = 0
AND (dict_key = '合同文件' OR dict_value = '合同文件');
@@ -119,8 +119,8 @@ CREATE TABLE `blade_contract_manage` (
`contract_name` varchar(100) DEFAULT NULL COMMENT '合同名称',
`project_id` bigint(20) DEFAULT NULL COMMENT '所属项目ID',
`project_name` varchar(100) DEFAULT NULL COMMENT '所属项目',
`organization_id` bigint(20) DEFAULT NULL COMMENT '所属组织ID',
`organization_name` varchar(100) DEFAULT NULL COMMENT '所属组织',
`organization_id` bigint(20) DEFAULT NULL COMMENT '合同负责部门ID',
`organization_name` varchar(100) DEFAULT NULL COMMENT '合同负责部门',
`contract_category` varchar(50) DEFAULT NULL COMMENT '合同类别',
`sign_type` varchar(50) DEFAULT NULL COMMENT '签约类型',
`party_a` varchar(200) DEFAULT NULL COMMENT '甲方',
@@ -132,6 +132,7 @@ CREATE TABLE `blade_contract_manage` (
`handler_user_id` bigint(20) DEFAULT NULL COMMENT '经办人ID',
`handler_user_name` varchar(100) DEFAULT NULL COMMENT '经办人',
`sign_date` date DEFAULT NULL COMMENT '签订日期',
`apply_date` date DEFAULT NULL COMMENT '申请日期(提交正式合同时写入)',
`settlement_mode` varchar(100) DEFAULT NULL COMMENT '结算方式',
`contract_format` varchar(50) DEFAULT NULL COMMENT '合同格式',
`legal_seal_flag` int(11) DEFAULT '0' COMMENT '是否需要加盖法人章',
@@ -160,6 +161,7 @@ CREATE TABLE `blade_contract_manage` (
`change_reason` varchar(500) DEFAULT NULL COMMENT '变更原因',
`change_attachments_json` text DEFAULT NULL COMMENT '待审批变更附件JSON',
`terminate_reason` varchar(500) DEFAULT NULL COMMENT '终止原因',
`main_content` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '合同主要内容',
`remark` varchar(2000) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_contract_project` (`project_id`) USING BTREE,