✨ 客商档案表单重构并补充实缴资本、营业期限起日与已使用额度

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-22 12:56:48 +08:00
parent 83b2144407
commit 73c8f6665e
6 changed files with 148 additions and 29 deletions
@@ -115,6 +115,10 @@ public class CustomerArchive extends TenantEntity {
@Schema(description = "营业期限类型")
private String businessTermType;
@Schema(description = "营业期限开始日")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDate businessStartDate;
@Schema(description = "营业期限截止日")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDate businessEndDate;
@@ -123,6 +127,10 @@ public class CustomerArchive extends TenantEntity {
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private BigDecimal registeredCapital;
@Schema(description = "实缴资本(万元)")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private BigDecimal paidInCapital;
@Schema(description = "客商负责人")
private String principal;
@@ -140,6 +148,10 @@ public class CustomerArchive extends TenantEntity {
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private BigDecimal applyCreditLimit;
@Schema(description = "已使用额度(万元),新建可录历史值,后续由收付款业务累加/减更新")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private BigDecimal usedCreditLimit;
@Schema(description = "备注")
private String remark;
@@ -33,13 +33,16 @@
<result column="business_scope" property="businessScope"/>
<result column="network_freight_platform" property="networkFreightPlatform"/>
<result column="business_term_type" property="businessTermType"/>
<result column="business_start_date" property="businessStartDate"/>
<result column="business_end_date" property="businessEndDate"/>
<result column="registered_capital" property="registeredCapital"/>
<result column="paid_in_capital" property="paidInCapital"/>
<result column="principal" property="principal"/>
<result column="mnemonic_code" property="mnemonicCode"/>
<result column="customer_level" property="customerLevel"/>
<result column="max_credit_limit" property="maxCreditLimit"/>
<result column="apply_credit_limit" property="applyCreditLimit"/>
<result column="used_credit_limit" property="usedCreditLimit"/>
<result column="remark" property="remark"/>
<result column="qualification_attachments" property="qualificationAttachments"/>
<result column="access_type" property="accessType"/>
@@ -85,13 +88,16 @@
business_scope,
network_freight_platform,
business_term_type,
business_start_date,
business_end_date,
registered_capital,
paid_in_capital,
principal,
mnemonic_code,
customer_level,
max_credit_limit,
apply_credit_limit,
used_credit_limit,
remark,
qualification_attachments,
access_type,
@@ -963,13 +963,16 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
customer.setBusinessScope(trimToNull(customer.getBusinessScope()));
customer.setInvoiceTaxRate(normalizeSentinelAmount(customer.getInvoiceTaxRate()));
customer.setRegisteredCapital(normalizeSentinelAmount(customer.getRegisteredCapital()));
customer.setPaidInCapital(normalizeSentinelAmount(customer.getPaidInCapital()));
customer.setMaxCreditLimit(normalizeSentinelAmount(customer.getMaxCreditLimit()));
customer.setApplyCreditLimit(normalizeSentinelAmount(customer.getApplyCreditLimit()));
customer.setUsedCreditLimit(normalizeSentinelAmount(customer.getUsedCreditLimit()));
customer.setRegisteredRegionName(trimToNull(customer.getRegisteredRegionName()));
customer.setRegisteredDetailAddress(trimToNull(customer.getRegisteredDetailAddress()));
customer.setRegisteredAddress(buildRegisteredAddress(customer));
customer.setLegalPerson(trimToEmpty(customer.getLegalPerson()));
customer.setContactPhone(trimToEmpty(customer.getContactPhone()));
customer.setContacts(customer.getContacts() == null ? new ArrayList<>() : customer.getContacts());
syncContactPhoneFromContacts(customer);
applyFixedOwnerDept(customer);
customer.setAccessType(Func.isEmpty(customer.getAccessType()) ? ACCESS_TEMPORARY : customer.getAccessType());
customer.setApprovalStatus(Func.isEmpty(customer.getApprovalStatus()) ? APPROVAL_DRAFT : customer.getApprovalStatus());
@@ -977,12 +980,33 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
if (Func.isEmpty(customer.getStatus())) {
customer.setStatus(STATUS_ENABLED);
}
customer.setContacts(customer.getContacts() == null ? new ArrayList<>() : customer.getContacts());
customer.setReceiptAccounts(customer.getReceiptAccounts() == null ? new ArrayList<>() : customer.getReceiptAccounts());
customer.setInvoices(customer.getInvoices() == null ? new ArrayList<>() : customer.getInvoices());
customer.setScores(customer.getScores() == null ? new ArrayList<>() : customer.getScores());
}
/**
* 主表联系电话改由联系人维护:取第一条有效联系人手机号回填,兼容历史 NOT NULL 及金蝶同步字段。
*/
private void syncContactPhoneFromContacts(CustomerArchiveVO customer) {
if (Func.isNotEmpty(customer.getContactPhone())) {
customer.setContactPhone(trimToEmpty(customer.getContactPhone()));
return;
}
List<CustomerContactVO> contacts = customer.getContacts();
if (Func.isEmpty(contacts)) {
customer.setContactPhone(null);
return;
}
String phone = contacts.stream()
.map(CustomerContactVO::getContactPhone)
.filter(Func::isNotEmpty)
.map(String::trim)
.findFirst()
.orElse(null);
customer.setContactPhone(phone);
}
private void validateBase(CustomerArchiveVO customer) {
if (Func.isEmpty(customer.getFullName())) {
throw new ServiceException("客商全称不能为空");
@@ -1009,10 +1033,8 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
if (Func.isEmpty(customer.getLegalPerson())) {
throw new ServiceException("法人/负责人不能为空");
}
if (Func.isEmpty(customer.getContactPhone())) {
throw new ServiceException("联系电话不能为空");
}
if (!customer.getContactPhone().matches("^1\\d{10}$")) {
validateContactsRequired(customer);
if (Func.isNotEmpty(customer.getContactPhone()) && !customer.getContactPhone().matches("^1\\d{10}$")) {
throw new ServiceException("联系电话必须为11位手机号");
}
if (Func.isEmpty(customer.getDeptName())) {
@@ -1028,16 +1050,30 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
validateLength(customer.getRegisteredRegionName(), 100, "省市区最多100个字符");
validateLength(customer.getRegisteredDetailAddress(), 255, "详细地址最多255个字符");
validateLength(customer.getRegisteredAddress(), 400, "地址最多400个字符");
validateLength(customer.getBusinessScope(), 500, "经营范围最多500个字符");
validateLength(customer.getBusinessScope(), 2000, "经营范围最多2000个字符");
validateLength(customer.getRemark(), 500, "备注最多500个字符");
validateNonNegative(customer.getInvoiceTaxRate(), "开票税点不能小于0");
validateMax(customer.getInvoiceTaxRate(), new BigDecimal("13"), "开票税点必须在0到13之间");
validateNonNegative(customer.getRegisteredCapital(), "注册资金不能小于0");
validateNonNegative(customer.getPaidInCapital(), "实缴资本不能小于0");
validateNonNegative(customer.getMaxCreditLimit(), "最大资金使用额度不能小于0");
validateNonNegative(customer.getApplyCreditLimit(), "申请总资金使用额度不能小于0");
validateNonNegative(customer.getUsedCreditLimit(), "已使用额度不能小于0");
validateUnique(customer);
}
/**
* 提交客商档案时必须至少维护一条联系人。
*/
private void validateContactsRequired(CustomerArchiveVO customer) {
List<CustomerContactVO> contacts = customer.getContacts() == null ? List.of() : customer.getContacts();
boolean hasContact = contacts.stream()
.anyMatch(item -> Func.isNotEmpty(item.getContactName()) || Func.isNotEmpty(item.getContactPhone()));
if (!hasContact) {
throw new ServiceException("请至少维护一条联系人");
}
}
private void validateCustomerType(String customerType) {
List<String> typeList = Func.toStrList(customerType.replace(",", ","))
.stream()
@@ -2040,13 +2076,16 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
snapshot.put("开票税点", customer.getInvoiceTaxRate());
snapshot.put("经营范围", customer.getBusinessScope());
snapshot.put("营业期限类型", customer.getBusinessTermType());
snapshot.put("营业期限开始日", customer.getBusinessStartDate());
snapshot.put("营业期限截止日", customer.getBusinessEndDate());
snapshot.put("注册资金(万元)", customer.getRegisteredCapital());
snapshot.put("实缴资本(万元)", customer.getPaidInCapital());
snapshot.put("客商负责人", customer.getPrincipal());
snapshot.put("助记码", customer.getMnemonicCode());
snapshot.put("客户等级", customer.getCustomerLevel());
snapshot.put("最大资金使用额度(万元)", customer.getMaxCreditLimit());
snapshot.put("申请总资金使用额度(万元)", customer.getApplyCreditLimit());
snapshot.put("已使用额度(万元)", customer.getUsedCreditLimit());
snapshot.put("备注", customer.getRemark());
snapshot.put("客商材料", customer.getQualificationAttachments());
snapshot.put("准入类型", customer.getAccessType());
@@ -40,11 +40,14 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerSaveDTO;
import org.springblade.transport.config.BfiProperties;
import org.springblade.transport.constant.CustomerBfiConstants;
import org.springblade.transport.pojo.vo.CustomerArchiveVO;
import org.springblade.transport.pojo.vo.CustomerContactVO;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -86,12 +89,13 @@ public class CustomerBfiConverter {
param.setType(this.resolvePartnerType(customer.getCustomerNature()));
// 详细地址:注册地址(省市区 + 详细地址拼接文本)
param.setBizpartnerAddress(customer.getRegisteredAddress());
// 经营范围:多选编码 → 字典文本「、」拼接
// 经营范围:长文本直传;兼容历史多选编码→字典文本
param.setBusinessscope(this.buildBusinessScopeText(customer.getBusinessScope()));
// 营业期限:长期传「长期」,固定期限传「至 yyyy-MM-dd」(TMS 未采集起始日)
param.setBusinessterm(this.buildBusinessTerm(customer.getBusinessTermType(), customer.getBusinessEndDate()));
// 联系电话:客商联系电话
param.setBizpartnerPhone(customer.getContactPhone());
// 营业期限:长期传「长期」,固定期限传「yyyy-MM-dd 至 yyyy-MM-dd」
param.setBusinessterm(this.buildBusinessTerm(
customer.getBusinessTermType(), customer.getBusinessStartDate(), customer.getBusinessEndDate()));
// 联系电话:优先主表,空时回退首条联系人
param.setBizpartnerPhone(this.resolveContactPhone(customer));
// 备注:客商备注,空时不传
param.setFcusbeizhu(customer.getRemark());
// 业务职能:固定全选(1销售,2结算,3付款,4收货)
@@ -107,7 +111,7 @@ public class CustomerBfiConverter {
param.setEntryGroupstandard(List.of(groupEntry));
// 银行信息分录:收款账户明细(账户名称传开户行,币种默认 RMB)
param.setEntryBank(this.buildBankEntries(customer));
// 联系人信息分录:页面填写的客商负责人与联系电话
// 联系人信息分录:客商联系人明细(兼容历史负责人字段)
param.setEntryLinkman(this.buildLinkmanEntries(customer));
return param;
}
@@ -175,25 +179,58 @@ public class CustomerBfiConverter {
}
/**
* 组装 A902-2 联系人信息分录:页面填写的客商负责人与联系电话,未填负责人时返回空分录
* 组装 A902-2 联系人信息分录:优先推送客商联系人明细;无明细时兼容历史「负责人+联系电话」。
*
* @param customer 客商档案
* @return 联系人信息分录
*/
private List<BfiCustomerLinkmanEntryDTO> buildLinkmanEntries(CustomerArchiveVO customer) {
if (Func.isNotEmpty(customer.getContacts())) {
List<BfiCustomerLinkmanEntryDTO> entries = new ArrayList<>();
boolean defaultAssigned = false;
for (CustomerContactVO contact : customer.getContacts()) {
if (Func.isBlank(contact.getContactName()) && Func.isBlank(contact.getContactPhone())) {
continue;
}
BfiCustomerLinkmanEntryDTO entry = new BfiCustomerLinkmanEntryDTO();
entry.setContactperson(contact.getContactName());
entry.setPhone(contact.getContactPhone());
boolean isDefault = !defaultAssigned && (Objects.equals(contact.getIsDefault(), 1)
|| Func.isNotBlank(contact.getContactName()));
entry.setIsdefaultLinkman(isDefault);
if (isDefault) {
defaultAssigned = true;
}
entries.add(entry);
}
if (!entries.isEmpty()) {
return entries;
}
}
if (Func.isBlank(customer.getPrincipal())) {
return List.of();
}
BfiCustomerLinkmanEntryDTO entry = new BfiCustomerLinkmanEntryDTO();
// 联系人:客商负责人
entry.setContactperson(customer.getPrincipal());
// 电话:联系电话
entry.setPhone(customer.getContactPhone());
// 默认联系人
entry.setPhone(this.resolveContactPhone(customer));
entry.setIsdefaultLinkman(Boolean.TRUE);
return List.of(entry);
}
private String resolveContactPhone(CustomerArchiveVO customer) {
if (Func.isNotBlank(customer.getContactPhone())) {
return customer.getContactPhone();
}
if (Func.isEmpty(customer.getContacts())) {
return null;
}
return customer.getContacts().stream()
.map(CustomerContactVO::getContactPhone)
.filter(Func::isNotBlank)
.findFirst()
.orElse(null);
}
/**
* 金蝶伙伴类型编码:客商性质(nature_of_client 字典)——
* 值为 dictKey(即伙伴类型编码,1~17)时直接使用;
@@ -248,35 +285,50 @@ public class CustomerBfiConverter {
}
/**
* 经营范围多选编码 → 字典文本「、」拼接,字典缺失时回退原编码
* 经营范围:长文本直传;兼容历史多选编码 → 字典文本「、」拼接。
*
* @param businessScope 逗号拼接的经营范围编码
* @param businessScope 经营范围长文本或历史逗号拼接编码
* @return 金蝶经营范围文本
*/
public String buildBusinessScopeText(String businessScope) {
if (Func.isEmpty(businessScope)) {
return "";
}
String text = businessScope.trim();
// 长文本(含中文描述)直接推送,不再按字典拆分
if (text.length() > 40 || text.contains(";") || text.contains("。") || text.contains("\n")) {
return text;
}
List<DictBiz> dictList = DictBizCache.getList("scope_of_business");
Map<String, String> scopeNames = dictList == null ? Map.of() : dictList.stream()
.collect(Collectors.toMap(DictBiz::getDictKey, DictBiz::getDictValue, (first, second) -> first));
return Func.toStrList(businessScope.replace(",", ",")).stream()
return Func.toStrList(text.replace(",", ",")).stream()
.map(code -> scopeNames.getOrDefault(code, code))
.collect(Collectors.joining("、"));
}
/**
* 营业期限:长期传「长期」;固定期限传「至 yyyy-MM-dd」(TMS 未采集起始日)
* 营业期限:长期传「长期」;固定期限传「yyyy-MM-dd 至 yyyy-MM-dd」或「至 yyyy-MM-dd」。
*
* @param businessTermType 营业期限类型
* @param businessEndDate 营业期限截止日
* @param businessTermType 营业期限类型
* @param businessStartDate 营业期限开始日
* @param businessEndDate 营业期限截止日
* @return 金蝶营业期限文本
*/
public String buildBusinessTerm(String businessTermType, LocalDate businessEndDate) {
public String buildBusinessTerm(String businessTermType, LocalDate businessStartDate, LocalDate businessEndDate) {
if (CustomerBfiConstants.BUSINESS_TERM_LONG.equals(businessTermType)) {
return CustomerBfiConstants.BUSINESS_TERM_LONG;
if (businessStartDate == null) {
return CustomerBfiConstants.BUSINESS_TERM_LONG;
}
return businessStartDate + " 至 " + CustomerBfiConstants.BUSINESS_TERM_LONG;
}
return businessEndDate == null ? "" : "至 " + businessEndDate;
if (businessStartDate != null && businessEndDate != null) {
return businessStartDate + " 至 " + businessEndDate;
}
if (businessEndDate != null) {
return "至 " + businessEndDate;
}
return businessStartDate == null ? "" : String.valueOf(businessStartDate);
}
}
+5 -2
View File
@@ -18,21 +18,24 @@ CREATE TABLE `blade_customer_archive` (
`registered_region_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '注册地址行政区划',
`registered_detail_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '注册地址详细地址',
`legal_person` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '法人/负责人',
`contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '联系电话',
`contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',
`dept_id` bigint NULL DEFAULT NULL COMMENT '所属组织ID',
`dept_ids` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所属组织ID集合',
`dept_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所属组织',
`invoice_tax_rate` decimal(6,2) NULL DEFAULT NULL COMMENT '开票税点',
`business_scope` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '经营范围',
`business_scope` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '经营范围',
`network_freight_platform` tinyint NULL DEFAULT NULL COMMENT '网络货运平台:0否,1是',
`business_term_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '营业期限类型',
`business_start_date` date NULL DEFAULT NULL COMMENT '营业期限开始日',
`business_end_date` date NULL DEFAULT NULL COMMENT '营业期限截止日',
`registered_capital` decimal(18,2) NULL DEFAULT NULL COMMENT '注册资金(万元)',
`paid_in_capital` decimal(18,2) NULL DEFAULT NULL COMMENT '实缴资本(万元)',
`principal` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客商负责人',
`mnemonic_code` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码',
`customer_level` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户等级',
`max_credit_limit` decimal(18,2) NULL DEFAULT NULL COMMENT '最大资金使用额度(万元)',
`apply_credit_limit` decimal(18,2) NULL DEFAULT NULL COMMENT '申请总资金使用额度(万元)',
`used_credit_limit` decimal(18,2) NULL DEFAULT NULL COMMENT '已使用额度(万元)',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`qualification_attachments` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '客商材料JSON',
`access_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'temporary' COMMENT '准入类型:temporary临时,formal正式',
@@ -0,0 +1,7 @@
-- 客商档案:实缴资本、营业期限开始日、已使用额度;经营范围加长;联系电话改为可空(改由联系人维护)
ALTER TABLE `blade_customer_archive`
ADD COLUMN `business_start_date` date NULL DEFAULT NULL COMMENT '营业期限开始日' AFTER `business_term_type`,
ADD COLUMN `paid_in_capital` decimal(18,2) NULL DEFAULT NULL COMMENT '实缴资本(万元)' AFTER `registered_capital`,
ADD COLUMN `used_credit_limit` decimal(18,2) NULL DEFAULT NULL COMMENT '已使用额度(万元)' AFTER `apply_credit_limit`,
MODIFY COLUMN `business_scope` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '经营范围',
MODIFY COLUMN `contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话';