diff --git a/src/main/java/com/example/web/service/CustomerChangeMonitorService.java b/src/main/java/com/example/web/service/CustomerChangeMonitorService.java new file mode 100644 index 0000000..c121f6c --- /dev/null +++ b/src/main/java/com/example/web/service/CustomerChangeMonitorService.java @@ -0,0 +1,93 @@ +package com.example.web.service; + +import com.example.web.entity.CustomerData; +import com.example.web.entity.Login; +import com.example.web.dto.NotificationDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 客户数据变化监控服务 + * 用于监控数据库中客户数据的变化,当管理员在外部系统修改客户数据时,通知对应的业务员 + */ +@Service +public class CustomerChangeMonitorService { + + @Autowired + private CustomerService customerService; + + @Autowired + private NotificationService notificationService; + + // 存储上次检查时的客户数据状态 + private Map lastCustomerStates = new HashMap<>(); + + /** + * 定时检查客户数据变化 + * 每5秒检查一次 + */ + @Scheduled(fixedRate = 5000) + public void monitorCustomerChanges() { + try { + // 获取所有客户数据 + List allCustomers = customerService.getAllCustomers(); + + // 检查每个客户的数据变化 + for (CustomerData customer : allCustomers) { + // 构建客户状态字符串,包含关键字段 + String currentState = buildCustomerState(customer); + String customerId = customer.getId(); + String lastState = lastCustomerStates.get(customerId); + + // 如果是新客户或者客户数据发生变化 + if (lastState == null || !lastState.equals(currentState)) { + // 检查是否是分配给业务员的情况 + if (customer.getUserName() != null && !customer.getUserName().isEmpty() && + "banold".equals(customer.getNotice())) { + + // 发送客户分配通知 + NotificationDTO notification = new NotificationDTO(); + notification.setType("CUSTOMER_ASSIGNMENT"); + notification.setTitle("客户分配通知"); + notification.setMessage("您已被分配了一个新客户: " + customer.getNickName()); + notification.setCustomerId(customerId); + notification.setTimestamp(LocalDateTime.now(ZoneOffset.UTC)); + + // 广播通知 + notificationService.broadcastNotification(notification); + + // 发送特定客户通知 + notificationService.sendCustomerUpdateNotification(customerId, "客户已分配给您"); + } + + // 更新客户状态 + lastCustomerStates.put(customerId, currentState); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 构建客户状态字符串,用于比较客户数据是否变化 + */ + private String buildCustomerState(CustomerData customer) { + return String.format("%s|%s|%s|%s|%s|%s|%s|%s", + customer.getId(), + customer.getNickName(), + customer.getUserName(), + customer.getManagercompany(), + customer.getManagerdepartment(), + customer.getOrganization(), + customer.getNotice(), + customer.getUpdated_at()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/web/service/impl/CustomerServiceImpl.java b/src/main/java/com/example/web/service/impl/CustomerServiceImpl.java index 7c77ec4..b3324f4 100644 --- a/src/main/java/com/example/web/service/impl/CustomerServiceImpl.java +++ b/src/main/java/com/example/web/service/impl/CustomerServiceImpl.java @@ -15,6 +15,7 @@ import com.example.web.mapper.CustomerMapper; import com.example.web.mapper.InformationTraMapper; import com.example.web.mapper.WechatCustomerMapper; import com.example.web.service.CustomerService; +import com.example.web.service.NotificationService; import com.example.web.utils.CustomerTraceUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -44,6 +45,9 @@ public class CustomerServiceImpl implements CustomerService { @Autowired private CustomerTraceUtil customerTraceUtil; + + @Autowired + private NotificationService notificationService; @Override public List getAllCustomers() { @@ -330,6 +334,9 @@ public class CustomerServiceImpl implements CustomerService { // 记录客户认领操作 customerTraceUtil.recordCustomerFollowup(contacts.getId(), login.getManagercompany(), login.getManagerdepartment(), login.getOrganization(), login.getProjectName(), login.getUserName(), originalData.toString(), modifiedData.toString(), changedFields); + // 发送客户认领通知 + notificationService.sendCustomerUpdateNotification(contacts.getId(), "客户已被认领"); + return true; } @@ -416,6 +423,9 @@ public class CustomerServiceImpl implements CustomerService { // 记录客户认领操作 customerTraceUtil.recordCustomerFollowup(String.valueOf(user.getUserId()), login.getManagercompany(), login.getManagerdepartment(), login.getOrganization(), login.getProjectName(), login.getUserName(), originalData.toString(), modifiedData.toString(), changedFields); + + // 发送客户认领通知 + notificationService.sendCustomerUpdateNotification(String.valueOf(user.getUserId()), "客户已被认领"); } return result > 0; @@ -868,6 +878,9 @@ public class CustomerServiceImpl implements CustomerService { // 记录客户编辑操作 customerTraceUtil.recordCustomerEdit(existingContacts.getId(), login.getManagercompany(), login.getManagerdepartment(), login.getOrganization(), login.getProjectName(), login.getUserName(), originalData, modifiedData, changedFields); + // 发送客户更新通知 + notificationService.sendCustomerUpdateNotification(existingContacts.getId(), "客户信息已更新"); + return primaryResult; } @@ -1021,9 +1034,12 @@ public class CustomerServiceImpl implements CustomerService { } // 记录客户编辑操作 - customerTraceUtil.recordCustomerEdit(String.valueOf(existingUser.getUserId()), login.getManagercompany(), login.getManagerdepartment(), login.getOrganization(), login.getProjectName(), login.getUserName(), originalData, modifiedData, changedFields); - - return wechatResult; + customerTraceUtil.recordCustomerEdit(String.valueOf(existingUser.getUserId()), login.getManagercompany(), login.getManagerdepartment(), login.getOrganization(), login.getProjectName(), login.getUserName(), originalData, modifiedData, changedFields); + + // 发送客户更新通知 + notificationService.sendCustomerUpdateNotification(String.valueOf(existingUser.getUserId()), "客户信息已更新"); + + return wechatResult; } } catch (Exception e) { System.out.println("DEBUG: 从wechat数据源查询用户信息失败,异常信息: " + e.getMessage()); @@ -1172,12 +1188,22 @@ public class CustomerServiceImpl implements CustomerService { System.out.println("DEBUG: 获取联系人信息结果: " + (contacts != null ? "找到" : "未找到")); if (contacts != null) { - // 更新联系人信息 - contacts.setWechat((String) customerData.get("wechat")); - contacts.setAccount((String) customerData.get("account")); - contacts.setAccountNumber((String) customerData.get("accountNumber")); - contacts.setBank((String) customerData.get("bank")); - contacts.setAddress((String) customerData.get("address")); + // 只更新customerData中存在的字段,避免将现有字段设为null + if (customerData.containsKey("wechat")) { + contacts.setWechat((String) customerData.get("wechat")); + } + if (customerData.containsKey("account")) { + contacts.setAccount((String) customerData.get("account")); + } + if (customerData.containsKey("accountNumber")) { + contacts.setAccountNumber((String) customerData.get("accountNumber")); + } + if (customerData.containsKey("bank")) { + contacts.setBank((String) customerData.get("bank")); + } + if (customerData.containsKey("address")) { + contacts.setAddress((String) customerData.get("address")); + } contacts.setUpdated_at(now); int contactsUpdateResult = wechatCustomerMapper.updateUsersContacts(contacts); System.out.println("DEBUG: 更新联系人信息结果: " + contactsUpdateResult); @@ -1188,13 +1214,25 @@ public class CustomerServiceImpl implements CustomerService { System.out.println("DEBUG: 获取负责人信息结果: " + (managements != null ? "找到" : "未找到")); if (managements != null) { - // 更新负责人信息 - managements.setManagercompany((String) customerData.get("managercompany")); - managements.setManagerdepartment((String) customerData.get("managerdepartment")); - managements.setOrganization((String) customerData.get("organization")); - managements.setRole((String) customerData.get("projectName")); - managements.setUserName((String) customerData.get("userName")); - managements.setAssistant((String) customerData.get("assistant")); + // 只更新customerData中存在的字段,避免将现有字段设为null + if (customerData.containsKey("managercompany")) { + managements.setManagercompany((String) customerData.get("managercompany")); + } + if (customerData.containsKey("managerdepartment")) { + managements.setManagerdepartment((String) customerData.get("managerdepartment")); + } + if (customerData.containsKey("organization")) { + managements.setOrganization((String) customerData.get("organization")); + } + if (customerData.containsKey("projectName")) { + managements.setRole((String) customerData.get("projectName")); + } + if (customerData.containsKey("userName")) { + managements.setUserName((String) customerData.get("userName")); + } + if (customerData.containsKey("assistant")) { + managements.setAssistant((String) customerData.get("assistant")); + } managements.setUpdated_at(now); int managementsUpdateResult = wechatCustomerMapper.updateUsersManagements(managements); System.out.println("DEBUG: 更新负责人信息结果: " + managementsUpdateResult); diff --git a/src/main/resources/static/sells.html b/src/main/resources/static/sells.html index 1c9e2d1..d2ab632 100644 --- a/src/main/resources/static/sells.html +++ b/src/main/resources/static/sells.html @@ -4489,10 +4489,12 @@ // 所有可能的等级 const allLevels = ['important', 'regular', 'low-value', 'logistics', 'unclassified', 'department-sea-pools', 'organization-sea-pools']; + // 非公海池等级(用于全部客户视图) + const nonSeaPoolLevels = ['important', 'regular', 'low-value', 'logistics', 'unclassified']; if (level === 'all') { // 全部客户包含所有等级,包括部门和组织公海池 - allLevels.forEach(level => { + nonSeaPoolLevels.forEach(level => { const tbody = document.getElementById(`${level}-customers`); tbody.style.display = 'table-row-group'; // 全部客户视图,隐藏没有数据的等级的"暂无数据"提示 @@ -4521,14 +4523,12 @@ // 非全部客户视图,如果没有数据则显示"暂无数据"提示 if (tbody.children.length === 0) { const noDataText = { - 'important': '暂无重要客户数据', - 'regular': '暂无普通客户数据', - 'low-value': '暂无低价值客户数据', - 'logistics': '暂无物流客户数据', - 'unclassified': '暂无未分级客户数据', - 'department-sea-pools': '暂无部门公海客户数据', - 'organization-sea-pools': '暂无组织公海客户数据' - }[level] || '暂无客户数据'; + 'important': '暂无重要客户数据', + 'regular': '暂无普通客户数据', + 'low-value': '暂无低价值客户数据', + 'logistics': '暂无物流客户数据', + 'unclassified': '暂无未分级客户数据' + }[level] || '暂无客户数据'; tbody.innerHTML = `${noDataText}`; } } else { @@ -5606,10 +5606,13 @@ updateOrganizationSeaPoolTable(); } - // 同时更新全局客户列表和相关表格 - updateRecentCustomers(); - updateStatsCards(); - updateCustomerTable(customer); + // 重新获取客户数据,确保认领的客户出现在我的客户列表中 + // 检查stompClient是否存在 + if (typeof stompClient !== 'undefined' && stompClient && stompClient.connected) { + const loginInfo = getLoginUserInfo(); + // 重新请求获取最新的客户数据 - 使用buyer角色 + stompClient.send("/app/customer/role/buyer", {}, JSON.stringify(loginInfo)); + } } else { alert('客户认领失败: ' + (data.message || '未知错误')); } @@ -6737,9 +6740,9 @@ }); // WebSocket客户端逻辑 + let stompClient = null; // 暴露到全局作用域 (function () { - // 全局变量 - let stompClient = null; + // 局部变量 let loginUser = null; // 初始化WebSocket连接 @@ -6789,6 +6792,41 @@ handleOrganizationSeaPoolMessage(organizationSeaPoolCustomers); }); + // 订阅客户更新通知 + stompClient.subscribe('/topic/notifications', function(response) { + try { + const notification = JSON.parse(response.body); + console.log('收到客户更新通知:', notification); + + // 更新通知显示 + updateNotificationDisplay(); + + // 重新获取客户数据 + const loginInfo = getLoginUserInfo(); + stompClient.send("/app/customer/role/buyer", {}, JSON.stringify(loginInfo)); + + // 获取部门和组织公海池数据 + stompClient.send("/app/customer/departmentSeaPool/buyer", {}, JSON.stringify(loginInfo)); + stompClient.send("/app/customer/organizationSeaPool/buyer", {}, JSON.stringify(loginInfo)); + } catch (error) { + console.error('处理通知数据时出错:', error); + } + }); + + // 订阅特定客户更新通知 + stompClient.subscribe('/topic/customers/*', function(response) { + try { + const notification = JSON.parse(response.body); + console.log('收到特定客户更新通知:', notification); + + // 重新获取客户数据 + const loginInfo = getLoginUserInfo(); + stompClient.send("/app/customer/role/buyer", {}, JSON.stringify(loginInfo)); + } catch (error) { + console.error('处理特定客户通知时出错:', error); + } + }); + // 订阅buyer角色数据(sells.html页面显示buyer类型客户) stompClient.subscribe('/topic/role/buyer', function(response) { const customers = JSON.parse(response.body); @@ -6855,8 +6893,17 @@ console.log('过滤后的客户数据:', filteredCustomers); - // 更新客户数据,过滤掉Colleague类型的客户 + // 更新客户数据,过滤掉Colleague类型的客户,保留公海池客户数据 const newCustomerData = {}; + + // 先保留现有公海池客户数据 + Object.values(customerData).forEach(customer => { + if (customer.level && customer.level.includes('sea-pools')) { + newCustomerData[customer.id] = customer; + } + }); + + // 再添加当前用户的客户数据 filteredCustomers.forEach(customer => { // 过滤掉Colleague类型的客户 if (customer.type === 'Colleague') { @@ -6881,27 +6928,29 @@ updateStatsCards(); updateRecentCustomers(); - // 收集所有唯一的客户等级 + // 收集所有唯一的客户等级(不包括公海池) const customerList = Object.values(customerData); - const levels = [...new Set(customerList.map(customer => customer.level || 'unclassified'))]; + const levels = [...new Set(customerList.map(customer => customer.level || 'unclassified'))].filter(level => !level.includes('sea-pools')); - // 清空所有等级的客户表格 - const allLevels = ['important', 'regular', 'low-value', 'logistics', 'unclassified', 'department-sea-pools', 'organization-sea-pools']; - allLevels.forEach(level => { + // 只清空非公海池等级的客户表格 + const nonSeaPoolLevels = ['important', 'regular', 'low-value', 'logistics', 'unclassified']; + nonSeaPoolLevels.forEach(level => { const tbody = document.getElementById(`${level}-customers`); if (tbody) { tbody.innerHTML = ''; } }); - // 重新渲染所有客户 + // 重新渲染非公海池客户 customerList.forEach(customer => { - updateCustomerTable(customer); + if (!customer.level.includes('sea-pools')) { + updateCustomerTable(customer); + } }); // 检查每个等级的tbody,如果没有数据且当前不是全部客户视图,则添加"暂无数据"提示 const currentLevel = document.querySelector('.level-tab.active').dataset.level; - allLevels.forEach(level => { + nonSeaPoolLevels.forEach(level => { const tbody = document.getElementById(`${level}-customers`); if (tbody && tbody.children.length === 0) { if (currentLevel === 'all') { diff --git a/src/main/resources/static/supply.html b/src/main/resources/static/supply.html index a8e71ab..fe9c785 100644 --- a/src/main/resources/static/supply.html +++ b/src/main/resources/static/supply.html @@ -4577,14 +4577,12 @@ // 非全部客户视图,如果没有数据则显示"暂无数据"提示 if (tbody.children.length === 0) { const noDataText = { - 'important': '暂无重要客户数据', - 'regular': '暂无普通客户数据', - 'low-value': '暂无低价值客户数据', - 'logistics': '暂无物流客户数据', - 'unclassified': '暂无未分级客户数据', - 'department-sea-pools': '暂无部门公海客户数据', - 'organization-sea-pools': '暂无组织公海客户数据' - }[level] || '暂无客户数据'; + 'important': '暂无重要客户数据', + 'regular': '暂无普通客户数据', + 'low-value': '暂无低价值客户数据', + 'logistics': '暂无物流客户数据', + 'unclassified': '暂无未分级客户数据' + }[level] || '暂无客户数据'; tbody.innerHTML = `${noDataText}`; } } else { @@ -5637,15 +5635,11 @@ renderOrganizationSeaPool(organizationSeaPoolData); } - // 同时更新全局客户列表和相关表格 - if (typeof updateRecentCustomers === 'function') { - updateRecentCustomers(); - } - if (typeof updateStatsCards === 'function') { - updateStatsCards(); - } - if (typeof updateCustomerTable === 'function') { - updateCustomerTable(customer); + // 重新获取客户数据,确保认领的客户出现在我的客户列表中 + if (stompClient && stompClient.connected) { + const loginInfo = getLoginInfo(); + // 重新请求获取最新的客户数据 + stompClient.send("/app/customer/role/seller", {}, JSON.stringify(loginInfo)); } } else { alert('客户认领失败: ' + (data.message || '未知错误')); @@ -6915,6 +6909,41 @@ } }); + // 订阅客户更新通知 + stompClient.subscribe('/topic/notifications', function(response) { + try { + const notification = JSON.parse(response.body); + console.log('收到客户更新通知:', notification); + + // 更新通知显示 + updateNotificationDisplay(); + + // 重新获取客户数据 + const loginInfo = getLoginInfo(); + stompClient.send("/app/customer/role/seller", {}, JSON.stringify(loginInfo)); + + // 获取部门和组织公海池数据 + stompClient.send("/app/customer/departmentSeaPool/seller", {}, JSON.stringify(loginInfo)); + stompClient.send("/app/customer/organizationSeaPool/seller", {}, JSON.stringify(loginInfo)); + } catch (error) { + console.error('处理通知数据时出错:', error); + } + }); + + // 订阅特定客户更新通知 + stompClient.subscribe('/topic/customers/*', function(response) { + try { + const notification = JSON.parse(response.body); + console.log('收到特定客户更新通知:', notification); + + // 重新获取客户数据 + const loginInfo = getLoginInfo(); + stompClient.send("/app/customer/role/seller", {}, JSON.stringify(loginInfo)); + } catch (error) { + console.error('处理特定客户通知时出错:', error); + } + }); + // 订阅seller角色数据(supply.html页面显示seller类型客户) stompClient.subscribe('/topic/role/seller', function(response) { try { @@ -6930,8 +6959,17 @@ console.log('过滤后的客户数据:', filteredCustomers); - // 更新客户数据 - 以客户ID为键,过滤掉Colleague类型的客户 + // 更新客户数据 - 以客户ID为键,过滤掉Colleague类型的客户,保留公海池客户数据 const newCustomerData = {}; + + // 先保留现有公海池客户数据 + Object.values(customerData).forEach(customer => { + if (customer.level && customer.level.includes('sea-pools')) { + newCustomerData[customer.id] = customer; + } + }); + + // 再添加当前用户的客户数据 filteredCustomers.forEach(customer => { // 过滤掉Colleague类型的客户 if (customer.type === 'Colleague') { @@ -6956,27 +6994,29 @@ updateStatsCards(); updateRecentCustomers(); - // 收集所有唯一的客户等级 + // 收集所有唯一的客户等级(不包括公海池) const customerList = Object.values(customerData); - const levels = [...new Set(customerList.map(customer => customer.level || 'unclassified'))]; + const levels = [...new Set(customerList.map(customer => customer.level || 'unclassified'))].filter(level => !level.includes('sea-pools')); - // 清空所有等级的客户表格 - const allLevels = ['important', 'regular', 'low-value', 'logistics', 'unclassified', 'department-sea-pools', 'organization-sea-pools']; - allLevels.forEach(level => { + // 只清空非公海池等级的客户表格 + const nonSeaPoolLevels = ['important', 'regular', 'low-value', 'logistics', 'unclassified']; + nonSeaPoolLevels.forEach(level => { const tbody = document.getElementById(`${level}-customers`); if (tbody) { tbody.innerHTML = ''; } }); - // 重新渲染所有客户 + // 重新渲染非公海池客户 customerList.forEach(customer => { - updateCustomerTable(customer); + if (!customer.level.includes('sea-pools')) { + updateCustomerTable(customer); + } }); // 检查每个等级的tbody,如果没有数据且当前不是全部客户视图,则添加"暂无数据"提示 const currentLevel = document.querySelector('.level-tab.active').dataset.level; - allLevels.forEach(level => { + nonSeaPoolLevels.forEach(level => { const tbody = document.getElementById(`${level}-customers`); if (tbody && tbody.children.length === 0) { if (currentLevel === 'all') {