|
@@ -0,0 +1,727 @@
|
|
|
|
|
+package com.zxyj.service;
|
|
|
|
|
+
|
|
|
|
|
+import com.zxyj.service.api.DataMigrationServiceInterface;
|
|
|
|
|
+
|
|
|
|
|
+import com.zxyj.entity.*;
|
|
|
|
|
+import com.zxyj.mapper.*;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class DataMigrationService {
|
|
|
|
|
+
|
|
|
|
|
+ @Resource(name = "sfmsJdbcTemplate")
|
|
|
|
|
+ private JdbcTemplate sfms;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private FamilyMapper familyMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private UserMapper userMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private ChildMapper childMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TeacherMapper teacherMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private GuideFamilyMapper guideFamilyMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private PackageOrderMapper packageOrderMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private DanAssessmentResultMapper danAssessmentResultMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private GrowthRecordMapper growthRecordMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private GrowthPlanService growthPlanService;
|
|
|
|
|
+
|
|
|
|
|
+ // ============ 统计结果 ============
|
|
|
|
|
+ private int migratedFamilies;
|
|
|
|
|
+ private int migratedParents;
|
|
|
|
|
+ private int migratedChildren;
|
|
|
|
|
+ private int migratedTeachers;
|
|
|
|
|
+ private int migratedOrders;
|
|
|
|
|
+ private int migratedResults;
|
|
|
|
|
+ private int migratedRecords;
|
|
|
|
|
+ private int migratedPlans;
|
|
|
|
|
+ private final List<String> errors = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ // ============ ID 映射缓存 ============
|
|
|
|
|
+ private final Map<String, Long> sfmsPhoneToFamilyId = new HashMap<>();
|
|
|
|
|
+ private final Map<Long, Long> sfmsClientIdToChildUserId = new HashMap<>();
|
|
|
|
|
+ private final Map<Long, Long> sfmsClientIdToFamilyId = new HashMap<>();
|
|
|
|
|
+ private final Map<String, Long> sfmsOpenIdToTeacherUserId = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ public Map<String, Object> runMigration() {
|
|
|
|
|
+ long start = System.currentTimeMillis();
|
|
|
|
|
+ resetCounters();
|
|
|
|
|
+ log.info("========== 开始 SFMS 数据迁移 ==========");
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 按依赖顺序执行
|
|
|
|
|
+ migrateFamilies();
|
|
|
|
|
+ migrateParentUsers();
|
|
|
|
|
+ migrateChildUsersAndChildren();
|
|
|
|
|
+ migrateTeachers();
|
|
|
|
|
+ migrateGuideFamilies();
|
|
|
|
|
+ migratePackageOrders();
|
|
|
|
|
+ migrateAssessmentResults();
|
|
|
|
|
+ migrateGrowthRecords();
|
|
|
|
|
+ migrateGrowthPlans();
|
|
|
|
|
+
|
|
|
|
|
+ log.info("========== SFMS 数据迁移完成,耗时: {}ms ==========", System.currentTimeMillis() - start);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移过程中出错", e);
|
|
|
|
|
+ errors.add("全局错误: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return buildResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void resetCounters() {
|
|
|
|
|
+ migratedFamilies = 0;
|
|
|
|
|
+ migratedParents = 0;
|
|
|
|
|
+ migratedChildren = 0;
|
|
|
|
|
+ migratedTeachers = 0;
|
|
|
|
|
+ migratedOrders = 0;
|
|
|
|
|
+ migratedResults = 0;
|
|
|
|
|
+ migratedRecords = 0;
|
|
|
|
|
+ migratedPlans = 0;
|
|
|
|
|
+ errors.clear();
|
|
|
|
|
+ sfmsPhoneToFamilyId.clear();
|
|
|
|
|
+ sfmsClientIdToChildUserId.clear();
|
|
|
|
|
+ sfmsClientIdToFamilyId.clear();
|
|
|
|
|
+ sfmsOpenIdToTeacherUserId.clear();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 1. 家庭迁移:从 crmclientinfo 中提取去重家长手机号,创建家庭
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ private void migrateFamilies() {
|
|
|
|
|
+ log.info("--- 1/9: 迁移家庭 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT DISTINCT ci.cell_phone, cip.value AS urgent_name " +
|
|
|
|
|
+ "FROM crmclientinfo ci " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'urgentName') cip ON cip.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "WHERE ci.cell_phone IS NOT NULL AND ci.cell_phone != ''"
|
|
|
|
|
+ );
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ String phone = (String) row.get("cell_phone");
|
|
|
|
|
+ if (phone == null || sfmsPhoneToFamilyId.containsKey(phone)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ String urgentName = (String) row.get("urgent_name");
|
|
|
|
|
+ String familyName = urgentName != null && !urgentName.isEmpty() ? urgentName + "的家庭" : phone + "的家庭";
|
|
|
|
|
+
|
|
|
|
|
+ Family family = new Family();
|
|
|
|
|
+ family.setName(familyName);
|
|
|
|
|
+ family.setInviteCode(generateInviteCode());
|
|
|
|
|
+ try {
|
|
|
|
|
+ familyMapper.insert(family);
|
|
|
|
|
+ sfmsPhoneToFamilyId.put(phone, family.getId());
|
|
|
|
|
+ migratedFamilies++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("创建家庭失败 phone={}: {}", phone, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info(" 创建 {} 个家庭", migratedFamilies);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移家庭出错", e);
|
|
|
|
|
+ errors.add("迁移家庭出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 2. 家长用户迁移:从 crmclientinfo 创建家长 accounts
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ private void migrateParentUsers() {
|
|
|
|
|
+ log.info("--- 2/9: 迁移家长用户 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 收集去重家长(电话+家长名称分组)
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT ci.cell_phone, ci.openid, cip.value AS urgent_name " +
|
|
|
|
|
+ "FROM crmclientinfo ci " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'urgentName') cip ON cip.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "WHERE ci.cell_phone IS NOT NULL AND ci.cell_phone != '' AND ci.openid IS NOT NULL AND ci.openid != ''"
|
|
|
|
|
+ );
|
|
|
|
|
+ Set<String> dedup = new HashSet<>();
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ String phone = (String) row.get("cell_phone");
|
|
|
|
|
+ String openid = (String) row.get("openid");
|
|
|
|
|
+ String parentName = (String) row.get("urgent_name");
|
|
|
|
|
+ String key = phone + "|" + openid;
|
|
|
|
|
+ if (dedup.contains(key)) continue;
|
|
|
|
|
+ dedup.add(key);
|
|
|
|
|
+
|
|
|
|
|
+ Long familyId = sfmsPhoneToFamilyId.get(phone);
|
|
|
|
|
+ if (familyId == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ User user = new User();
|
|
|
|
|
+ user.setOpenid(openid + "_parent");
|
|
|
|
|
+ user.setFamilyId(familyId);
|
|
|
|
|
+ user.setRole("parent");
|
|
|
|
|
+ user.setRoles("[\"parent\"]");
|
|
|
|
|
+ user.setNickname(parentName != null ? parentName : phone);
|
|
|
|
|
+ user.setPhone(phone);
|
|
|
|
|
+ user.setRealName(parentName);
|
|
|
|
|
+ try {
|
|
|
|
|
+ userMapper.insert(user);
|
|
|
|
|
+ count++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("创建家长用户失败 phone={}: {}", phone, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ migratedParents = count;
|
|
|
|
|
+ log.info(" 创建 {} 个家长用户", migratedParents);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移家长用户出错", e);
|
|
|
|
|
+ errors.add("迁移家长用户出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 3. 孩子用户 + children 表迁移
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ @Transactional
|
|
|
|
|
+ public void migrateChildUsersAndChildren() {
|
|
|
|
|
+ log.info("--- 3/9: 迁移孩子用户 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT ci.crm_client_info_id, ci.name, ci.cell_phone, ci.openid, ci.sex, " +
|
|
|
|
|
+ "ci.birth_day, ci.social_no, ci.province_show_label, ci.city_show_label, " +
|
|
|
|
|
+ "ci.district_show_label, ci.company_name_show_label " +
|
|
|
|
|
+ "FROM crmclientinfo ci " +
|
|
|
|
|
+ "WHERE ci.name IS NOT NULL AND ci.name != ''"
|
|
|
|
|
+ );
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ long sfmsClientId = ((Number) row.get("crm_client_info_id")).longValue();
|
|
|
|
|
+ String phone = (String) row.get("cell_phone");
|
|
|
|
|
+ String openid = (String) row.get("openid");
|
|
|
|
|
+
|
|
|
|
|
+ // 确定 family
|
|
|
|
|
+ Long familyId = null;
|
|
|
|
|
+ if (phone != null && !phone.isEmpty()) {
|
|
|
|
|
+ familyId = sfmsPhoneToFamilyId.get(phone);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (familyId == null) {
|
|
|
|
|
+ // 如果没有匹配到家庭,创建一个独立家庭
|
|
|
|
|
+ Family family = new Family();
|
|
|
|
|
+ family.setName(((String) row.get("name")) + "的家庭");
|
|
|
|
|
+ family.setInviteCode(generateInviteCode());
|
|
|
|
|
+ familyMapper.insert(family);
|
|
|
|
|
+ familyId = family.getId();
|
|
|
|
|
+ migratedFamilies++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 孩子 openid
|
|
|
|
|
+ String childOpenid = (openid != null && !openid.isEmpty()) ? openid + "_child_" + sfmsClientId : "migrated_child_" + sfmsClientId;
|
|
|
|
|
+
|
|
|
|
|
+ // 创建孩子用户
|
|
|
|
|
+ User childUser = new User();
|
|
|
|
|
+ childUser.setOpenid(childOpenid);
|
|
|
|
|
+ childUser.setFamilyId(familyId);
|
|
|
|
|
+ childUser.setRole("child");
|
|
|
|
|
+ childUser.setRoles("[\"child\"]");
|
|
|
|
|
+ childUser.setNickname((String) row.get("name"));
|
|
|
|
|
+ childUser.setPhone(phone);
|
|
|
|
|
+ childUser.setIdCard((String) row.get("social_no"));
|
|
|
|
|
+ if (row.get("birth_day") != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ childUser.setBirthday(sdf.parse((String) row.get("birth_day")));
|
|
|
|
|
+ } catch (Exception ignored) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ childUser.setGender("1".equals(String.valueOf(row.get("sex"))) ? "female" : "male");
|
|
|
|
|
+ userMapper.insert(childUser);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建 children 记录
|
|
|
|
|
+ Child child = new Child();
|
|
|
|
|
+ child.setUserId(childUser.getId());
|
|
|
|
|
+ child.setFamilyId(familyId);
|
|
|
|
|
+ child.setNickname((String) row.get("name"));
|
|
|
|
|
+ child.setAge(calcAge((String) row.get("birth_day")));
|
|
|
|
|
+ child.setGender("1".equals(String.valueOf(row.get("sex"))) ? "female" : "male");
|
|
|
|
|
+ child.setPenaltyEnabled(1);
|
|
|
|
|
+ child.setTheme("default");
|
|
|
|
|
+ child.setTotalPoints(0);
|
|
|
|
|
+ child.setStreakDays(0);
|
|
|
|
|
+ child.setFocusMaxDaily(3);
|
|
|
|
|
+ child.setFocusRemaining(3);
|
|
|
|
|
+ childMapper.insert(child);
|
|
|
|
|
+
|
|
|
|
|
+ sfmsClientIdToChildUserId.put(sfmsClientId, childUser.getId());
|
|
|
|
|
+ sfmsClientIdToFamilyId.put(sfmsClientId, familyId);
|
|
|
|
|
+ count++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("迁移孩子失败 clientId={}: {}", row.get("crm_client_info_id"), e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ migratedChildren = count;
|
|
|
|
|
+ log.info(" 迁移 {} 个孩子", migratedChildren);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移孩子出错", e);
|
|
|
|
|
+ errors.add("迁移孩子出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 4. 规划师迁移
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ private void migrateTeachers() {
|
|
|
|
|
+ log.info("--- 4/9: 迁移规划师 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ // sfms 中规划师在 sysuser 表中
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT su.user_id, su._open_id, su.user_name, su.user_pic, " +
|
|
|
|
|
+ "sd.dept_bm, sd.dept_type, sd.dept_type_show_label " +
|
|
|
|
|
+ "FROM sysuser su " +
|
|
|
|
|
+ "JOIN sysdept sd ON sd.dept_id = su.user_dept_id " +
|
|
|
|
|
+ "WHERE sd.dept_type IN ('sales', 'assistant', 'angel') AND su._open_id IS NOT NULL"
|
|
|
|
|
+ );
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String openid = (String) row.get("_open_id");
|
|
|
|
|
+ if (openid == null || openid.isEmpty()) continue;
|
|
|
|
|
+ String openidKey = openid + "_teacher";
|
|
|
|
|
+ if (sfmsOpenIdToTeacherUserId.containsKey(openidKey)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ User user = new User();
|
|
|
|
|
+ user.setOpenid(openidKey);
|
|
|
|
|
+ user.setFamilyId(0L);
|
|
|
|
|
+ user.setRole("teacher");
|
|
|
|
|
+ user.setRoles("[\"teacher\"]");
|
|
|
|
|
+ user.setNickname((String) row.get("user_name"));
|
|
|
|
|
+ user.setTeacherNo((String) row.get("dept_bm"));
|
|
|
|
|
+ user.setTeacherStatus("approved");
|
|
|
|
|
+ userMapper.insert(user);
|
|
|
|
|
+
|
|
|
|
|
+ Teacher teacher = new Teacher();
|
|
|
|
|
+ teacher.setUserId(user.getId());
|
|
|
|
|
+ teacher.setTeacherNo((String) row.get("dept_bm"));
|
|
|
|
|
+ teacher.setStatus("active");
|
|
|
|
|
+ teacher.setVerificationStatus("approved");
|
|
|
|
|
+ teacher.setVerifiedAt(new Date());
|
|
|
|
|
+ teacherMapper.insert(teacher);
|
|
|
|
|
+
|
|
|
|
|
+ sfmsOpenIdToTeacherUserId.put(openidKey, user.getId());
|
|
|
|
|
+ count++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("迁移规划师失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ migratedTeachers = count;
|
|
|
|
|
+ log.info(" 迁移 {} 个规划师", migratedTeachers);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移规划师出错", e);
|
|
|
|
|
+ errors.add("迁移规划师出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 5. 规划师-家庭关系迁移
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ private void migrateGuideFamilies() {
|
|
|
|
|
+ log.info("--- 5/9: 迁移规划师-家庭关系 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 通过订单中的 responser(规划师 user_id)推断关系
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT DISTINCT go.responser, go.client_id " +
|
|
|
|
|
+ "FROM crmgiftorder go " +
|
|
|
|
|
+ "WHERE go.responser IS NOT NULL AND go.client_id IS NOT NULL"
|
|
|
|
|
+ );
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ long sfmsTeacherUserId = ((Number) row.get("responser")).longValue();
|
|
|
|
|
+ long sfmsClientId = ((Number) row.get("client_id")).longValue();
|
|
|
|
|
+
|
|
|
|
|
+ Long teacherUserId = findTeacherUserId(sfmsTeacherUserId);
|
|
|
|
|
+ Long familyId = sfmsClientIdToFamilyId.get(sfmsClientId);
|
|
|
|
|
+ if (teacherUserId == null || familyId == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ GuideFamily gf = new GuideFamily();
|
|
|
|
|
+ gf.setGuideId(teacherUserId);
|
|
|
|
|
+ gf.setFamilyId(familyId);
|
|
|
|
|
+ gf.setServiceType("consulting");
|
|
|
|
|
+ gf.setStatus("binding");
|
|
|
|
|
+ gf.setBoundAt(new Date());
|
|
|
|
|
+ try {
|
|
|
|
|
+ guideFamilyMapper.insert(gf);
|
|
|
|
|
+ count++;
|
|
|
|
|
+ } catch (Exception ignored) {
|
|
|
|
|
+ // 唯一键冲突,跳过
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("迁移规划师-家庭关系失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info(" 迁移 {} 条规划师-家庭关系", count);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移规划师-家庭关系出错", e);
|
|
|
|
|
+ errors.add("迁移规划师-家庭关系出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 6. 订单迁移
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ private void migratePackageOrders() {
|
|
|
|
|
+ log.info("--- 6/9: 迁移订单 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT go.crm_gift_order_id, go.order_no, go.order_status, go.fee_total, " +
|
|
|
|
|
+ "go.coupon, go.responser, go.client_id, go.create_time, " +
|
|
|
|
|
+ "go.grade, go.school_show_label " +
|
|
|
|
|
+ "FROM crmgiftorder go " +
|
|
|
|
|
+ "WHERE go.client_id IS NOT NULL"
|
|
|
|
|
+ );
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ long sfmsClientId = ((Number) row.get("client_id")).longValue();
|
|
|
|
|
+ Long userId = sfmsClientIdToChildUserId.get(sfmsClientId);
|
|
|
|
|
+ Long familyId = sfmsClientIdToFamilyId.get(sfmsClientId);
|
|
|
|
|
+ if (userId == null || familyId == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ String status = mapOrderStatus((String) row.get("order_status"));
|
|
|
|
|
+ BigDecimal fee = row.get("fee_total") != null
|
|
|
|
|
+ ? BigDecimal.valueOf(((Number) row.get("fee_total")).longValue()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP)
|
|
|
|
|
+ : BigDecimal.ZERO;
|
|
|
|
|
+
|
|
|
|
|
+ PackageOrder order = new PackageOrder();
|
|
|
|
|
+ order.setOrderNo((String) row.get("order_no"));
|
|
|
|
|
+ order.setUserId(userId);
|
|
|
|
|
+ order.setFamilyId(familyId);
|
|
|
|
|
+ order.setPackageName("测评套餐");
|
|
|
|
|
+ order.setPrice(fee);
|
|
|
|
|
+ order.setStatus(status);
|
|
|
|
|
+ if (row.get("create_time") != null) {
|
|
|
|
|
+ order.setCreatedAt(parseDateTime((String) row.get("create_time")));
|
|
|
|
|
+ }
|
|
|
|
|
+ packageOrderMapper.insert(order);
|
|
|
|
|
+ count++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("迁移订单失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ migratedOrders = count;
|
|
|
|
|
+ log.info(" 迁移 {} 个订单", migratedOrders);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移订单出错", e);
|
|
|
|
|
+ errors.add("迁移订单出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 7. 测评结果迁移
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ private void migrateAssessmentResults() {
|
|
|
|
|
+ log.info("--- 7/9: 迁移测评结果 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT cr.crm_contact_rec_id, cr.client_id, cr.order_id, " +
|
|
|
|
|
+ "cr.meet_time, cr.comm_cont, cr.description, cr.responser, " +
|
|
|
|
|
+ "cr.rec_pic, cr.rec_video, cr.rec_audio " +
|
|
|
|
|
+ "FROM crmcontactrec cr " +
|
|
|
|
|
+ "WHERE cr.client_id IS NOT NULL AND cr.comm_cont IS NOT NULL"
|
|
|
|
|
+ );
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ long sfmsClientId = ((Number) row.get("client_id")).longValue();
|
|
|
|
|
+ Long childUserId = sfmsClientIdToChildUserId.get(sfmsClientId);
|
|
|
|
|
+ if (childUserId == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 找 child_id
|
|
|
|
|
+ Child child = childMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Child>()
|
|
|
|
|
+ .eq(Child::getUserId, childUserId)
|
|
|
|
|
+ .last("LIMIT 1")
|
|
|
|
|
+ );
|
|
|
|
|
+ if (child == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 找 teacher_id
|
|
|
|
|
+ Long teacherUserId = null;
|
|
|
|
|
+ if (row.get("responser") != null) {
|
|
|
|
|
+ teacherUserId = findTeacherUserId(((Number) row.get("responser")).longValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ DanAssessmentResult result = new DanAssessmentResult();
|
|
|
|
|
+ result.setChildId(child.getId());
|
|
|
|
|
+ result.setTeacherId(teacherUserId != null ? teacherUserId : 0L);
|
|
|
|
|
+ result.setDanLevel("C"); // sfms 没有明确 DAN 等级,默认 C
|
|
|
|
|
+ result.setAnalysisReport((String) row.get("comm_cont"));
|
|
|
|
|
+ result.setProcessDesc((String) row.get("description"));
|
|
|
|
|
+ result.setStatus("completed");
|
|
|
|
|
+ if (row.get("meet_time") != null) {
|
|
|
|
|
+ result.setAssessmentDate(parseDate((String) row.get("meet_time")));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 媒体字段
|
|
|
|
|
+ result.setPics((String) row.get("rec_pic"));
|
|
|
|
|
+ result.setVideos((String) row.get("rec_video"));
|
|
|
|
|
+ result.setAudio((String) row.get("rec_audio"));
|
|
|
|
|
+
|
|
|
|
|
+ danAssessmentResultMapper.insert(result);
|
|
|
|
|
+ count++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("迁移测评结果失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ migratedResults = count;
|
|
|
|
|
+ log.info(" 迁移 {} 条测评结果", migratedResults);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移测评结果出错", e);
|
|
|
|
|
+ errors.add("迁移测评结果出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 8. 成长档案迁移(带快照)
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ private void migrateGrowthRecords() {
|
|
|
|
|
+ log.info("--- 8/9: 迁移成长档案 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 从 crmclientinfo 中的孩子快照字段 + 最近的测评结果创建成长档案
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT ci.crm_client_info_id, ci.name, ci.cell_phone, ci.birth_day, " +
|
|
|
|
|
+ "ci.sex, ci.company_name_show_label, " +
|
|
|
|
|
+ "cip_grade.value AS grade, cip_class.value AS grade_class, " +
|
|
|
|
|
+ "cip_height.value AS height, cip_weight.value AS weight, " +
|
|
|
|
|
+ "cip_nation.value AS nation, cip_degree.value AS parent_degree, " +
|
|
|
|
|
+ "cip_family.value AS family_status, cip_single.value AS single_child, " +
|
|
|
|
|
+ "cip_score.value AS school_score, cip_address.value AS address, " +
|
|
|
|
|
+ "ci.province_show_label, ci.city_show_label, ci.district_show_label " +
|
|
|
|
|
+ "FROM crmclientinfo ci " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'grade') cip_grade ON cip_grade.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'gradeClass') cip_class ON cip_class.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'height') cip_height ON cip_height.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'weight') cip_weight ON cip_weight.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'nation') cip_nation ON cip_nation.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'parentDegree') cip_degree ON cip_degree.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'familyStatus') cip_family ON cip_family.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'singleChild') cip_single ON cip_single.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'schoolScore') cip_score ON cip_score.client_id = ci.crm_client_info_id " +
|
|
|
|
|
+ "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'address') cip_address ON cip_address.client_id = ci.crm_client_info_id "
|
|
|
|
|
+ );
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ long sfmsClientId = ((Number) row.get("crm_client_info_id")).longValue();
|
|
|
|
|
+ Long childUserId = sfmsClientIdToChildUserId.get(sfmsClientId);
|
|
|
|
|
+ Long familyId = sfmsClientIdToFamilyId.get(sfmsClientId);
|
|
|
|
|
+ if (childUserId == null || familyId == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ Child child = childMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Child>()
|
|
|
|
|
+ .eq(Child::getUserId, childUserId)
|
|
|
|
|
+ .last("LIMIT 1")
|
|
|
|
|
+ );
|
|
|
|
|
+ if (child == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 找家长ID(family 中的第一个 parent)
|
|
|
|
|
+ User parent = userMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User>()
|
|
|
|
|
+ .eq(User::getFamilyId, familyId)
|
|
|
|
|
+ .eq(User::getRole, "parent")
|
|
|
|
|
+ .last("LIMIT 1")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ GrowthRecord record = new GrowthRecord();
|
|
|
|
|
+ record.setChildId(child.getId());
|
|
|
|
|
+ record.setParentId(parent != null ? parent.getId() : 0L);
|
|
|
|
|
+ record.setRecordTitle("历史成长档案 - " + (String) row.get("name"));
|
|
|
|
|
+ record.setChildAge(calcAge((String) row.get("birth_day")));
|
|
|
|
|
+ record.setHeight((String) row.get("height"));
|
|
|
|
|
+ record.setWeight((String) row.get("weight"));
|
|
|
|
|
+ record.setDanLevel("C");
|
|
|
|
|
+ record.setStatus("completed");
|
|
|
|
|
+ record.setExternalSync(true);
|
|
|
|
|
+ record.setExternalSource("sfms");
|
|
|
|
|
+
|
|
|
|
|
+ // 构建快照摘要
|
|
|
|
|
+ StringBuilder summary = new StringBuilder();
|
|
|
|
|
+ if (row.get("grade") != null) summary.append("年级:").append(row.get("grade")).append("; ");
|
|
|
|
|
+ if (row.get("grade_class") != null) summary.append("班级:").append(row.get("grade_class")).append("; ");
|
|
|
|
|
+ if (row.get("school_score") != null) summary.append("成绩:").append(row.get("school_score")).append("; ");
|
|
|
|
|
+ if (row.get("family_status") != null) summary.append("家庭:").append(row.get("family_status")).append("; ");
|
|
|
|
|
+ if (row.get("nation") != null) summary.append("民族:").append(row.get("nation")).append("; ");
|
|
|
|
|
+ if (row.get("parent_degree") != null) summary.append("父母学历:").append(row.get("parent_degree")).append("; ");
|
|
|
|
|
+ String summaryStr = summary.toString();
|
|
|
|
|
+ if (!summaryStr.isEmpty()) {
|
|
|
|
|
+ record.setAssessmentSummary(summaryStr);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ growthRecordMapper.insert(record);
|
|
|
|
|
+ count++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("迁移成长档案失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ migratedRecords = count;
|
|
|
|
|
+ log.info(" 迁移 {} 条成长档案", migratedRecords);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移成长档案出错", e);
|
|
|
|
|
+ errors.add("迁移成长档案出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 9. 成长方案迁移
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ private void migrateGrowthPlans() {
|
|
|
|
|
+ log.info("--- 9/9: 迁移成长方案 ---");
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Map<String, Object>> rows = sfms.queryForList(
|
|
|
|
|
+ "SELECT gp.crm_client_gp_id, gp.client_id, gp.order_id, " +
|
|
|
|
|
+ "gp.plan_name, gp.plan_desc, gp.plan_status " +
|
|
|
|
|
+ "FROM crmclientgp gp " +
|
|
|
|
|
+ "WHERE gp.client_id IS NOT NULL"
|
|
|
|
|
+ );
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ long sfmsClientId = ((Number) row.get("client_id")).longValue();
|
|
|
|
|
+ Long childUserId = sfmsClientIdToChildUserId.get(sfmsClientId);
|
|
|
|
|
+ if (childUserId == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ Child child = childMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Child>()
|
|
|
|
|
+ .eq(Child::getUserId, childUserId)
|
|
|
|
|
+ .last("LIMIT 1")
|
|
|
|
|
+ );
|
|
|
|
|
+ if (child == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ GrowthPlan plan = new GrowthPlan();
|
|
|
|
|
+ plan.setChildId(child.getId());
|
|
|
|
|
+ plan.setPlanTitle((String) row.get("plan_name"));
|
|
|
|
|
+ plan.setPlanContent((String) row.get("plan_desc"));
|
|
|
|
|
+ plan.setStatus("active".equals(row.get("plan_status")) ? "active" : "draft");
|
|
|
|
|
+ growthPlanService.createPlan(plan);
|
|
|
|
|
+ count++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("迁移成长方案失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ migratedPlans = count;
|
|
|
|
|
+ log.info(" 迁移 {} 条成长方案", migratedPlans);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("迁移成长方案出错", e);
|
|
|
|
|
+ errors.add("迁移成长方案出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+ // 辅助方法
|
|
|
|
|
+ // ================================================================
|
|
|
|
|
+
|
|
|
|
|
+ private Long findTeacherUserId(long sfmsTeacherUserId) {
|
|
|
|
|
+ // 从 sfms sysuser 的反向查找
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, Object> su = sfms.queryForMap(
|
|
|
|
|
+ "SELECT _open_id FROM sysuser WHERE user_id = ?", sfmsTeacherUserId
|
|
|
|
|
+ );
|
|
|
|
|
+ String openid = (String) su.get("_open_id");
|
|
|
|
|
+ if (openid != null) {
|
|
|
|
|
+ String key = openid + "_teacher";
|
|
|
|
|
+ Long uid = sfmsOpenIdToTeacherUserId.get(key);
|
|
|
|
|
+ if (uid != null) return uid;
|
|
|
|
|
+ User u = userMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User>()
|
|
|
|
|
+ .eq(User::getOpenid, key)
|
|
|
|
|
+ .last("LIMIT 1")
|
|
|
|
|
+ );
|
|
|
|
|
+ if (u != null) {
|
|
|
|
|
+ sfmsOpenIdToTeacherUserId.put(key, u.getId());
|
|
|
|
|
+ return u.getId();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("查找规划师用户ID失败 sfmsUserId={}", sfmsTeacherUserId);
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int calcAge(String birthdayStr) {
|
|
|
|
|
+ if (birthdayStr == null || birthdayStr.isEmpty()) return 0;
|
|
|
|
|
+ try {
|
|
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
+ Date birth = sdf.parse(birthdayStr);
|
|
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
|
|
+ int thisYear = cal.get(Calendar.YEAR);
|
|
|
|
|
+ cal.setTime(birth);
|
|
|
|
|
+ int birthYear = cal.get(Calendar.YEAR);
|
|
|
|
|
+ return thisYear - birthYear;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String mapOrderStatus(String sfmsStatus) {
|
|
|
|
|
+ if (sfmsStatus == null) return "pending";
|
|
|
|
|
+ switch (sfmsStatus) {
|
|
|
|
|
+ case "accept": return "pending";
|
|
|
|
|
+ case "paid":
|
|
|
|
|
+ case "pick": return "paid";
|
|
|
|
|
+ case "cancel": return "cancelled";
|
|
|
|
|
+ default: return "pending";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Date parseDate(String str) {
|
|
|
|
|
+ if (str == null || str.isEmpty()) return null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ String d = str.length() > 10 ? str.substring(0, 10) : str;
|
|
|
|
|
+ return new SimpleDateFormat("yyyy-MM-dd").parse(d);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Date parseDateTime(String str) {
|
|
|
|
|
+ if (str == null || str.isEmpty()) return null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return parseDate(str);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String generateInviteCode() {
|
|
|
|
|
+ String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
|
|
|
+ StringBuilder code = new StringBuilder();
|
|
|
|
|
+ Random rand = new Random();
|
|
|
|
|
+ for (int i = 0; i < 8; i++) {
|
|
|
|
|
+ code.append(chars.charAt(rand.nextInt(chars.length())));
|
|
|
|
|
+ }
|
|
|
|
|
+ return code.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> buildResult() {
|
|
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
|
|
+ result.put("success", errors.isEmpty());
|
|
|
|
|
+ result.put("migratedFamilies", migratedFamilies);
|
|
|
|
|
+ result.put("migratedParents", migratedParents);
|
|
|
|
|
+ result.put("migratedChildren", migratedChildren);
|
|
|
|
|
+ result.put("migratedTeachers", migratedTeachers);
|
|
|
|
|
+ result.put("migratedOrders", migratedOrders);
|
|
|
|
|
+ result.put("migratedResults", migratedResults);
|
|
|
|
|
+ result.put("migratedRecords", migratedRecords);
|
|
|
|
|
+ result.put("migratedPlans", migratedPlans);
|
|
|
|
|
+ result.put("total", migratedFamilies + migratedParents + migratedChildren + migratedTeachers
|
|
|
|
|
+ + migratedOrders + migratedResults + migratedRecords + migratedPlans);
|
|
|
|
|
+ if (!errors.isEmpty()) {
|
|
|
|
|
+ result.put("errors", errors);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|