lifuquan 1 年之前
父節點
當前提交
9ab71c482f
共有 23 個文件被更改,包括 952 次插入188 次删除
  1. 87 0
      doc/开发环境/2023年适配/2024年适配.md
  2. 20 1
      doc/需求文档/2024年适配/关于投诉工单日报表2024年适配的需求.md
  3. 0 26
      src/main/java/com/nokia/tsl_data/config/TaskSchedulingConfig.java
  4. 17 1
      src/main/java/com/nokia/tsl_data/config/TslDataConfig.java
  5. 28 0
      src/main/java/com/nokia/tsl_data/controller/DataWarehouseController.java
  6. 42 18
      src/main/java/com/nokia/tsl_data/dao/HighQualityCountMapper.java
  7. 6 0
      src/main/java/com/nokia/tsl_data/dao/ManagementDetailMapper.java
  8. 53 22
      src/main/java/com/nokia/tsl_data/dao/TslDataDao.java
  9. 0 9
      src/main/java/com/nokia/tsl_data/dao/WorkFlowBasicDataRepository.java
  10. 56 35
      src/main/java/com/nokia/tsl_data/dao/WorkFlowDao.java
  11. 15 26
      src/main/java/com/nokia/tsl_data/entity/WorkFlowBasicData.java
  12. 21 0
      src/main/java/com/nokia/tsl_data/entity/pojo/XSSFWorkbookWrapper.java
  13. 12 0
      src/main/java/com/nokia/tsl_data/exception/LackOfDataRuntimeException.java
  14. 12 0
      src/main/java/com/nokia/tsl_data/properties/TslDataProperties.java
  15. 7 0
      src/main/java/com/nokia/tsl_data/service/DataWarehouseService.java
  16. 11 0
      src/main/java/com/nokia/tsl_data/service/ManagementDetailService.java
  17. 2 4
      src/main/java/com/nokia/tsl_data/service/ReportServiceV1.java
  18. 441 0
      src/main/java/com/nokia/tsl_data/service/ReportServiceV3.java
  19. 18 22
      src/main/java/com/nokia/tsl_data/service/TaskService.java
  20. 28 19
      src/main/java/com/nokia/tsl_data/service/TslDataService.java
  21. 70 1
      src/main/java/com/nokia/tsl_data/util/excel/PoiUtil.java
  22. 1 1
      src/main/resources/application.yml
  23. 5 3
      src/test/java/com/nokia/tsl_data/TslDataApplicationTest.java

+ 87 - 0
doc/开发环境/2023年适配/2024年适配.md

@@ -1,5 +1,7 @@
 # 2024年适配
 
+## sheet1 管理端-移网感知类
+
 统计数据在原有基础(河北_CEM移网质量投诉明细全量数据)上增加新订阅的 河北客户体验管理智能定责投诉明细月累计接口日 表中 日定责问题分类(duty_reason_id_day) 字段值为 业务使用>>基础业务使用>>移网主被叫>>手机无法主被叫 的部分。
 
 新建表 management_detail 用于
@@ -37,3 +39,88 @@ from tsl_data.complaint_details_fix_ywd_day cdfyd
 where duty_reason_id_day = '业务使用>>基础业务使用>>移网主被叫>>手机无法主被叫'
 and month_id = '202312' and day_id = '24'
 ```
+
+## 投诉处理时长、超时工单概况调整
+
+- 从工作流获取数据
+
+```sql
+select create_time as work_flow_create_time, 
+update_time as work_flow_update_time,
+city as city_id, 
+form_basic_data->>'yh_region' as region_id, 
+form_basic_data->>'kd_kfsn' as kfsn,
+-- 客服受理时间
+form_basic_data->>'kd_accepttime' as kd_accepttime,
+-- 客服要求答复时间
+form_basic_data->>'kd_requestreplytime' as kd_requestreplytime,
+-- 客服答复状态
+form_basic_data->>'re_is_status' as re_is_status,
+-- 退单时间
+form_basic_data->>'re_is_date' as re_is_date,
+-- 退单内容 这个字段有问题
+-- form_basic_data->>'kd_kfopinion' as kd_kfopinion,
+-- 客服归档时间
+form_basic_data->>'kf_file_time' as kf_file_time,
+-- 客服归档类型
+form_basic_data->>'kf_file_type' as kf_file_type
+from flow_form_basic
+where create_time >= to_timestamp('2023-12-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')
+```
+
+- 新建本地存储表
+
+```sql
+CREATE TABLE tsl_data.work_flow_basic_data (
+	id bigserial NOT null,
+	order_no varchar(50) not null,
+	kfsn varchar(50) NOT NULL,
+	city_id varchar(50) NULL,
+	region_id varchar(50) NULL,
+	kd_accept_time varchar(50) NULL,
+	kd_request_reply_time varchar(50) NULL,
+	re_is_status_id varchar(50) NULL,
+	kf_file_time varchar(50) NULL,
+	kf_file_type varchar(50) NULL,
+	work_flow_create_time timestamp NULL,
+	work_flow_update_time timestamp NULL,
+	CONSTRAINT work_flow_basic_data_pkey PRIMARY KEY (id)
+);
+```
+
+c43d1e18b0b84da0b7bd13e482e8a5a1	未答复
+06678b79185349b5bf0c24490a978fbb	已退单
+06678b79185349b5bf0c24490a977fbb	已回单
+
+投诉处理时长 = 归档时间 - 受理时间
+
+kd_accepttime 客服受理时间 没有空的清苦给你
+re_is_status 客服答复状态 不存在空值
+
+> 已退单的排除掉,不做考察
+
+### 已回单情况
+
+- kf_file_type 客服归档类型 情况
+
+回访归档	有归档时间	6438
+null	归档时间为空	231
+GIS归档	有归档时间	2
+
+> 归档时间为空的情况可以认为是回单了但未归档
+
+### 未答复情况
+
+> ??? 未答复的情况应如何处理
+
+撤单归档	有归档时间	7
+回访归档	有归档时间	9
+null	归档时间为空	708
+
+> 归档时间为空的情况如何处理?
+
+111
+
+回单超时?
+
+回单未超时?

+ 20 - 1
doc/需求文档/2024年适配/关于投诉工单日报表2024年适配的需求.md

@@ -40,7 +40,26 @@
 
 投诉处理时长、超时工单概况:数据源更换成进入TOP的工单(剔除退单),计算各地市的平均处理时长(归档时间-受理时间),如果归档时间缺失(……),()目标值36小时
 
-### 7.4 支撑地市日报功能
+修改投诉处理时长、超时工单概况处理逻辑:
+
+1. 数据源修改为进入工单流程系统(TOP)的工单。
+    - 排除已退单(re_is_status)状态的工单
+    - 统计范围为所有存在归档时间的工单
+2. 处理时长 = 客服受理时间 - 客服受理时间,处理时长超过36小时为超时。
+
+### 7.4 三率调整
+
+指标三:移网综合投诉评价“三率”(1.5分)
+1、指标定义
+按照响应率:解决率:满意率=1:4:5,综合评价投诉处理质效,考核范围为全量投诉单。
+2、计算规则
+移网综合投诉评价“三率”=响应率*0.1+解决率*0.4+满意率*0.5
+目标值92%,解决率≤90%不得分,90%至92%线性得分。
+3、取值来源:客服运营平台
+
+如果响应:解决:满意是90%:91%:92%,那这个值就是90%*0.1+91%*0.4+92%*0.5=91.4%
+
+### 7.5 支撑地市日报功能
 
 支撑地市发送日报功能,可实现地市更新区县后,
 

+ 0 - 26
src/main/java/com/nokia/tsl_data/config/TaskSchedulingConfig.java

@@ -1,26 +0,0 @@
-package com.nokia.tsl_data.config;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-
-/**
- * 任务调度线程配置
- */
-@Configuration
-public class TaskSchedulingConfig {
-
-    private final int poolSize = 3;
-
-    /**
-     * 调度器,使用默认的ThreadPoolTaskScheduler
-     */
-    @Bean
-    public ThreadPoolTaskScheduler taskScheduler() {
-        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
-        taskScheduler.setPoolSize(poolSize);
-        taskScheduler.setThreadNamePrefix("taskSchedulerThreadPool-");
-        taskScheduler.initialize();
-        return taskScheduler;
-    }
-}

+ 17 - 1
src/main/java/com/nokia/tsl_data/config/TslDataConfig.java

@@ -3,6 +3,7 @@ package com.nokia.tsl_data.config;
 import com.nokia.tsl_data.properties.CustomerRateTargetProperties;
 import com.nokia.tsl_data.properties.DataWarehouseProperties;
 import com.nokia.tsl_data.properties.OutputProperties;
+import com.nokia.tsl_data.properties.TslDataProperties;
 import com.zaxxer.hikari.HikariConfig;
 import com.zaxxer.hikari.HikariDataSource;
 
@@ -12,12 +13,15 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 
 @Configuration
-@EnableConfigurationProperties({ DataWarehouseProperties.class, OutputProperties.class,
+@EnableConfigurationProperties({ TslDataProperties.class, DataWarehouseProperties.class, OutputProperties.class,
         CustomerRateTargetProperties.class })
 public class TslDataConfig {
 
+    private final int poolSize = 3;
+
     @Bean
     public JdbcTemplate jdbcTemplate(DataSource dataSource) {
         return new JdbcTemplate(dataSource);
@@ -36,4 +40,16 @@ public class TslDataConfig {
         config.setPassword("Richr00t");
         return new JdbcTemplate(new HikariDataSource(config));
     }
+
+    /**
+     * 调度器,使用默认的ThreadPoolTaskScheduler
+     */
+    @Bean
+    public ThreadPoolTaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
+        taskScheduler.setPoolSize(poolSize);
+        taskScheduler.setThreadNamePrefix("taskSchedulerThreadPool-");
+        taskScheduler.initialize();
+        return taskScheduler;
+    }
 }

+ 28 - 0
src/main/java/com/nokia/tsl_data/controller/DataWarehouseController.java

@@ -105,4 +105,32 @@ public class DataWarehouseController {
             return R.error().message(String.format("工作流基础数据更新失败: %s", e.getMessage()));
         }
     }
+
+    @PostMapping("warehouse/complaint_details_fix_ywd")
+    public R warehouseComplaintDetailsFixYwdDay(@RequestBody String day) {
+        try {
+            dataWarehouseService.warehouseComplaintDetailsFixYwdDay(day);
+            return R.ok().message(String.format("管理端详表 ComplaintDetailsFix(河北客户体验管理智能定责投诉明细月累计接口日) 数据账期 %s 入库成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("管理端详表 ComplaintDetailsFix(河北客户体验管理智能定责投诉明细月累计接口日) 数据账期 %s 入库失败: %s",
+                    day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("delete/complaint_details_fix_ywd")
+    public R deleteComplaintDetailsFixYwdDay(@RequestBody String day) {
+        try {
+            int count = dataWarehouseService.deleteComplaintDetailsFixYwdDay(day);
+            return R.ok().message(
+                    String.format("客户端详表 ComplaintDetailsFix(河北客户体验管理智能定责投诉明细月累计接口日) 数据账期 %s 删除 %s 条", day, count));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("客户端详表 ComplaintDetailsFix(河北客户体验管理智能定责投诉明细月累计接口日) 数据账期 %s 删除失败: %s",
+                    day, e.getMessage()));
+        }
+    }
+
 }

+ 42 - 18
src/main/java/com/nokia/tsl_data/dao/HighQualityCountMapper.java

@@ -24,10 +24,14 @@ public interface HighQualityCountMapper {
     /**
      * 查询客户端地市响应率数据 参数day_id 20231105
      */
-    @Select("select businoareaname                                                                           as city,\n" +
-            "       complaint_response_list::float8                                                          as numerator,\n" +
-            "       complaint_response_count::float8 + complaint::float8                                     as denominator,\n" +
-            "       complaint_response_list::float8 / (complaint_response_count::float8 + complaint::float8) as rate\n" +
+    @Select("select businoareaname                                                                           as city,\n"
+            +
+            "       complaint_response_list::float8                                                          as numerator,\n"
+            +
+            "       complaint_response_count::float8 + complaint::float8                                     as denominator,\n"
+            +
+            "       complaint_response_list::float8 / (complaint_response_count::float8 + complaint::float8) as rate\n"
+            +
             "from tsl_data.high_quality_count_day\n" +
             "where month_id = substring(#{day_id} from 1 for 6)\n" +
             "  and day_id = substring(#{day_id} from 7 for 2)\n" +
@@ -39,10 +43,14 @@ public interface HighQualityCountMapper {
     /**
      * 查询客户端地市解决率 参数day_id 20231105
      */
-    @Select("select businoareaname                                                                               as city,\n" +
-            "       complaint_resolution_list::float8                                                            as numerator,\n" +
-            "       complaint_resolution_count::float8 + complaint::float8                                       as denominator,\n" +
-            "       complaint_resolution_list::float8 / (complaint_resolution_count::float8 + complaint::float8) as rate\n" +
+    @Select("select businoareaname                                                                               as city,\n"
+            +
+            "       complaint_resolution_list::float8                                                            as numerator,\n"
+            +
+            "       complaint_resolution_count::float8 + complaint::float8                                       as denominator,\n"
+            +
+            "       complaint_resolution_list::float8 / (complaint_resolution_count::float8 + complaint::float8) as rate\n"
+            +
             "from tsl_data.high_quality_count_day\n" +
             "where month_id = substring(#{day_id} from 1 for 6)\n" +
             "  and day_id = substring(#{day_id} from 7 for 2)\n" +
@@ -54,10 +62,14 @@ public interface HighQualityCountMapper {
     /**
      * 查询客户端地市满意度 参数day_id 20231105
      */
-    @Select("select businoareaname                                                                             as city,\n" +
-            "       complaint_satisfied_list::float8                                                           as numerator,\n" +
-            "       complaint_satisfied_count::float8 + complaint::float8                                      as denominator,\n" +
-            "       complaint_satisfied_list::float8 / (complaint_satisfied_count::float8 + complaint::float8) as rate\n" +
+    @Select("select businoareaname                                                                             as city,\n"
+            +
+            "       complaint_satisfied_list::float8                                                           as numerator,\n"
+            +
+            "       complaint_satisfied_count::float8 + complaint::float8                                      as denominator,\n"
+            +
+            "       complaint_satisfied_list::float8 / (complaint_satisfied_count::float8 + complaint::float8) as rate\n"
+            +
             "from tsl_data.high_quality_count_day\n" +
             "where month_id = substring(#{day_id} from 1 for 6)\n" +
             "  and day_id = substring(#{day_id} from 7 for 2)\n" +
@@ -73,15 +85,20 @@ public interface HighQualityCountMapper {
             "complaint_resolution_list::float8, complaint_resolution_count::float8, complaint_response_list::float8, " +
             "complaint_response_count::float8, complaint::float8 " +
             "from report_auto.he_d_high_quality hdhq " +
-            "where acct_date = #{day} and profes_dep = '网络质量' and big_type_name = '移网网络体验' and small_type_name = '--') " +
-            "select '全省' as businoareaname, sum(complaint_satisfied_list) / sum(complaint_satisfied_count + complaint) as complaint_satisfied, " +
-            "   sum(complaint_resolution_list) / sum(complaint_resolution_count + complaint) as complaint_resolution, " +
-            "   sum(complaint_response_list) / sum(complaint_response_count + complaint)  as complaint_response from t1 " +
+            "where acct_date = #{day} and profes_dep = '网络质量' and big_type_name = '移网网络体验' and small_type_name = '--') "
+            +
+            "select '全省' as businoareaname, sum(complaint_satisfied_list) / sum(complaint_satisfied_count + complaint) as complaint_satisfied, "
+            +
+            "   sum(complaint_resolution_list) / sum(complaint_resolution_count + complaint) as complaint_resolution, "
+            +
+            "   sum(complaint_response_list) / sum(complaint_response_count + complaint)  as complaint_response from t1 "
+            +
             "union select businoareaname, " +
             "case when (complaint_satisfied_count + complaint) = 0 then 0 " +
             "  else complaint_satisfied_list / (complaint_satisfied_count + complaint) end as complaint_satisfied, " +
             "case when (complaint_resolution_count + complaint) = 0 then 0 " +
-            "  else complaint_resolution_list / (complaint_resolution_count + complaint) end as complaint_resolution, " +
+            "  else complaint_resolution_list / (complaint_resolution_count + complaint) end as complaint_resolution, "
+            +
             "case when (complaint_response_count + complaint) = 0 then 0 " +
             "  else complaint_response_list / (complaint_response_count + complaint) end     as complaint_response from t1")
     List<Map<String, Object>> selectClientRatioForDay(String day);
@@ -100,7 +117,7 @@ public interface HighQualityCountMapper {
             "where month_id = #{month_id} and day_id = #{day_id} and profes_dep = '网络质量' " +
             "and big_type_name = '移网网络体验' and small_type_name = '--'")
     List<Map<String, Object>> selectTotalComplaintsForMonthIdAndDay(@Param("month_id") String monthId,
-                                                                    @Param("day_id") String day);
+            @Param("day_id") String day);
 
     @Delete("delete from tsl_data.high_quality_count_day where month_id = #{month_id} and day_id = #{day_id}")
     int deleteHighQualityCountForMonthIdAndDayId(@Param("month_id") String monthId, @Param("day_id") String dayId);
@@ -115,4 +132,11 @@ public interface HighQualityCountMapper {
     default int countForDay(String day) {
         return countForDay(day.substring(0, 6), day.substring(6));
     }
+
+    /**
+     * 按照账期查询数据
+     */
+    default int selectCountOfStatDay(String day) {
+        return countForDay(day.substring(0, 6), day.substring(6));
+    }
 }

+ 6 - 0
src/main/java/com/nokia/tsl_data/dao/ManagementDetailMapper.java

@@ -70,4 +70,10 @@ public interface ManagementDetailMapper {
             " where duty_reason_id_day = '业务使用>>基础业务使用>>移网主被叫>>手机无法主被叫'\r\n" +
             " and month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2) ")
     int insertFromComplaintDetailsFixYwd(String day);
+
+    /**
+     * 按照账期统计某个账期的数据量
+     */
+    @Select("select count(1) from tsl_data.management_detail where stat_day_id = #{day} ")
+    int selectCountofStatDay(String day);
 }

+ 53 - 22
src/main/java/com/nokia/tsl_data/dao/TslDataDao.java

@@ -27,20 +27,30 @@ public class TslDataDao {
      * 采用BatchSqlUpdate的方法批量insert到表tableName(可以是临时表)
      */
     public void batchInsertWorkFlowBasicData(List<WorkFlowBasicData> data, String tableName) {
-        String sqlFormat = "INSERT INTO tsl_data.%s\n" +
-                "(city_id, kfsn, region_id, work_flow_create_time, work_flow_update_time)\n" +
-                "VALUES(?,?,?,?,?)";
+        String sqlFormat = "INSERT INTO %s " +
+                "(order_no, kfsn, city_id, region_id, kd_accept_time, kd_request_reply_time, re_is_status_id, " +
+                " kf_file_time, kf_file_type, work_flow_create_time, work_flow_update_time) " +
+                "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
         BatchSqlUpdate batchSqlUpdate = new BatchSqlUpdate(Objects.requireNonNull(jdbcTemplate.getDataSource()),
                 String.format(sqlFormat, tableName));
         batchSqlUpdate.setBatchSize(1000);
         batchSqlUpdate.setTypes(new int[] {
-                Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, Types.TIMESTAMP
+                Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+                Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+                Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
+                Types.TIMESTAMP, Types.TIMESTAMP
         });
         for (WorkFlowBasicData item : data) {
             batchSqlUpdate.update(
-                    item.getCityId(),
+                    item.getOrderNo(),
                     item.getKfsn(),
+                    item.getCityId(),
                     item.getRegionId(),
+                    item.getKdAcceptTime(),
+                    item.getKdRequestReplyTime(),
+                    item.getReIsStatusId(),
+                    item.getKfFileTime(),
+                    item.getKfFileType(),
                     item.getWorkFlowCreateTime() == null ? null : Timestamp.from(item.getWorkFlowCreateTime()),
                     item.getWorkFlowUpdateTime() == null ? null : Timestamp.from(item.getWorkFlowUpdateTime()));
         }
@@ -52,15 +62,21 @@ public class TslDataDao {
      */
     public String createTempTableOfWorkFlowBasicData() {
         // 临时表添加一个时间戳
-        String tempTableName = "work_flow_basic_data_temp_" + System.currentTimeMillis();
-        String sqlFormat = "CREATE TABLE tsl_data.%s (\n" +
-                "\tcity_id varchar(50) NULL,\n" +
-                "\tkfsn varchar(50) NOT NULL,\n" +
-                "\tregion_id varchar(50) NULL,\n" +
-                "\twork_flow_create_time timestamp NULL,\n" +
-                "\twork_flow_update_time timestamp NULL\n" +
+        String tempTableName = "tsl_data.work_flow_basic_data_temp_" + System.currentTimeMillis();
+        String sql = "CREATE TABLE %s (  " + //
+                " order_no varchar(50) not null,  " + //
+                " kfsn varchar(50) NOT NULL,  " + //
+                " city_id varchar(50) NULL,  " + //
+                " region_id varchar(50) NULL,  " + //
+                " kd_accept_time varchar(50) NULL,  " + //
+                " kd_request_reply_time varchar(50) NULL,  " + //
+                " re_is_status_id varchar(50) NULL,  " + //
+                " kf_file_time varchar(50) NULL,  " + //
+                " kf_file_type varchar(50) NULL,  " + //
+                " work_flow_create_time timestamp NULL,  " + //
+                " work_flow_update_time timestamp NULL " + //
                 ");";
-        jdbcTemplate.execute(String.format(sqlFormat, tempTableName));
+        jdbcTemplate.update(String.format(sql, tempTableName));
         return tempTableName;
     }
 
@@ -91,11 +107,11 @@ public class TslDataDao {
      */
     public int insertWorkFlowBasicDataFromTempTable(String tempTableName) {
         String sqlFormat = "insert into tsl_data.work_flow_basic_data" +
-                " (city_id, kfsn, region_id, work_flow_create_time, work_flow_update_time," +
-                " create_date, last_update_date)" +
-                " select t.city_id, t.kfsn, t.region_id, t.work_flow_create_time," +
-                " t.work_flow_update_time, now(), now()" +
-                " from tsl_data.%s t " +
+                " (order_no, kfsn, city_id, region_id, kd_accept_time, kd_request_reply_time, re_is_status_id, " +
+                " kf_file_time, kf_file_type, work_flow_create_time, work_flow_update_time)" +
+                " select t.order_no, t.kfsn, t.city_id, t.region_id, t.kd_accept_time, t.kd_request_reply_time," +
+                " t.re_is_status_id, t.kf_file_time, t.kf_file_type, t.work_flow_create_time, t.work_flow_update_time" +
+                " from %s t " +
                 " left join tsl_data.work_flow_basic_data b" +
                 " on t.kfsn = b.kfsn" +
                 " where b.kfsn is null";
@@ -107,10 +123,14 @@ public class TslDataDao {
      */
     public int updateWorkFlowBasicDataFromTempTable(String tempTableName) {
         String sqlFormat = "update tsl_data.work_flow_basic_data b\n" +
-                "set city_id = t.city_id, region_id = t.region_id, work_flow_update_time = t.work_flow_update_time," +
-                " last_update_date = now()" +
-                " from (select city_id, kfsn, region_id, work_flow_create_time, work_flow_update_time" +
-                " from tsl_data.%s) t" +
+                "set order_no = t.order_no, city_id = t.city_id, region_id = t.region_id, " +
+                " kd_accept_time = t.kd_accept_time, kd_request_reply_time = t.kd_request_reply_time, " +
+                " re_is_status_id = t.re_is_status_id, kf_file_time = t.kf_file_time, " +
+                " kf_file_type = t.kf_file_type, work_flow_create_time = t.work_flow_create_time, " +
+                " work_flow_update_time = t.work_flow_update_time " +
+                " from (select order_no, kfsn, city_id, region_id, kd_accept_time, kd_request_reply_time, " +
+                " re_is_status_id, kf_file_time, kf_file_type, work_flow_create_time, work_flow_update_time " +
+                " from %s ) t" +
                 " where b.kfsn = t.kfsn and (b.city_id != t.city_id or b.region_id != t.region_id)";
         return jdbcTemplate.update(String.format(sqlFormat, tempTableName));
     }
@@ -197,4 +217,15 @@ public class TslDataDao {
                 " ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now());";
         jdbcTemplate.batchUpdate(sql, data);
     }
+
+    /**
+     * 按照账期清空 complaint_details_fix_ywd_day 表某天的数据
+     */
+    public int deleteComplaintDetailsFixYwdDay(String day) {
+        String sql = "delete from tsl_data.complaint_details_fix_ywd_day where month_id = ? and day_id = ?";
+        return jdbcTemplate.update(sql, (ps) -> {
+            ps.setString(1, day.substring(0, 6));
+            ps.setString(2, day.substring(6, 8));
+        });
+    }
 }

+ 0 - 9
src/main/java/com/nokia/tsl_data/dao/WorkFlowBasicDataRepository.java

@@ -1,9 +0,0 @@
-package com.nokia.tsl_data.dao;
-
-import com.nokia.tsl_data.entity.WorkFlowBasicData;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public interface WorkFlowBasicDataRepository extends JpaRepository<WorkFlowBasicData, Long> {
-}

+ 56 - 35
src/main/java/com/nokia/tsl_data/dao/WorkFlowDao.java

@@ -4,39 +4,50 @@ import com.nokia.tsl_data.entity.SysDataDictionary;
 import com.nokia.tsl_data.entity.WorkFlowBasicData;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
 import org.springframework.stereotype.Component;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
+import java.sql.Types;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
 
 /**
- * 从
+ * 从 工作流数据库 读取
  */
 @Component
 public class WorkFlowDao {
 
     private final JdbcTemplate jdbcTemplate;
 
-
     public WorkFlowDao(@Qualifier("workFlowJdbcTemplate") JdbcTemplate jdbcTemplate) {
         this.jdbcTemplate = jdbcTemplate;
     }
 
     // 获取全部地市信息
     public List<SysDataDictionary> findSysDataDictionaryForCity() {
-        String sql = "select id, name, type_code from sys_data_dictionary sdd where type_code = 'city'";
-        return jdbcTemplate.query(sql, new SysDataDictionaryRowMapper());
+        return findSysDataDictionaries("city");
     }
 
     // 获取全部区县信息
     public List<SysDataDictionary> findSysDataDictionaryForRegion() {
-        String sql = "select id, name, type_code from sys_data_dictionary sdd where type_code = 'region'";
-        return jdbcTemplate.query(sql, new SysDataDictionaryRowMapper());
+        return findSysDataDictionaries("region");
+    }
+
+    // 读取某种类型的字典表
+    public List<SysDataDictionary> findSysDataDictionaries(String type) {
+        String sql = "select id, name, type_code from sys_data_dictionary sdd where type_code = ? ";
+        return jdbcTemplate.query(sql,
+                (ps) -> {
+                    ps.setString(1, type);
+                },
+                (rs, rowNum) -> {
+                    SysDataDictionary sysDataDictionary = new SysDataDictionary();
+                    sysDataDictionary.setType("work_flow_" + rs.getString("type_code"));
+                    sysDataDictionary.setNickCode(rs.getString("id"));
+                    sysDataDictionary.setNickName(rs.getString("name"));
+                    return sysDataDictionary;
+                });
     }
 
     // 获取update_time在start之后的全部工单信息
@@ -46,31 +57,41 @@ public class WorkFlowDao {
 
     // 获取update_time在两个范围之间的全部工单信息
     public List<WorkFlowBasicData> findWorkFlowBasicDataByUpdateTime(Instant start, Instant end) {
-        String sqlFormat = "select create_time as work_flow_create_time, update_time as work_flow_update_time, \n" +
-                "city as city_id, form_basic_data->>'yh_region' as region_id, form_basic_data->>'kd_kfsn' as kfsn \n" +
-                "from flow_form_basic where update_time >= to_timestamp('%s', 'yyyy-mm-dd hh24:mi:ss') \n" +
-                "and update_time <= to_timestamp('%s', 'yyyy-mm-dd hh24:mi:ss')";
-        DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("Asia/Shanghai"));
-        return jdbcTemplate.query(String.format(sqlFormat, dateFormat.format(start), dateFormat.format(end)), (rs, rowNum) -> {
-            WorkFlowBasicData basicData = new WorkFlowBasicData();
-            basicData.setWorkFlowCreateTime(Instant.ofEpochMilli(rs.getTimestamp("work_flow_create_time").getTime()));
-            basicData.setWorkFlowUpdateTime(Instant.ofEpochMilli(rs.getTimestamp("work_flow_update_time").getTime()));
-            basicData.setCityId(rs.getString("city_id"));
-            basicData.setRegionId(rs.getString("region_id"));
-            basicData.setKfsn(rs.getString("kfsn"));
-            return basicData;
-        });
-    }
-
-    private static class SysDataDictionaryRowMapper implements RowMapper<SysDataDictionary> {
-
-        @Override
-        public SysDataDictionary mapRow(ResultSet rs, int rowNum) throws SQLException {
-            SysDataDictionary sysDataDictionary = new SysDataDictionary();
-            sysDataDictionary.setType("work_flow_"+rs.getString("type_code"));
-            sysDataDictionary.setNickCode(rs.getString("id"));
-            sysDataDictionary.setNickName(rs.getString("name"));
-            return sysDataDictionary;
-        }
+        String sql = "select order_no, " + //
+                "city as city_id, " + //
+                "form_basic_data->>'yh_region' as region_id, " + //
+                "form_basic_data->>'kd_kfsn' as kfsn, " + //
+                "form_basic_data->>'kd_accepttime' as kd_accepttime, " + //
+                "form_basic_data->>'kd_requestreplytime' as kd_requestreplytime, " + //
+                "form_basic_data->>'re_is_status' as re_is_status_id, " + //
+                "form_basic_data->>'kf_file_time' as kf_file_time, " + //
+                "form_basic_data->>'kf_file_type' as kf_file_type, " + //
+                "create_time as work_flow_create_time,  " + //
+                "update_time as work_flow_update_time " + //
+                "from flow_form_basic " + //
+                "where update_time >= to_timestamp(?, 'yyyy-mm-dd hh24:mi:ss') " + //
+                "and update_time <= to_timestamp(?, 'yyyy-mm-dd hh24:mi:ss') ";
+        DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
+                .withZone(ZoneId.of("Asia/Shanghai"));
+        Object[] objects = new Object[] { dateFormat.format(start), dateFormat.format(end) };
+        int[] argTypes = new int[] { Types.TIMESTAMP, Types.TIMESTAMP };
+        return jdbcTemplate.query(sql, objects, argTypes,
+                (rs, rowNum) -> {
+                    WorkFlowBasicData basicData = new WorkFlowBasicData();
+                    basicData.setOrderNo(rs.getString("order_no"));
+                    basicData.setCityId(rs.getString("city_id"));
+                    basicData.setRegionId(rs.getString("region_id"));
+                    basicData.setKfsn(rs.getString("kfsn"));
+                    basicData.setKdAcceptTime(rs.getString("kd_accepttime"));
+                    basicData.setKdRequestReplyTime(rs.getString("kd_requestreplytime"));
+                    basicData.setReIsStatusId(rs.getString("re_is_status_id"));
+                    basicData.setKfFileTime(rs.getString("kf_file_time"));
+                    basicData.setKfFileType(rs.getString("kf_file_type"));
+                    basicData.setWorkFlowCreateTime(
+                            Instant.ofEpochMilli(rs.getTimestamp("work_flow_create_time").getTime()));
+                    basicData.setWorkFlowUpdateTime(
+                            Instant.ofEpochMilli(rs.getTimestamp("work_flow_update_time").getTime()));
+                    return basicData;
+                });
     }
 }

+ 15 - 26
src/main/java/com/nokia/tsl_data/entity/WorkFlowBasicData.java

@@ -1,47 +1,36 @@
 package com.nokia.tsl_data.entity;
 
 import lombok.Data;
-import org.springframework.data.annotation.CreatedDate;
-import org.springframework.data.annotation.LastModifiedDate;
-import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-
-import javax.persistence.*;
 import java.time.Instant;
 
 /**
  * 数据源是工单流程的flow_form_basic表 码表 sys_data_dictionary
+ * tsl_data.work_flow_basic_data
  */
 @Data
-@Entity
-@EntityListeners(AuditingEntityListener.class)
-@Table(name = "work_flow_basic_data", schema = "tsl_data")
 public class WorkFlowBasicData {
 
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
-
+    // 流程系统内工单号 唯一
+    private String orderNo;
     // 客服工单号 理论上不应有重复,但是实际上会出现重复
-    @Column(name = "kfsn", columnDefinition = "varchar(50)", nullable = false)
     private String kfsn;
-
     // 归属地市
-    @Column(name = "city_id", columnDefinition = "varchar(50)")
     private String cityId;
-
     // 归属区县
-    @Column(name = "region_id", columnDefinition = "varchar(50)")
     private String regionId;
-
-    @Column
+    // 客服受理时间
+    private String kdAcceptTime;
+    // 客服要求答复时间
+    private String kdRequestReplyTime;
+    // 客服答复状态
+    private String reIsStatusId;
+    // 客服归档时间
+    private String kfFileTime;
+    // 客服归档类型
+    private String kfFileType;
+    // 工单创建时间
     private Instant workFlowCreateTime;
-
-    @Column
+    // 工单更新时间
     private Instant workFlowUpdateTime;
-
-    @CreatedDate
-    private Instant createDate;
-
-    @LastModifiedDate
-    private Instant lastUpdateDate;
 }

+ 21 - 0
src/main/java/com/nokia/tsl_data/entity/pojo/XSSFWorkbookWrapper.java

@@ -0,0 +1,21 @@
+package com.nokia.tsl_data.entity.pojo;
+
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import lombok.Data;
+
+@Data
+public class XSSFWorkbookWrapper {
+    private XSSFWorkbook workbook;
+    // 基本模式 微软雅黑 10号字 带全边框 水平居中
+    private XSSFCellStyle cellStyle1;
+    // 百分比 2位小数
+    private XSSFCellStyle cellStyle2;
+    // 自动换行 背景色 FFAEAAAA
+    private XSSFCellStyle cellStyle3;
+    // 加粗 背景色 FFAEAAAA
+    private XSSFCellStyle cellStyle4;
+    // 2位小数
+    private XSSFCellStyle cellStyle5;
+}

+ 12 - 0
src/main/java/com/nokia/tsl_data/exception/LackOfDataRuntimeException.java

@@ -0,0 +1,12 @@
+package com.nokia.tsl_data.exception;
+
+public class LackOfDataRuntimeException extends RuntimeException {
+
+    public LackOfDataRuntimeException(String message) {
+        super(message);
+    }
+
+    public LackOfDataRuntimeException(String tableName, String statDay) {
+        super(String.format("%s 表缺少账期 %s 数据", tableName, statDay));
+    }
+}

+ 12 - 0
src/main/java/com/nokia/tsl_data/properties/TslDataProperties.java

@@ -0,0 +1,12 @@
+package com.nokia.tsl_data.properties;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import lombok.Data;
+
+@Data
+@ConfigurationProperties(prefix = "tsl.data")
+public class TslDataProperties {
+    private String outputPath = "./output";
+    private String outputFileNamePrefix = "投诉清单各地市投诉率";
+}

+ 7 - 0
src/main/java/com/nokia/tsl_data/service/DataWarehouseService.java

@@ -114,6 +114,13 @@ public class DataWarehouseService {
         return highQualityListDayMapper.deleteHighQualityListForDay(day);
     }
 
+    /**
+     * 删除 河北客户体验管理智能定责投诉明细月累计接口日
+     */
+    public int deleteComplaintDetailsFixYwdDay(String day) {
+        return tslDataDao.deleteComplaintDetailsFixYwdDay(day);
+    }
+
     /**
      * 入库 河北高质量2日明细数据
      */

+ 11 - 0
src/main/java/com/nokia/tsl_data/service/ManagementDetailService.java

@@ -14,6 +14,7 @@ import com.nokia.tsl_data.dao.ManagementDetailMapper;
 import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
 import com.nokia.tsl_data.dao.TargetTsRatioRepository;
 import com.nokia.tsl_data.entity.TargetTsRatio;
+import com.nokia.tsl_data.exception.LackOfDataRuntimeException;
 import com.nokia.tsl_data.util.DateFormatUtil;
 
 /**
@@ -122,4 +123,14 @@ public class ManagementDetailService {
         }
         return result;
     }
+
+    /**
+     * 按照账期检查数据
+     */
+    public void checkStatDayCount(String day) {
+        int count = managementDetailMapper.selectCountofStatDay(day);
+        if (count == 0) {
+            throw new LackOfDataRuntimeException(String.format("management_detail 表缺少账期 %s 数据", day));
+        }
+    }
 }

+ 2 - 4
src/main/java/com/nokia/tsl_data/service/TslReportService.java → src/main/java/com/nokia/tsl_data/service/ReportServiceV1.java

@@ -4,8 +4,8 @@ import com.nokia.tsl_data.dao.HighQualityCountMapper;
 import com.nokia.tsl_data.dao.MobileComplaintMapper;
 import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
 import com.nokia.tsl_data.properties.OutputProperties;
+import com.nokia.tsl_data.util.excel.PoiUtil;
 import com.nokia.tsl_data.util.excel.entity.CellRect;
-import com.nokia.tsl_data.util.excel.poi.PoiUtil;
 
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.EncryptedDocumentException;
@@ -35,7 +35,7 @@ import java.util.*;
  */
 @Slf4j
 @Service
-public class TslReportService {
+public class ReportServiceV1 {
 
     @Autowired
     private TslDataService tslDataService;
@@ -254,8 +254,6 @@ public class TslReportService {
      * 客户端-投诉问题解决满意度
      * 客户端-投诉问题解决率
      * 客户端-投诉问题响应率
-     * 
-     * @param day
      */
     private void getSheet4_6(String day) {
         // 计算时间常数

+ 441 - 0
src/main/java/com/nokia/tsl_data/service/ReportServiceV3.java

@@ -0,0 +1,441 @@
+package com.nokia.tsl_data.service;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Paths;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFColor;
+import org.apache.poi.xssf.usermodel.XSSFDataFormat;
+import org.apache.poi.xssf.usermodel.XSSFFont;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
+import com.nokia.tsl_data.entity.pojo.XSSFWorkbookWrapper;
+import com.nokia.tsl_data.properties.TslDataProperties;
+import com.nokia.tsl_data.util.excel.PoiUtil;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+public class ReportServiceV3 {
+
+    @Autowired
+    private TslDataProperties tslDataProperties;
+
+    @Autowired
+    private SysDataDictionaryRepository sysDataDictionaryRepository;
+
+    @Autowired
+    private ManagementDetailService managementDetailService;
+
+    @Autowired
+    private TslDataService tslDataService;
+
+    private static final DateFormat DAY_FORMAT = new SimpleDateFormat("yyyyMMdd");
+
+    /**
+     * 生成报表
+     */
+    public void generateReport(String day) {
+        // 1. 检查文件路径
+        String fileName = tslDataProperties.getOutputFileNamePrefix() + day + ".xlsx";
+        File file = Paths.get(tslDataProperties.getOutputPath(), "V3", day).toFile();
+        if (!file.exists()) {
+            boolean mkdirs = file.mkdirs();
+            System.out.println(mkdirs);
+        }
+        // 2. 创建workbook并写入各个sheet
+        XSSFWorkbookWrapper workbookWrapper = getWorkbook();
+        // 2.1 写入sheet 管理端-移网感知类
+        managementDetailService.checkStatDayCount(day);
+        writeSheet1(workbookWrapper, day);
+        // 2.2 写入 服请情况
+        // 2.3 写入 重复投诉 超时工单情况
+        // 2.4 写入三率
+        tslDataService.checkStatDayCount(day);
+        writeSheet4_6(workbookWrapper, day);
+        // 3. 写入到本地文件
+        try (OutputStream outputStream = new FileOutputStream(
+                Paths.get(file.getAbsolutePath(), fileName).toFile())) {
+            workbookWrapper.getWorkbook().write(outputStream);
+            workbookWrapper.getWorkbook().close();
+            // 销毁 workbookWrapper 实例
+            workbookWrapper = null;
+        } catch (IOException e) {
+            log.error("写入失败。。。" + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 客户端-投诉问题解决满意度
+     * 客户端-投诉问题解决率
+     * 客户端-投诉问题响应率
+     */
+    private void writeSheet4_6(XSSFWorkbookWrapper workbookWrapper, String day) {
+        List<List<List<Object>>> sheet4_6Data = tslDataService.getSheet4_6Data(day);
+        // 计算时间常数
+        Calendar calendar = Calendar.getInstance(Locale.CHINA);
+        try {
+            calendar.setTime(DAY_FORMAT.parse(day));
+        } catch (ParseException e) {
+            log.error("时间字符串解析失败--{}", day);
+        }
+        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+        Sheet sheet4 = workbookWrapper.getWorkbook().createSheet("客户端-投诉问题解决满意度");
+        Sheet sheet5 = workbookWrapper.getWorkbook().createSheet("客户端-投诉问题解决率");
+        Sheet sheet6 = workbookWrapper.getWorkbook().createSheet("客户端-投诉问题响应率");
+        Row row;
+        Cell cell;
+
+        // 客户端-投诉问题解决满意度 第一行
+        row = sheet4.createRow(0);
+        cell = row.createCell(0);
+        cell.setCellValue(String.format("投诉问题解决满意率(1-%s)", dayOfMonth));
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        // 合并单元格 A1 - D1
+        PoiUtil.addMergedRegion(sheet4, 0, 0, 0, 3);
+        // 客户端-投诉问题解决满意度 第二行
+        row = sheet4.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(1);
+        cell.setCellValue("满意率");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(2);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(3);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        int rowNum = 2;
+        for (List<Object> list : sheet4_6Data.get(0)) {
+            row = sheet4.createRow(rowNum++);
+            // 地市
+            cell = row.createCell(0);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(workbookWrapper.getCellStyle1());
+            // 投诉问题解决满意率
+            cell = row.createCell(1);
+            cell.setCellValue(((double) list.get(1)));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+            // 达标值
+            cell = row.createCell(2);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+            // 与达标值差距
+            cell = row.createCell(3);
+            cell.setCellValue(((double) list.get(3)));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+        }
+        // 设置条件格式D3-D14
+        PoiUtil.setConditionalFormattingRedToGreen(sheet4, 2, 13, 3, 3);
+        // 设置列宽 2048 1304 2048 2304
+        for (int i = 0; i < 4; i++) {
+            sheet4.setColumnWidth(i, 2848);
+        }
+        // 设置行高 15.0 15.0...
+        for (int i = 0; i < 15; i++) {
+            sheet4.getRow(i).setHeightInPoints(15.0F);
+        }
+
+        // 客户端-投诉问题解决率
+        row = sheet5.createRow(0);
+        cell = row.createCell(0);
+        cell.setCellValue(String.format("投诉问题解决率(1-%s)", dayOfMonth));
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        // 合并单元格 A1 - D1
+        PoiUtil.addMergedRegion(sheet5, 0, 0, 0, 3);
+        // 客户端-投诉问题解决率 第二行
+        row = sheet5.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(1);
+        cell.setCellValue("解决率");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(2);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(3);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        rowNum = 2;
+        for (List<Object> list : sheet4_6Data.get(1)) {
+            row = sheet5.createRow(rowNum++);
+            // 地市
+            cell = row.createCell(0);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(workbookWrapper.getCellStyle1());
+            // 投诉问题解决率
+            cell = row.createCell(1);
+            cell.setCellValue(((double) list.get(1)));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+            // 达标值
+            cell = row.createCell(2);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+            // 与达标值差距
+            cell = row.createCell(3);
+            cell.setCellValue(((double) list.get(3)));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+        }
+        // 设置条件格式D3-D14
+        PoiUtil.setConditionalFormattingRedToGreen(sheet5, 2, 13, 3, 3);
+        // 设置列宽 2048 1304 2048 2304
+        for (int i = 0; i < 4; i++) {
+            sheet5.setColumnWidth(i, 2848);
+        }
+        // 设置行高 15.0 15.0...
+        for (int i = 0; i < 15; i++) {
+            sheet5.getRow(i).setHeightInPoints(15.0F);
+        }
+
+        // 客户端-投诉问题响应率
+        row = sheet6.createRow(0);
+        cell = row.createCell(0);
+        cell.setCellValue(String.format("投诉问题响应率(1-%s)", dayOfMonth));
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        // 合并单元格 A1 - D1
+        PoiUtil.addMergedRegion(sheet6, 0, 0, 0, 3);
+        // 客户端-投诉问题响应率 第二行
+        row = sheet6.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(1);
+        cell.setCellValue("响应率");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(2);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        cell = row.createCell(3);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(workbookWrapper.getCellStyle3());
+        rowNum = 2;
+        for (List<Object> list : sheet4_6Data.get(2)) {
+            row = sheet6.createRow(rowNum++);
+            // 地市
+            cell = row.createCell(0);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(workbookWrapper.getCellStyle1());
+            // 投诉问题解决率
+            cell = row.createCell(1);
+            cell.setCellValue(((double) list.get(1)));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+            // 达标值
+            cell = row.createCell(2);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+            // 与达标值差距
+            cell = row.createCell(3);
+            cell.setCellValue(((double) list.get(3)));
+            cell.setCellStyle(workbookWrapper.getCellStyle2());
+        }
+        // 设置条件格式D3-D14
+        PoiUtil.setConditionalFormattingRedToGreen(sheet6, 2, 13, 3, 3);
+        // 设置列宽 2048 1304 2048 2304
+        for (int i = 0; i < 4; i++) {
+            sheet6.setColumnWidth(i, 2848);
+        }
+        // 设置行高 15.0 15.0...
+        for (int i = 0; i < 15; i++) {
+            sheet6.getRow(i).setHeightInPoints(15.0F);
+        }
+    }
+
+    /**
+     * 管理端-移网感知类 2024年适配需求
+     */
+    private void writeSheet1(XSSFWorkbookWrapper workbookWrapper, String day) {
+        String sheetName = "管理端-移网感知类";
+        // 获取数据
+        Map<String, List<Object>> seet1Data = managementDetailService.getSheet1DataV3(day);
+        // 计算天数
+        LocalDate date = LocalDate.parse(day, DateTimeFormatter.ofPattern("yyyyMMdd"));
+        int dayOfMonth = date.getDayOfMonth();
+        Sheet sheet = workbookWrapper.getWorkbook().createSheet(sheetName);
+        Row row;
+        Cell cell;
+        // 第一行 标题栏
+        cell = sheet.createRow(0).createCell(0);
+        cell.setCellValue(day.substring(0, 4) + "年客服投诉清单各地市投诉率情况(" + sheetName + ")");
+        cell.setCellStyle(workbookWrapper.getCellStyle4());
+        PoiUtil.addMergedRegion(sheet, 0, 0, 0, dayOfMonth + 7);
+
+        // 第二行 列名
+        row = sheet.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+        for (int i = 1; i <= dayOfMonth; i++) {
+            cell = row.createCell(i);
+            cell.setCellValue(i + "日");
+            cell.setCellStyle(workbookWrapper.getCellStyle1());
+        }
+        cell = row.createCell(dayOfMonth + 1);
+        cell.setCellValue("投诉总量");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+        cell = row.createCell(dayOfMonth + 2);
+        cell.setCellValue("用户数");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+        cell = row.createCell(dayOfMonth + 3);
+        cell.setCellValue("目前万投率");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+        cell = row.createCell(dayOfMonth + 4);
+        cell.setCellValue("本月预测");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+        cell = row.createCell(dayOfMonth + 5);
+        cell.setCellValue("目标值");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+        cell = row.createCell(dayOfMonth + 6);
+        cell.setCellValue("与目标差距");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+        cell = row.createCell(dayOfMonth + 7);
+        cell.setCellValue("地市");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+
+        int rowNum = 2;
+        int cellNum = 0;
+        // 写入各地市数据
+        for (String area : sysDataDictionaryRepository.findAllCityName()) {
+            row = sheet.createRow(rowNum++);
+            // 写入A列的地市
+            cell = row.createCell(cellNum++);
+            cell.setCellValue(area);
+            cell.setCellStyle(workbookWrapper.getCellStyle1());
+            for (Object obj : seet1Data.get(area)) {
+                cell = row.createCell(cellNum++);
+                cell.setCellValue(Double.parseDouble(obj.toString()));
+                cell.setCellStyle(workbookWrapper.getCellStyle1());
+            }
+            // 写入最后一列的地市
+            cell = row.createCell(cellNum);
+            cell.setCellValue(area);
+            cell.setCellStyle(workbookWrapper.getCellStyle1());
+            // cellNum复位
+            cellNum = 0;
+        }
+        // 写入全省数据
+        row = sheet.createRow(rowNum);
+        // 写入A列的地市
+        cell = row.createCell(cellNum++);
+        cell.setCellValue("全省");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+        for (Object obj : seet1Data.get("全省")) {
+            cell = row.createCell(cellNum++);
+            cell.setCellValue(Double.parseDouble(obj.toString()));
+            cell.setCellStyle(workbookWrapper.getCellStyle1());
+        }
+        // 写入最后一列的地市
+        cell = row.createCell(cellNum);
+        cell.setCellValue("全省");
+        cell.setCellStyle(workbookWrapper.getCellStyle1());
+
+        // 3-15行(2-14)dayOfMonth+2 - dayOfMonth+6是浮点数,设置为显示小数点后2位
+        for (int i = 2; i <= 14; i++) {
+            row = sheet.getRow(i);
+            for (int j = dayOfMonth + 2; j <= dayOfMonth + 6; j++) {
+                cell = row.getCell(j);
+                cell.setCellStyle(workbookWrapper.getCellStyle5());
+            }
+        }
+
+        // 添加条件格式B15-V15
+        PoiUtil.setConditionalFormattingGreenToRed(sheet, rowNum, rowNum, 1, dayOfMonth);
+        // 添加条件格式(dayOfMonth+3)(3-14)
+        PoiUtil.setConditionalFormattingGreenToRed(sheet, 2, 13, dayOfMonth + 3, dayOfMonth + 3);
+        // 添加条件格式Z3-Z14
+        PoiUtil.setConditionalFormattingGreenToRed(sheet, 2, 13, dayOfMonth + 4, dayOfMonth + 4);
+        // 添加条件格式AB3-AB14
+        PoiUtil.setConditionalFormattingGreenToRed(sheet, 2, 13, dayOfMonth + 6, dayOfMonth + 6);
+
+        // 设置sheet的列宽
+        sheet.setColumnWidth(0, 1600);
+        for (int i = 1; i <= dayOfMonth; i++) {
+            sheet.setColumnWidth(i, 1100);
+        }
+        sheet.setColumnWidth(dayOfMonth + 1, 1700);
+        sheet.setColumnWidth(dayOfMonth + 2, 2100);
+        sheet.setColumnWidth(dayOfMonth + 3, 2100);
+        sheet.setColumnWidth(dayOfMonth + 4, 2100);
+        sheet.setColumnWidth(dayOfMonth + 5, 1700);
+        sheet.setColumnWidth(dayOfMonth + 6, 2100);
+        sheet.setColumnWidth(dayOfMonth + 7, 1600);
+
+        // 设置行高 15
+        for (int i = 0; i < rowNum; i++) {
+            sheet.getRow(i).setHeightInPoints(15F);
+        }
+    }
+
+    /**
+     * 获取一个 XSSFWorkbook 并初始化几个需要的格式
+     */
+    private XSSFWorkbookWrapper getWorkbook() {
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        XSSFWorkbookWrapper workbookWrapper = new XSSFWorkbookWrapper();
+        XSSFDataFormat dataFormat = workbook.createDataFormat();
+        workbookWrapper.setWorkbook(workbook);
+        // 基本模式 微软雅黑 10号字 带全边框 水平居中
+        XSSFCellStyle baseStyle = workbook.createCellStyle();
+        XSSFFont font = workbook.createFont();
+        font.setFontName("微软雅黑");
+        font.setFontHeightInPoints((short) 10);
+        baseStyle.setFont(font);
+        baseStyle.setAlignment(HorizontalAlignment.CENTER);
+        baseStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        baseStyle.setBorderBottom(BorderStyle.THIN);
+        baseStyle.setBorderTop(BorderStyle.THIN);
+        baseStyle.setBorderLeft(BorderStyle.THIN);
+        baseStyle.setBorderRight(BorderStyle.THIN);
+        // cellStyle1 基本模式 微软雅黑 10号字 带全边框 水平居中
+        workbookWrapper.setCellStyle1(baseStyle);
+        workbookWrapper.setCellStyle2(baseStyle.copy());
+        workbookWrapper.setCellStyle3(baseStyle.copy());
+        workbookWrapper.setCellStyle4(baseStyle.copy());
+        // 2位小数
+        workbookWrapper.setCellStyle5(baseStyle.copy());
+        // cellStyle2 百分比 2位小数
+        workbookWrapper.getCellStyle2().setDataFormat(dataFormat.getFormat("0.00%"));
+        // cellStyle3 背景色 FFAEAAAA
+        XSSFColor color = new XSSFColor();
+        color.setARGBHex("FFAEAAAA");
+        workbookWrapper.getCellStyle3().setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        workbookWrapper.getCellStyle3().setFillForegroundColor(color);
+        // cellStyle4 背景色 FFAEAAAA 加粗
+        XSSFFont font2 = workbook.createFont();
+        font2.setFontName("微软雅黑");
+        font2.setBold(true);
+        font2.setFontHeightInPoints((short) 10);
+        workbookWrapper.getCellStyle4().setFont(font2);
+        workbookWrapper.getCellStyle4().setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        workbookWrapper.getCellStyle4().setFillForegroundColor(color);
+        // cellStyle5 2位小数
+        workbookWrapper.getCellStyle5().setDataFormat(dataFormat.getFormat("0.00"));
+        return workbookWrapper;
+    }
+}

+ 18 - 22
src/main/java/com/nokia/tsl_data/service/TaskService.java

@@ -13,6 +13,8 @@ import com.nokia.tsl_data.scheduling.service.SchedulingService;
 import com.nokia.tsl_data.util.DateFormatUtil;
 
 import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 
@@ -27,28 +29,22 @@ import java.util.List;
 @Service
 public class TaskService {
 
-    private final TslDataDao tslDataDao;
-    private final TaskRecordRepository taskRecordRepository;
-    private final WorkFlowService workFlowService;
-    private final DataWarehouseService dataWarehouseService;
-    private final MessageService messageService;
-    private final TslReportService tslReportService;
-    private final HighQualityDataService highQualityDataService;
-    private final SchedulingService schedulingService;
-
-    public TaskService(WorkFlowService workFlowService, DataWarehouseService dataWarehouseService,
-            MessageService messageService, TslDataDao tslDataDao, TaskRecordRepository taskRecordRepository,
-            TslReportService tslReportService, HighQualityDataService highQualityDataService,
-            SchedulingService schedulingService) {
-        this.workFlowService = workFlowService;
-        this.dataWarehouseService = dataWarehouseService;
-        this.messageService = messageService;
-        this.tslDataDao = tslDataDao;
-        this.taskRecordRepository = taskRecordRepository;
-        this.tslReportService = tslReportService;
-        this.highQualityDataService = highQualityDataService;
-        this.schedulingService = schedulingService;
-    }
+    @Autowired
+    private TslDataDao tslDataDao;
+    @Autowired
+    private TaskRecordRepository taskRecordRepository;
+    @Autowired
+    private WorkFlowService workFlowService;
+    @Autowired
+    private DataWarehouseService dataWarehouseService;
+    @Autowired
+    private MessageService messageService;
+    @Autowired
+    private ReportServiceV1 tslReportService;
+    @Autowired
+    private HighQualityDataService highQualityDataService;
+    @Autowired
+    private SchedulingService schedulingService;
 
     /**
      * 每日定时任务

+ 28 - 19
src/main/java/com/nokia/tsl_data/service/TslDataService.java

@@ -2,8 +2,10 @@ package com.nokia.tsl_data.service;
 
 import com.nokia.tsl_data.dao.*;
 import com.nokia.tsl_data.entity.TargetTsRatio;
+import com.nokia.tsl_data.exception.LackOfDataRuntimeException;
 import com.nokia.tsl_data.properties.CustomerRateTargetProperties;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.text.ParseException;
@@ -17,25 +19,22 @@ import java.util.Map.Entry;
 @Service
 public class TslDataService {
 
-    private final MobileComplaintMapper mobileComplaintMapper;
-    private final SysDataDictionaryRepository sysDataDictionaryRepository;
-    private final HighQualityCountMapper highQualityCountMapper;
-    private final AvgDurationMapper avgDurationMapper;
-    private final HighQualityCountService highQualityCountService;
-    private final UserCountService userCountService;
-    private final TargetTsRatioRepository targetTsRatioRepository;
-    private final CustomerRateTargetProperties customerRateTargetProperties;
-
-    public TslDataService(MobileComplaintMapper mobileComplaintMapper, SysDataDictionaryRepository sysDataDictionaryRepository, HighQualityCountMapper highQualityCountMapper, AvgDurationMapper avgDurationMapper, HighQualityCountService highQualityCountService, UserCountService userCountService, TargetTsRatioRepository targetTsRatioRepository, CustomerRateTargetProperties customerRateTargetProperties) {
-        this.mobileComplaintMapper = mobileComplaintMapper;
-        this.sysDataDictionaryRepository = sysDataDictionaryRepository;
-        this.highQualityCountMapper = highQualityCountMapper;
-        this.avgDurationMapper = avgDurationMapper;
-        this.highQualityCountService = highQualityCountService;
-        this.userCountService = userCountService;
-        this.targetTsRatioRepository = targetTsRatioRepository;
-        this.customerRateTargetProperties = customerRateTargetProperties;
-    }
+    @Autowired
+    private MobileComplaintMapper mobileComplaintMapper;
+    @Autowired
+    private SysDataDictionaryRepository sysDataDictionaryRepository;
+    @Autowired
+    private HighQualityCountMapper highQualityCountMapper;
+    @Autowired
+    private AvgDurationMapper avgDurationMapper;
+    @Autowired
+    private HighQualityCountService highQualityCountService;
+    @Autowired
+    private UserCountService userCountService;
+    @Autowired
+    private TargetTsRatioRepository targetTsRatioRepository;
+    @Autowired
+    private CustomerRateTargetProperties customerRateTargetProperties;
 
     /**
      * 客户端-投诉问题解决满意度 客户端-投诉问题解决率 客户端-投诉问题响应率
@@ -444,4 +443,14 @@ public class TslDataService {
         }
         return result;
     }
+
+    /**
+     * 按照账期检查数据
+     */
+    public void checkStatDayCount(String day) {
+        int count = highQualityCountMapper.selectCountOfStatDay(day);
+        if (count == 0) {
+            throw new LackOfDataRuntimeException("high_quality_count_day", day);
+        }
+    }
 }

+ 70 - 1
src/main/java/com/nokia/tsl_data/util/excel/poi/PoiUtil.java → src/main/java/com/nokia/tsl_data/util/excel/PoiUtil.java

@@ -1,4 +1,4 @@
-package com.nokia.tsl_data.util.excel.poi;
+package com.nokia.tsl_data.util.excel;
 
 import org.apache.poi.hssf.usermodel.HSSFFont;
 import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
@@ -8,6 +8,7 @@ import org.apache.poi.ss.usermodel.Color;
 import org.apache.poi.ss.usermodel.Font;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.RegionUtil;
 import org.apache.poi.xssf.usermodel.XSSFColor;
 import org.apache.poi.xssf.usermodel.XSSFFont;
 import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
@@ -404,4 +405,72 @@ public class PoiUtil {
             }
         }
     }
+
+    /**
+     * 添加合并单元格并添加边框
+     */
+    public static void addMergedRegion(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol) {
+        CellRangeAddress range = new CellRangeAddress(firstRow, lastRow, firstCol, lastCol);
+        addMergedRegion(sheet, range);
+    }
+
+    /**
+     * 添加合并单元格并添加边框
+     */
+    public static void addMergedRegion(Sheet sheet, CellRangeAddress range) {
+        sheet.addMergedRegion(range);
+        // 设置合并单元格的边框
+        RegionUtil.setBorderBottom(BorderStyle.THIN, range, sheet);
+        RegionUtil.setBorderTop(BorderStyle.THIN, range, sheet);
+        RegionUtil.setBorderLeft(BorderStyle.THIN, range, sheet);
+        RegionUtil.setBorderRight(BorderStyle.THIN, range, sheet);
+    }
+
+    /**
+     * 条件格式 3色渐变 数字小的为绿色,大的为红色
+     * 绿 FF63BE7B 黄 FFFFEB84 红 FFF8696B
+     */
+    public static void setConditionalFormattingGreenToRed(Sheet sheet, int firstRow, int lastRow, int firstCol,
+            int lastCol) {
+        String[] colorStringArray = new String[] { "FF63BE7B", "FFFFEB84", "FFF8696B" };
+        setConditionalFormatting(sheet, firstRow, lastRow, firstCol, lastCol, colorStringArray);
+    }
+
+    /**
+     * 条件格式 3色渐变 数字小的为红色,大的为绿色
+     * 绿 FF63BE7B 黄 FFFFEB84 红 FFF8696B
+     */
+    public static void setConditionalFormattingRedToGreen(Sheet sheet, int firstRow, int lastRow, int firstCol,
+            int lastCol) {
+        String[] colorStringArray = new String[] { "FF63BE7B", "FFFFEB84", "FFF8696B" };
+        setConditionalFormatting(sheet, firstRow, lastRow, firstCol, lastCol, colorStringArray);
+    }
+
+    /**
+     * 条件格式 3色渐变
+     */
+    public static void setConditionalFormatting(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol,
+            String[] colorStringArray) {
+        CellRangeAddress range = new CellRangeAddress(firstRow, lastRow, firstCol, lastCol);
+        setConditionalFormatting(sheet, range, colorStringArray);
+    }
+
+    /**
+     * 条件格式 3色渐变
+     */
+    public static void setConditionalFormatting(Sheet sheet, CellRangeAddress range, String[] colorStringArray) {
+        SheetConditionalFormatting conditionalFormatting = sheet.getSheetConditionalFormatting();
+        ConditionalFormattingRule rule = conditionalFormatting.createConditionalFormattingColorScaleRule();
+        XSSFColor[] colors = new XSSFColor[] {
+                new XSSFColor(), new XSSFColor(), new XSSFColor()
+        };
+        colors[0].setARGBHex(colorStringArray[0]);
+        colors[1].setARGBHex(colorStringArray[1]);
+        colors[2].setARGBHex(colorStringArray[2]);
+        rule.getColorScaleFormatting().setColors(colors);
+        CellRangeAddress[] cellRangeAddresses = new CellRangeAddress[] {
+                range
+        };
+        conditionalFormatting.addConditionalFormatting(cellRangeAddresses, rule);
+    }
 }

+ 1 - 1
src/main/resources/application.yml

@@ -3,7 +3,7 @@ server:
 
 Spring:
   profiles:
-    active: pro
+    active: dev
   jpa:
     hibernate:
       ddl-auto: update

+ 5 - 3
src/test/java/com/nokia/tsl_data/TslDataApplicationTest.java

@@ -1,19 +1,21 @@
 package com.nokia.tsl_data;
 
-import com.nokia.tsl_data.service.TslReportService;
+import java.time.Instant;
 
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import com.nokia.tsl_data.service.WorkFlowService;
+
 @SpringBootTest
 class TslDataApplicationTest {
 
     @Autowired
-    private TslReportService tslReportService;
+    private WorkFlowService workFlowService;
 
     @Test
     void test() {
-        tslReportService.generateReportV3("20231224");
+        workFlowService.updateWorkFlowBasicData(Instant.now().plusSeconds(-60L * 60 * 24), Instant.now());
     }
 }