diff --git a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/domain/BmsCustomerLog.java b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/domain/BmsCustomerLog.java index a1d80c0a..405d0e41 100644 --- a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/domain/BmsCustomerLog.java +++ b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/domain/BmsCustomerLog.java @@ -16,6 +16,17 @@ public class BmsCustomerLog extends BaseEntity { private static final long serialVersionUID = 1L; + /** 业务模块:客户档案 */ + public static final String MODULE_MAIN = "main"; + /** 业务模块:公司主体 */ + public static final String MODULE_COMPANY = "company"; + /** 业务模块:联系人 */ + public static final String MODULE_CONTACT = "contact"; + /** 业务模块:附件资料 */ + public static final String MODULE_ATTACHMENT = "attachment"; + /** 业务模块:绑定套餐 */ + public static final String MODULE_PRICE_SET = "priceset"; + /** 操作类型:新建 */ public static final String OP_INSERT = "1"; /** 操作类型:修改 */ @@ -35,6 +46,12 @@ public class BmsCustomerLog extends BaseEntity /** 客户名称(操作时快照) */ private String customerName; + /** 业务模块(main/company/contact/attachment/priceset) */ + private String bizModule; + + /** 操作对象名称(公司全称/联系人姓名/文件名/套餐名) */ + private String bizName; + /** 操作类型(1新建 2修改 3删除) */ private String operationType; diff --git a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/IBmsCustomerLogService.java b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/IBmsCustomerLogService.java index ac985d48..98909ada 100644 --- a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/IBmsCustomerLogService.java +++ b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/IBmsCustomerLogService.java @@ -3,7 +3,12 @@ package com.bms.customer.customerinfo.service; import java.util.List; import com.bms.customer.customerinfo.domain.BmsCustomer; +import com.bms.customer.customerinfo.domain.BmsCustomerAttachment; +import com.bms.customer.customerinfo.domain.BmsCustomerCompany; +import com.bms.customer.customerinfo.domain.BmsCustomerContact; import com.bms.customer.customerinfo.domain.BmsCustomerLog; +import com.bms.customer.customerinfo.domain.BmsCustomerPriceSet; +import com.bms.customer.customerinfo.domain.BmsPriceSetOption; /** * 客户业务操作日志Service接口 @@ -28,6 +33,60 @@ public interface IBmsCustomerLogService */ void recordDelete(BmsCustomer customer); + /** + * 记录新增公司主体 + */ + void recordCompanyInsert(BmsCustomerCompany company); + + /** + * 记录修改公司主体(字段级变更,无变更不记录) + */ + void recordCompanyUpdate(BmsCustomerCompany oldCompany, BmsCustomerCompany newCompany); + + /** + * 记录删除公司主体 + */ + void recordCompanyDelete(BmsCustomerCompany company); + + /** + * 记录新增联系人 + */ + void recordContactInsert(BmsCustomerContact contact); + + /** + * 记录修改联系人(字段级变更,无变更不记录) + */ + void recordContactUpdate(BmsCustomerContact oldContact, BmsCustomerContact newContact); + + /** + * 记录删除联系人 + */ + void recordContactDelete(BmsCustomerContact contact); + + /** + * 客户档案附件全量重建前后的差异记录(新增/删除/修改的文件逐条记录) + * + * @param oldList 重建前附件(客户档案模块) + * @param newList 重建后附件 + */ + void recordAttachmentRebuild(List oldList, List newList); + + /** + * 记录新增客户档案附件(随客户新建上传) + */ + void recordAttachmentInsert(BmsCustomerAttachment attachment); + + /** + * 记录套餐绑定/更换(existing 为空=首次绑定) + */ + void recordPriceSetBind(BmsCustomer customer, BmsCustomerPriceSet existing, + BmsCustomerPriceSet bind, BmsPriceSetOption newSet); + + /** + * 记录套餐解绑 + */ + void recordPriceSetUnbind(BmsCustomerPriceSet bind); + /** * 查询某客户的全部业务日志(时间倒序,含已解析的变更明细) */ diff --git a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerLogServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerLogServiceImpl.java index 65799ca5..eebabbba 100644 --- a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerLogServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerLogServiceImpl.java @@ -1,7 +1,10 @@ package com.bms.customer.customerinfo.service.impl; import java.math.BigDecimal; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -12,9 +15,16 @@ import com.bms.common.core.domain.entity.SysUser; import com.bms.common.utils.DateUtils; import com.bms.common.utils.SecurityUtils; import com.bms.customer.customerinfo.domain.BmsCustomer; +import com.bms.customer.customerinfo.domain.BmsCustomerAttachment; +import com.bms.customer.customerinfo.domain.BmsCustomerCompany; +import com.bms.customer.customerinfo.domain.BmsCustomerContact; import com.bms.customer.customerinfo.domain.BmsCustomerLog; +import com.bms.customer.customerinfo.domain.BmsCustomerPriceSet; +import com.bms.customer.customerinfo.domain.BmsPriceSetOption; import com.bms.customer.customerinfo.mapper.BmsCustomerLogMapper; import com.bms.customer.customerinfo.service.IBmsCustomerLogService; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -30,6 +40,9 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService @Autowired private BmsCustomerLogMapper bmsCustomerLogMapper; + private static final ThreadLocal DATE_FMT = + ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); + /** 业务类型字典 */ private static final Map CUSTOMER_TYPE_MAP = Map.of( "0", "直客", "1", "同行", "2", "集运"); @@ -41,6 +54,17 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService private static final Map SETTLEMENT_TYPE_MAP = Map.of( "0", "月结", "1", "周结", "2", "日结"); + /** 联系人类型字典 */ + private static final Map CONTACT_TYPE_MAP = Map.of( + "0", "业务联系人", "1", "财务联系人", "2", "紧急联系人", "3", "其他"); + + /** 是否类字典 */ + private static final Map YES_NO_MAP = Map.of("0", "否", "1", "是"); + + /** 附件分类字典 */ + private static final Map ATTACHMENT_TYPE_MAP = Map.of( + "0", "附件资料", "1", "客户Logo", "2", "合同文件", "3", "其他"); + /** * 需要记录日志的主表字段(顺序即展示顺序)。 * requireNonBlank 为 true 的字段对应 update SQL 的 <if test="x!=null and x!=''">, @@ -66,6 +90,80 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService new FieldDef("productCodes", "绑定产品", BmsCustomer::getProductCodes, false, Function.identity()), new FieldDef("remark", "备注", BmsCustomer::getRemark, false, Function.identity())); + /** 公司主体需记录的字段(顺序即展示顺序) */ + private static final LinkedHashMap COMPANY_FIELDS = new LinkedHashMap<>(); + static + { + COMPANY_FIELDS.put("companyNameFull", "公司全称"); + COMPANY_FIELDS.put("englishName", "英文名称"); + COMPANY_FIELDS.put("creditCode", "统一社会信用代码"); + COMPANY_FIELDS.put("legalPerson", "法定代表人"); + COMPANY_FIELDS.put("legalPersonPhone", "法定代表人电话"); + COMPANY_FIELDS.put("registeredCapital", "注册资本(万元)"); + COMPANY_FIELDS.put("capitalCurrency", "注册资本币种"); + COMPANY_FIELDS.put("paidInCapital", "实收资本(万元)"); + COMPANY_FIELDS.put("establishDate", "成立日期"); + COMPANY_FIELDS.put("businessTermStart", "营业期限起"); + COMPANY_FIELDS.put("businessTermEnd", "营业期限止"); + COMPANY_FIELDS.put("businessTermPermanent", "是否长期有效"); + COMPANY_FIELDS.put("registrationAuthority", "登记机关"); + COMPANY_FIELDS.put("registrationNumber", "注册号"); + COMPANY_FIELDS.put("operatingStatus", "经营状态"); + COMPANY_FIELDS.put("employeeCount", "员工人数"); + COMPANY_FIELDS.put("businessScope", "经营范围"); + COMPANY_FIELDS.put("shareholders", "主要股东"); + COMPANY_FIELDS.put("companyPhone", "公司电话"); + COMPANY_FIELDS.put("companyFax", "公司传真"); + COMPANY_FIELDS.put("companyWebsite", "公司网址"); + COMPANY_FIELDS.put("companyEmail", "公司邮箱"); + COMPANY_FIELDS.put("registeredProvince", "注册省份"); + COMPANY_FIELDS.put("registeredCity", "注册城市"); + COMPANY_FIELDS.put("registeredDistrict", "注册区县"); + COMPANY_FIELDS.put("registeredAddressDetail", "注册详细地址"); + COMPANY_FIELDS.put("remark", "备注"); + } + + /** 联系人需记录的字段 */ + private static final LinkedHashMap CONTACT_FIELDS = new LinkedHashMap<>(); + static + { + CONTACT_FIELDS.put("contactName", "联系人姓名"); + CONTACT_FIELDS.put("position", "职位"); + CONTACT_FIELDS.put("department", "所属部门"); + CONTACT_FIELDS.put("phone", "手机号"); + CONTACT_FIELDS.put("landline", "座机"); + CONTACT_FIELDS.put("email", "电子邮箱"); + CONTACT_FIELDS.put("wechat", "微信号"); + CONTACT_FIELDS.put("qq", "QQ号"); + CONTACT_FIELDS.put("country", "国家/地区"); + CONTACT_FIELDS.put("province", "省份"); + CONTACT_FIELDS.put("city", "城市"); + CONTACT_FIELDS.put("district", "区/县"); + CONTACT_FIELDS.put("contactAddress", "详细地址"); + CONTACT_FIELDS.put("postalCode", "邮政编码"); + CONTACT_FIELDS.put("contactType", "联系人类型"); + CONTACT_FIELDS.put("isDefault", "是否默认"); + CONTACT_FIELDS.put("sortOrder", "排序"); + } + + /** 附件需记录的字段 */ + private static final LinkedHashMap ATTACHMENT_FIELDS = new LinkedHashMap<>(); + static + { + ATTACHMENT_FIELDS.put("fileName", "文件名称"); + ATTACHMENT_FIELDS.put("attachmentType", "附件分类"); + ATTACHMENT_FIELDS.put("sortOrder", "排序"); + ATTACHMENT_FIELDS.put("remark", "备注"); + } + + private static final Map> FIELD_DICTS = Map.of( + "businessTermPermanent", YES_NO_MAP, + "contactType", CONTACT_TYPE_MAP, + "isDefault", YES_NO_MAP, + "attachmentType", ATTACHMENT_TYPE_MAP); + + /* ============================== 客户主表 ============================== */ + @Override public void recordInsert(BmsCustomer customer) { @@ -82,7 +180,8 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService changes.add(new BmsCustomerLog.FieldChange(def.field, def.label, "", value)); } } - saveLog(customer, BmsCustomerLog.OP_INSERT, changes); + saveLog(customer.getCustomerId(), customer.getCustomerCode(), customer.getCustomerName(), + BmsCustomerLog.MODULE_MAIN, null, BmsCustomerLog.OP_INSERT, changes); } @Override @@ -117,8 +216,8 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService { return; } - // 日志快照以修改后的客户编码/名称为准 - saveLog(newCustomer, BmsCustomerLog.OP_UPDATE, changes); + saveLog(newCustomer.getCustomerId(), newCustomer.getCustomerCode(), newCustomer.getCustomerName(), + BmsCustomerLog.MODULE_MAIN, null, BmsCustomerLog.OP_UPDATE, changes); } @Override @@ -128,9 +227,236 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService { return; } - saveLog(customer, BmsCustomerLog.OP_DELETE, null); + saveLog(customer.getCustomerId(), customer.getCustomerCode(), customer.getCustomerName(), + BmsCustomerLog.MODULE_MAIN, null, BmsCustomerLog.OP_DELETE, null); } + /* ============================== 公司主体 ============================== */ + + @Override + public void recordCompanyInsert(BmsCustomerCompany company) + { + if (company == null || company.getCustomerId() == null) + { + return; + } + saveLog(company.getCustomerId(), null, null, BmsCustomerLog.MODULE_COMPANY, + company.getCompanyNameFull(), BmsCustomerLog.OP_INSERT, + beanInsertChanges(company, COMPANY_FIELDS)); + } + + @Override + public void recordCompanyUpdate(BmsCustomerCompany oldCompany, BmsCustomerCompany newCompany) + { + if (oldCompany == null || newCompany == null || newCompany.getCustomerId() == null) + { + return; + } + List changes = beanDiff(oldCompany, newCompany, COMPANY_FIELDS); + if (changes.isEmpty()) + { + return; + } + String bizName = trimToNull(newCompany.getCompanyNameFull()); + if (bizName == null) + { + bizName = oldCompany.getCompanyNameFull(); + } + saveLog(newCompany.getCustomerId(), null, null, BmsCustomerLog.MODULE_COMPANY, + bizName, BmsCustomerLog.OP_UPDATE, changes); + } + + @Override + public void recordCompanyDelete(BmsCustomerCompany company) + { + if (company == null || company.getCustomerId() == null) + { + return; + } + saveLog(company.getCustomerId(), null, null, BmsCustomerLog.MODULE_COMPANY, + company.getCompanyNameFull(), BmsCustomerLog.OP_DELETE, null); + } + + /* ============================== 联系人 ============================== */ + + @Override + public void recordContactInsert(BmsCustomerContact contact) + { + if (contact == null || contact.getCustomerId() == null) + { + return; + } + saveLog(contact.getCustomerId(), null, null, BmsCustomerLog.MODULE_CONTACT, + contact.getContactName(), BmsCustomerLog.OP_INSERT, + beanInsertChanges(contact, CONTACT_FIELDS)); + } + + @Override + public void recordContactUpdate(BmsCustomerContact oldContact, BmsCustomerContact newContact) + { + if (oldContact == null || newContact == null || newContact.getCustomerId() == null) + { + return; + } + List changes = beanDiff(oldContact, newContact, CONTACT_FIELDS); + if (changes.isEmpty()) + { + return; + } + String bizName = trimToNull(newContact.getContactName()); + if (bizName == null) + { + bizName = oldContact.getContactName(); + } + saveLog(newContact.getCustomerId(), null, null, BmsCustomerLog.MODULE_CONTACT, + bizName, BmsCustomerLog.OP_UPDATE, changes); + } + + @Override + public void recordContactDelete(BmsCustomerContact contact) + { + if (contact == null || contact.getCustomerId() == null) + { + return; + } + saveLog(contact.getCustomerId(), null, null, BmsCustomerLog.MODULE_CONTACT, + contact.getContactName(), BmsCustomerLog.OP_DELETE, null); + } + + /* ============================== 附件 ============================== */ + + @Override + public void recordAttachmentInsert(BmsCustomerAttachment attachment) + { + if (attachment == null || attachment.getCustomerId() == null) + { + return; + } + saveLog(attachment.getCustomerId(), null, null, BmsCustomerLog.MODULE_ATTACHMENT, + attachment.getFileName(), BmsCustomerLog.OP_INSERT, + beanInsertChanges(attachment, ATTACHMENT_FIELDS)); + } + + @Override + public void recordAttachmentRebuild(List oldListIn, List newListIn) + { + List oldList = safe(oldListIn); + List newList = safe(newListIn); + Map oldMap = new LinkedHashMap<>(); + for (BmsCustomerAttachment a : oldList) + { + if (a.getFilePath() != null) + { + oldMap.put(a.getFilePath(), a); + } + } + Map newMap = new LinkedHashMap<>(); + for (BmsCustomerAttachment a : newList) + { + if (a.getFilePath() != null) + { + newMap.put(a.getFilePath(), a); + } + } + Long customerId = null; + if (!oldList.isEmpty()) + { + customerId = oldList.get(0).getCustomerId(); + } + if (!newList.isEmpty()) + { + customerId = newList.get(0).getCustomerId(); + } + if (customerId == null) + { + return; + } + // 新增的文件 + for (Map.Entry e : newMap.entrySet()) + { + if (!oldMap.containsKey(e.getKey())) + { + saveLog(customerId, null, null, BmsCustomerLog.MODULE_ATTACHMENT, + e.getValue().getFileName(), BmsCustomerLog.OP_INSERT, + beanInsertChanges(e.getValue(), ATTACHMENT_FIELDS)); + } + } + // 修改的文件(分类/排序/备注/文件名) + for (Map.Entry e : newMap.entrySet()) + { + BmsCustomerAttachment old = oldMap.get(e.getKey()); + if (old == null) + { + continue; + } + List changes = beanDiff(old, e.getValue(), ATTACHMENT_FIELDS); + if (!changes.isEmpty()) + { + String fileName = trimToNull(e.getValue().getFileName()); + saveLog(customerId, null, null, BmsCustomerLog.MODULE_ATTACHMENT, + fileName != null ? fileName : old.getFileName(), BmsCustomerLog.OP_UPDATE, changes); + } + } + // 删除的文件 + for (Map.Entry e : oldMap.entrySet()) + { + if (!newMap.containsKey(e.getKey())) + { + saveLog(customerId, null, null, BmsCustomerLog.MODULE_ATTACHMENT, + e.getValue().getFileName(), BmsCustomerLog.OP_DELETE, null); + } + } + } + + /* ============================== 套餐绑定 ============================== */ + + @Override + public void recordPriceSetBind(BmsCustomer customer, BmsCustomerPriceSet existing, + BmsCustomerPriceSet bind, BmsPriceSetOption newSet) + { + if (bind == null || bind.getCustomerId() == null || newSet == null) + { + return; + } + List changes = new ArrayList<>(); + changes.add(new BmsCustomerLog.FieldChange("priceSetName", "套餐", + existing == null ? "" : nz(existing.getPriceSetName()), nz(newSet.getPriceSetName()))); + changes.add(new BmsCustomerLog.FieldChange("bindPriority", "优先级", + existing == null ? "" : numText(existing.getBindPriority()), numText(bind.getBindPriority()))); + changes.add(new BmsCustomerLog.FieldChange("prBegindate", "生效日期", + existing == null ? "" : dateText(existing.getPrBegindate()), dateText(bind.getPrBegindate()))); + changes.add(new BmsCustomerLog.FieldChange("prEnddate", "失效日期", + existing == null ? "长期" : dateTextOrLong(existing.getPrEnddate()), dateTextOrLong(bind.getPrEnddate()))); + changes.add(new BmsCustomerLog.FieldChange("priceSetNote", "备注", + existing == null ? "" : nz(existing.getPriceSetNote()), nz(bind.getPriceSetNote()))); + List realChanges = new ArrayList<>(); + for (BmsCustomerLog.FieldChange c : changes) + { + if (!Objects.equals(emptyToEmpty(c.getOldValue()), emptyToEmpty(c.getNewValue()))) + { + realChanges.add(c); + } + } + saveLog(bind.getCustomerId(), customer == null ? null : customer.getCustomerCode(), + customer == null ? null : customer.getCustomerName(), + BmsCustomerLog.MODULE_PRICE_SET, newSet.getPriceSetName(), + existing == null ? BmsCustomerLog.OP_INSERT : BmsCustomerLog.OP_UPDATE, realChanges); + } + + @Override + public void recordPriceSetUnbind(BmsCustomerPriceSet bind) + { + if (bind == null || bind.getCustomerId() == null) + { + return; + } + saveLog(bind.getCustomerId(), bind.getCustomerCode(), bind.getCustomerName(), + BmsCustomerLog.MODULE_PRICE_SET, bind.getPriceSetName(), + BmsCustomerLog.OP_DELETE, null); + } + + /* ============================== 查询 ============================== */ + @Override public List selectByCustomerId(Long customerId) { @@ -145,12 +471,18 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService return list; } - private void saveLog(BmsCustomer customer, String operationType, List changes) + /* ============================== 内部工具 ============================== */ + + private void saveLog(Long customerId, String customerCode, String customerName, + String module, String bizName, String operationType, + List changes) { BmsCustomerLog log = new BmsCustomerLog(); - log.setCustomerId(customer.getCustomerId()); - log.setCustomerCode(customer.getCustomerCode()); - log.setCustomerName(customer.getCustomerName()); + log.setCustomerId(customerId); + log.setCustomerCode(customerCode); + log.setCustomerName(customerName); + log.setBizModule(module); + log.setBizName(bizName); log.setOperationType(operationType); if (changes != null) { @@ -170,6 +502,88 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService bmsCustomerLogMapper.insertBmsCustomerLog(log); } + /** 新增场景:取所有非空字段作为初始值 */ + private List beanInsertChanges(Object bean, LinkedHashMap fields) + { + List changes = new ArrayList<>(); + BeanWrapper bw = new BeanWrapperImpl(bean); + for (Map.Entry f : fields.entrySet()) + { + String value = displayValue(bw, f.getKey()); + if (value != null && !value.isEmpty()) + { + changes.add(new BmsCustomerLog.FieldChange(f.getKey(), f.getValue(), "", value)); + } + } + return changes; + } + + /** 修改场景:逐字段比对,返回变化项 */ + private List beanDiff(Object oldBean, Object newBean, + LinkedHashMap fields) + { + List changes = new ArrayList<>(); + BeanWrapper oldBw = new BeanWrapperImpl(oldBean); + BeanWrapper newBw = new BeanWrapperImpl(newBean); + for (Map.Entry f : fields.entrySet()) + { + Object newRaw = newBw.getPropertyValue(f.getKey()); + // 与 update 动态 SQL 保持一致:新值为 null 的字段本次不落库,不参与比对 + if (newRaw == null) + { + continue; + } + Object oldRaw = oldBw.getPropertyValue(f.getKey()); + if (!sameValue(oldRaw, newRaw)) + { + changes.add(new BmsCustomerLog.FieldChange(f.getKey(), f.getValue(), + nz(displayValue(oldBw, f.getKey())), nz(displayValue(newBw, f.getKey())))); + } + } + return changes; + } + + /** 字段值 -> 日志展示文本(字典翻译/日期格式化) */ + private String displayValue(BeanWrapper bw, String field) + { + Object raw = bw.getPropertyValue(field); + if (raw == null) + { + return ""; + } + if (raw instanceof Date) + { + return DATE_FMT.get().format((Date) raw); + } + String text = raw.toString(); + if (text.isEmpty()) + { + return ""; + } + Map dict = FIELD_DICTS.get(field); + if (dict != null) + { + String translated = dict.get(text); + return translated != null ? translated : text; + } + return text; + } + + private static List safe(List list) + { + return list == null ? List.of() : list; + } + + private static String emptyToEmpty(String s) + { + return s == null ? "" : s; + } + + private static String nz(String s) + { + return s == null ? "" : s; + } + /** 人员字段展示:姓名(工号),缺姓名时只显示工号 */ private static String userDisplay(String code, String name) { @@ -195,6 +609,16 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService return num == null ? "" : num.toString(); } + private static String dateText(Date date) + { + return date == null ? "" : DATE_FMT.get().format(date); + } + + private static String dateTextOrLong(Date date) + { + return date == null ? "长期" : DATE_FMT.get().format(date); + } + private static String trimToNull(String s) { if (s == null) @@ -205,18 +629,32 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService return t.isEmpty() ? null : t; } - /** 数值按大小比较(规避 BigDecimal 精度差异),其余按字符串比较 */ + /** 数值按大小比较(规避 BigDecimal 精度差异),日期按时间比较,其余按字符串比较 */ private static boolean sameValue(Object a, Object b) { if (a instanceof BigDecimal && b instanceof BigDecimal) { return ((BigDecimal) a).compareTo((BigDecimal) b) == 0; } - return Objects.equals(a == null ? null : a.toString(), b == null ? null : b.toString()); + if (a instanceof Date && b instanceof Date) + { + return ((Date) a).getTime() == ((Date) b).getTime(); + } + String sa = a == null ? null : a.toString(); + String sb = b == null ? null : b.toString(); + if ("".equals(sa)) + { + sa = null; + } + if ("".equals(sb)) + { + sb = null; + } + return Objects.equals(sa, sb); } /** - * 字段定义 + * 主表字段定义 */ private static final class FieldDef { diff --git a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerPriceSetServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerPriceSetServiceImpl.java index 43b7438c..d14723aa 100644 --- a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerPriceSetServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerPriceSetServiceImpl.java @@ -9,6 +9,7 @@ import com.bms.customer.customerinfo.domain.BmsCustomer; import com.bms.customer.customerinfo.domain.BmsCustomerPriceSet; import com.bms.customer.customerinfo.domain.BmsPriceSetOption; import com.bms.customer.customerinfo.mapper.BmsCustomerPriceSetMapper; +import com.bms.customer.customerinfo.service.IBmsCustomerLogService; import com.bms.customer.customerinfo.service.IBmsCustomerPriceSetService; import com.bms.customer.customerinfo.service.IBmsCustomerService; @@ -27,6 +28,9 @@ public class BmsCustomerPriceSetServiceImpl implements IBmsCustomerPriceSetServi @Autowired private IBmsCustomerService customerService; + @Autowired + private IBmsCustomerLogService customerLogService; + @Override public List selectBindList(Long customerId) { @@ -85,19 +89,37 @@ public class BmsCustomerPriceSetServiceImpl implements IBmsCustomerPriceSetServi bind.setCustomerName(customer.getCustomerName()); BmsCustomerPriceSet existing = bindMapper.selectBindByCustomerId(bind.getCustomerId()); + // selectBindByCustomerId 未关联套餐表,补取旧套餐名用于日志展示 + if (existing != null && existing.getPriceSetId() != null) + { + BmsPriceSetOption oldSet = bindMapper.selectPriceSetOptionById(existing.getPriceSetId()); + if (oldSet != null) + { + existing.setPriceSetName(oldSet.getPriceSetName()); + } + } + int rows; try { if (existing != null) { bind.setCustomerPricesetId(existing.getCustomerPricesetId()); - return bindMapper.updateBind(bind); + rows = bindMapper.updateBind(bind); + } + else + { + rows = bindMapper.insertBind(bind); } - return bindMapper.insertBind(bind); } catch (DuplicateKeyException e) { throw new ServiceException("该客户已存在套餐绑定,请勿重复绑定"); } + if (rows > 0) + { + customerLogService.recordPriceSetBind(customer, existing, bind, set); + } + return rows; } @Override @@ -107,6 +129,24 @@ public class BmsCustomerPriceSetServiceImpl implements IBmsCustomerPriceSetServi { throw new ServiceException("请选择要解绑的记录"); } - return bindMapper.deleteBindByIds(customerPricesetIds); + // 删除前逐个取快照,用于解绑日志 + List snapshots = new java.util.ArrayList<>(); + for (Long id : customerPricesetIds) + { + BmsCustomerPriceSet snapshot = bindMapper.selectBindById(id); + if (snapshot != null) + { + snapshots.add(snapshot); + } + } + int rows = bindMapper.deleteBindByIds(customerPricesetIds); + if (rows > 0) + { + for (BmsCustomerPriceSet snapshot : snapshots) + { + customerLogService.recordPriceSetUnbind(snapshot); + } + } + return rows; } } diff --git a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerServiceImpl.java index 159d64c7..5167a095 100644 --- a/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/customer/customerinfo/service/impl/BmsCustomerServiceImpl.java @@ -98,6 +98,15 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService */ @Override public void saveBmsCustomerAttachments(Long customerId, List attachmentList) + { + saveBmsCustomerAttachments(customerId, attachmentList, false); + } + + /** + * 批量保存客户附件。 + * @param logInsert true=随客户新建上传,逐文件记新增日志;false=编辑页全量重建,日志由重建差异统一记录 + */ + private void saveBmsCustomerAttachments(Long customerId, List attachmentList, boolean logInsert) { if (attachmentList == null || attachmentList.isEmpty()) { @@ -123,6 +132,10 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService item.setCreateBy(user.getUserName()); item.setCreateTime(DateUtils.getNowDate()); bmsCustomerMapper.insertBmsCustomerAttachment(item); + if (logInsert) + { + customerLogService.recordAttachmentInsert(item); + } } } } @@ -167,7 +180,12 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService { bmsCustomerCompany.setDelFlag("0"); } - return bmsCustomerMapper.insertBmsCustomerCompany(bmsCustomerCompany); + int rows = bmsCustomerMapper.insertBmsCustomerCompany(bmsCustomerCompany); + if (rows > 0) + { + customerLogService.recordCompanyInsert(bmsCustomerCompany); + } + return rows; } /** @@ -176,10 +194,17 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService @Override public int updateBmsCustomerCompany(BmsCustomerCompany bmsCustomerCompany) { + BmsCustomerCompany old = bmsCustomerCompany.getCompanyId() == null ? null + : bmsCustomerMapper.selectBmsCustomerCompanyByCompanyId(bmsCustomerCompany.getCompanyId()); SysUser user = SecurityUtils.getLoginUser().getUser(); bmsCustomerCompany.setUpdateBy(user.getUserName()); bmsCustomerCompany.setUpdateTime(DateUtils.getNowDate()); - return bmsCustomerMapper.updateBmsCustomerCompany(bmsCustomerCompany); + int rows = bmsCustomerMapper.updateBmsCustomerCompany(bmsCustomerCompany); + if (rows > 0) + { + customerLogService.recordCompanyUpdate(old, bmsCustomerCompany); + } + return rows; } /** @@ -191,7 +216,12 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService int rows = 0; for (Long companyId : companyIds) { + BmsCustomerCompany snapshot = bmsCustomerMapper.selectBmsCustomerCompanyByCompanyId(companyId); rows += bmsCustomerMapper.deleteBmsCustomerCompanyByCompanyId(companyId); + if (snapshot != null) + { + customerLogService.recordCompanyDelete(snapshot); + } } return rows; } @@ -216,15 +246,18 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService } if (item.getCompanyId() != null) { + BmsCustomerCompany old = bmsCustomerMapper.selectBmsCustomerCompanyByCompanyId(item.getCompanyId()); item.setUpdateBy(user.getUserName()); item.setUpdateTime(DateUtils.getNowDate()); bmsCustomerMapper.updateBmsCustomerCompany(item); + customerLogService.recordCompanyUpdate(old, item); } else { item.setCreateBy(user.getUserName()); item.setCreateTime(DateUtils.getNowDate()); bmsCustomerMapper.insertBmsCustomerCompany(item); + customerLogService.recordCompanyInsert(item); } } } @@ -245,7 +278,7 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService int rows = bmsCustomerMapper.insertBmsCustomer(bmsCustomer); if (rows > 0) { - saveBmsCustomerAttachments(bmsCustomer.getCustomerId(), bmsCustomer.getAttachmentList()); + saveBmsCustomerAttachments(bmsCustomer.getCustomerId(), bmsCustomer.getAttachmentList(), true); saveBmsCustomerCompanies(bmsCustomer.getCustomerId(), bmsCustomer.getCompanyList()); saveBmsCustomerContacts(bmsCustomer.getCustomerId(), bmsCustomer.getContactList()); // 新建客户即初始化默认CNY账户:余额0、总额度1000 CNY,写INIT流水 @@ -271,6 +304,11 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService bmsCustomer.setUpdateBy(user.getUserName()); // 修改前先取旧值快照,用于业务日志记录字段级变更 BmsCustomer oldCustomer = bmsCustomerMapper.selectBmsCustomerByCustomerId(bmsCustomer.getCustomerId()); + // 附件重建前取旧档案附件快照,重建后按文件差异记业务日志 + BmsCustomerAttachment attachmentQuery = new BmsCustomerAttachment(); + attachmentQuery.setCustomerId(bmsCustomer.getCustomerId()); + attachmentQuery.setBizType(BmsCustomerAttachment.BIZ_TYPE_CUSTOMER); + List oldAttachments = bmsCustomerMapper.selectBmsCustomerAttachmentList(attachmentQuery); // 仅重建客户档案模块的附件,收款水单等其他业务模块的附件保留不动 bmsCustomerMapper.deleteBmsCustomerProfileAttachmentByCustomerId(bmsCustomer.getCustomerId()); if (bmsCustomer.getAttachmentList() != null) @@ -286,10 +324,12 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService // 联系人采用增量 upsert saveBmsCustomerContacts(bmsCustomer.getCustomerId(), bmsCustomer.getContactList()); int rows = bmsCustomerMapper.updateBmsCustomer(bmsCustomer); - // 业务日志:修改(字段级 修改前→修改后;无实际变更时不记录) if (rows > 0) { + // 业务日志:主表字段级修改(无实际变更时不记录) customerLogService.recordUpdate(oldCustomer, bmsCustomer); + // 业务日志:附件新增/删除/修改差异 + customerLogService.recordAttachmentRebuild(oldAttachments, bmsCustomer.getAttachmentList()); } return rows; } @@ -384,7 +424,12 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService { bmsCustomerContact.setDelFlag("0"); } - return bmsCustomerMapper.insertBmsCustomerContact(bmsCustomerContact); + int rows = bmsCustomerMapper.insertBmsCustomerContact(bmsCustomerContact); + if (rows > 0) + { + customerLogService.recordContactInsert(bmsCustomerContact); + } + return rows; } /** @@ -393,10 +438,17 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService @Override public int updateBmsCustomerContact(BmsCustomerContact bmsCustomerContact) { + BmsCustomerContact old = bmsCustomerContact.getContactId() == null ? null + : bmsCustomerMapper.selectBmsCustomerContactByContactId(bmsCustomerContact.getContactId()); SysUser user = SecurityUtils.getLoginUser().getUser(); bmsCustomerContact.setUpdateBy(user.getUserName()); bmsCustomerContact.setUpdateTime(DateUtils.getNowDate()); - return bmsCustomerMapper.updateBmsCustomerContact(bmsCustomerContact); + int rows = bmsCustomerMapper.updateBmsCustomerContact(bmsCustomerContact); + if (rows > 0) + { + customerLogService.recordContactUpdate(old, bmsCustomerContact); + } + return rows; } /** @@ -408,7 +460,12 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService int rows = 0; for (Long contactId : contactIds) { + BmsCustomerContact snapshot = bmsCustomerMapper.selectBmsCustomerContactByContactId(contactId); rows += bmsCustomerMapper.deleteBmsCustomerContactByContactId(contactId); + if (snapshot != null) + { + customerLogService.recordContactDelete(snapshot); + } } return rows; } @@ -433,15 +490,18 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService } if (item.getContactId() != null) { + BmsCustomerContact old = bmsCustomerMapper.selectBmsCustomerContactByContactId(item.getContactId()); item.setUpdateBy(user.getUserName()); item.setUpdateTime(DateUtils.getNowDate()); bmsCustomerMapper.updateBmsCustomerContact(item); + customerLogService.recordContactUpdate(old, item); } else { item.setCreateBy(user.getUserName()); item.setCreateTime(DateUtils.getNowDate()); bmsCustomerMapper.insertBmsCustomerContact(item); + customerLogService.recordContactInsert(item); } } } diff --git a/bms-back/bms-system/src/main/resources/mapper/customer/BmsCustomerLogMapper.xml b/bms-back/bms-system/src/main/resources/mapper/customer/BmsCustomerLogMapper.xml index b57253a8..7d85bdb4 100644 --- a/bms-back/bms-system/src/main/resources/mapper/customer/BmsCustomerLogMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/customer/BmsCustomerLogMapper.xml @@ -7,6 +7,8 @@ + + @@ -15,8 +17,8 @@ - select log_id, customer_id, customer_code, customer_name, operation_type, change_content, - create_by, create_by_name, create_time + select log_id, customer_id, customer_code, customer_name, biz_module, biz_name, + operation_type, change_content, create_by, create_by_name, create_time from ctr_customer_log @@ -27,6 +29,8 @@ customer_id, customer_code, customer_name, + biz_module, + biz_name, operation_type, change_content, create_by, @@ -37,6 +41,8 @@ #{customerId}, #{customerCode}, #{customerName}, + #{bizModule}, + #{bizName}, #{operationType}, #{changeContent}, #{createBy}, diff --git a/bms-front/src/views/customersuppliermanagement/customerinfo/components/CustomerLogDialog.vue b/bms-front/src/views/customersuppliermanagement/customerinfo/components/CustomerLogDialog.vue new file mode 100644 index 00000000..e56c0b60 --- /dev/null +++ b/bms-front/src/views/customersuppliermanagement/customerinfo/components/CustomerLogDialog.vue @@ -0,0 +1,133 @@ + + + diff --git a/bms-front/src/views/customersuppliermanagement/customerinfo/customeredit/index.vue b/bms-front/src/views/customersuppliermanagement/customerinfo/customeredit/index.vue index 91f3e99a..82e97121 100644 --- a/bms-front/src/views/customersuppliermanagement/customerinfo/customeredit/index.vue +++ b/bms-front/src/views/customersuppliermanagement/customerinfo/customeredit/index.vue @@ -10,6 +10,7 @@
客户编码:{{ form.customerCode || '保存后可维护子表数据' }}
+ 日 志 取 消 保 存 保 存 @@ -711,6 +712,14 @@
+ + + @@ -719,6 +728,7 @@ import { listCustomer, getCustomer, addCustomer, updateCustomer, delCustomerCompany, delCustomerContact } from '@/api/customersuppliermanagement/customerinfo/customer.js' +import CustomerLogDialog from '../components/CustomerLogDialog.vue' import { listCustomerPriceSet, listPriceSetOption, saveCustomerPriceSet, delCustomerPriceSet @@ -740,6 +750,7 @@ const { ensureProducts, findProduct } = useProducts() const pageLoading = ref(false) const saving = ref(false) const activeTab = ref('base') +const logOpen = ref(false) const customerId = computed(() => route.query.customerId ? Number(route.query.customerId) : null) const isAdd = computed(() => !customerId.value) diff --git a/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue b/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue index 1fc649c2..6f246245 100644 --- a/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue +++ b/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue @@ -197,61 +197,19 @@ /> - -
- 客户编码:{{ logCustomer.customerCode || '-' }} - 共 {{ logList.length }} 条日志(按操作时间倒序) -
- - - - - - - - - - - - - - - - - - -
+