lifuquan 2 kuukautta sitten
commit
2d2f1497e2
100 muutettua tiedostoa jossa 8560 lisäystä ja 0 poistoa
  1. 24 0
      .gitignore
  2. 5 0
      README.md
  3. BIN
      doc/客户安排的更新/20240711-更新7月以后的服请目标/2024年不满诉求考核测算V7.xlsx
  4. 260 0
      doc/开发环境/2023年适配/2024年适配.md
  5. 925 0
      doc/开发环境/SQL语句备份/tsl_data-bk-20231225.sql
  6. 87 0
      doc/开发环境/SQL语句备份/一些语句.sql
  7. 308 0
      doc/开发环境/任务分析.md
  8. BIN
      doc/数据订阅/客户提供/2024年日报考核指标修改20240106.xlsx
  9. 13 0
      doc/数据订阅/客户提供/abc.txt
  10. BIN
      doc/数据订阅/客户提供/地市目标.xlsx
  11. 67 0
      doc/数据订阅/客户提供/更新用户数等.md
  12. 54 0
      doc/数据订阅/能力商店/河北_CEM移网质量投诉明细/mobile_complaint_day建表.sql
  13. BIN
      doc/数据订阅/能力商店/河北_CEM移网质量投诉明细/河北_CEM移网质量投诉明细_HE_D_MOBILE_COMP.xlsx
  14. 0 0
      doc/数据订阅/能力商店/河北_CEM高品质2日明细/HE_D_HIGH_QUALITY_LIST_DAY_1114200095151828992_20230615.csv
  15. 75 0
      doc/数据订阅/能力商店/河北_CEM高品质2日明细/high_quality_list_day建表.sql
  16. BIN
      doc/数据订阅/能力商店/河北_CEM高品质2日明细/河北客户体验管理高品质日明细.xlsx
  17. BIN
      doc/数据订阅/能力商店/河北_CEM高品质2日明细/高品质2.0日明细接口规范(1).docx
  18. 40 0
      doc/数据订阅/能力商店/河北_CEM高品质2日统计/high_quality_count_day建表.sql
  19. BIN
      doc/数据订阅/能力商店/河北_CEM高品质2日统计/河北_CEM高品质2日统计_HE_D_HIGH_QUALITY.xlsx
  20. BIN
      doc/数据订阅/能力商店/河北_用户常驻基站数据/河北_用户常驻基站数据_018_DWA_S_D_RES_USER_RESIDENT_ALL.xlsx
  21. 238 0
      doc/数据订阅/能力商店/河北客户体验管理智能定责投诉明细月累计接口日/complaint_details_fix_ywd_day表相关语句.md
  22. BIN
      doc/数据订阅/能力商店/河北客户体验管理智能定责投诉明细月累计接口日/河北客户体验管理智能定责投诉明细月累计接口日.xlsx
  23. BIN
      doc/数据订阅/能力商店/河北客户体验管理系统移网网络体验明细日信息/河北客户体验管理系统移网网络体验明细日信息_HE_D_CEM_MOBILE_EXPERIENCE_LIST.xlsx
  24. BIN
      doc/立项材料/钉钉群报表自动化(地市需求)/[河北联通]关于投诉工单日报表自动推送增加区县三率的需求.docx
  25. BIN
      doc/立项材料/钉钉群报表自动化(地市需求)/投诉工单日报表增加县区三率的需求.xlsx
  26. BIN
      doc/立项材料/钉钉群报表自动化/[河北联通]关于投诉工单日报表自动推送的需求.docx
  27. BIN
      doc/立项材料/钉钉群报表自动化/投诉工单日报表自动推送需求工作量.xlsx
  28. BIN
      doc/立项材料/钉钉群报表自动化2024年适配需求-20231225/[河北联通]关于投诉工单日报表2024年适配的需求.docx
  29. 47 0
      doc/部署环境/任务注册-部署环境.http
  30. 70 0
      doc/部署环境/任务调度-部署环境.http
  31. BIN
      doc/部署环境/字段梳理.xlsx
  32. 158 0
      doc/部署环境/手动入库数据.md
  33. 0 0
      doc/部署环境/数据入库-部署环境.http
  34. 34 0
      doc/部署环境/用户更新.md
  35. 94 0
      doc/部署环境/部署环境接口测试-old.md
  36. 5 0
      doc/部署环境/部署记录/20240102部署记录.md
  37. 52 0
      doc/需求文档/20240408/20240408.md
  38. 19 0
      doc/需求文档/20240611提出的需求/20240611提出的需求.md
  39. BIN
      doc/需求文档/20240611提出的需求/2024年移网质量投诉率目标值(1).xlsx
  40. BIN
      doc/需求文档/20240611提出的需求/修改sheetname.png
  41. BIN
      doc/需求文档/20240611提出的需求/目标按这个,无法主被叫的那一项投诉删除,回退到去年的同口径福全6月11日 1611.png
  42. BIN
      doc/需求文档/2024年适配/2024年日报考核指标.xlsx
  43. BIN
      doc/需求文档/2024年适配/2024年适配-工作量.xlsx
  44. 83 0
      doc/需求文档/2024年适配/关于投诉工单日报表2024年适配的需求.md
  45. BIN
      doc/需求文档/用户数/各区县用户数_增加区县编码-12月.xlsx
  46. BIN
      doc/需求文档/用户数/各区县用户数_增加区县编码-4月.xlsx
  47. BIN
      doc/需求文档/用户数/各区县用户数_增加区县编码-5月.xlsx
  48. BIN
      doc/需求文档/用户数/各区县用户数_增加区县编码-6月.xlsx
  49. BIN
      doc/需求文档/用户数/各区县用户数_增加区县编码-8月.xlsx
  50. BIN
      doc/需求文档/输出样本/投诉清单各地市投诉率0322(1).xlsx
  51. BIN
      doc/需求文档/输出样本/投诉清单各地市投诉率20230911.xlsx
  52. BIN
      doc/需求文档/输出样本/投诉清单各地市投诉率20231016.xlsx
  53. 42 0
      doc/需求文档/钉钉自动报表需求.md
  54. 126 0
      pom.xml
  55. 14 0
      src/main/java/com/nokia/tsl_data/TslDataApplication.java
  56. 34 0
      src/main/java/com/nokia/tsl_data/config/RequestLogConfig.java
  57. 24 0
      src/main/java/com/nokia/tsl_data/config/SpringContextHolder.java
  58. 53 0
      src/main/java/com/nokia/tsl_data/config/TslDataConfig.java
  59. 199 0
      src/main/java/com/nokia/tsl_data/controller/DataWarehouseController.java
  60. 44 0
      src/main/java/com/nokia/tsl_data/controller/ReportGenerateController.java
  61. 35 0
      src/main/java/com/nokia/tsl_data/dao/AvgDurationMapper.java
  62. 46 0
      src/main/java/com/nokia/tsl_data/dao/AvgDurationMapperV3.java
  63. 61 0
      src/main/java/com/nokia/tsl_data/dao/CemMobileExperienceListMapper.java
  64. 16 0
      src/main/java/com/nokia/tsl_data/dao/ComplaintDetailsFixYwdDayMapper.java
  65. 128 0
      src/main/java/com/nokia/tsl_data/dao/HighQualityCountMapper.java
  66. 148 0
      src/main/java/com/nokia/tsl_data/dao/HighQualityDataMapper.java
  67. 12 0
      src/main/java/com/nokia/tsl_data/dao/HighQualityDataRepository.java
  68. 47 0
      src/main/java/com/nokia/tsl_data/dao/HighQualityListDayMapper.java
  69. 97 0
      src/main/java/com/nokia/tsl_data/dao/ManagementDetailMapper.java
  70. 183 0
      src/main/java/com/nokia/tsl_data/dao/MobileComplaintMapper.java
  71. 78 0
      src/main/java/com/nokia/tsl_data/dao/SysDataDictionaryRepository.java
  72. 28 0
      src/main/java/com/nokia/tsl_data/dao/TargetTsRatioMapper.java
  73. 25 0
      src/main/java/com/nokia/tsl_data/dao/TaskRecordRepository.java
  74. 221 0
      src/main/java/com/nokia/tsl_data/dao/TslDataDao.java
  75. 23 0
      src/main/java/com/nokia/tsl_data/dao/UserCountRepository.java
  76. 51 0
      src/main/java/com/nokia/tsl_data/dao/WorkFlowBasicDataMapper.java
  77. 291 0
      src/main/java/com/nokia/tsl_data/dao/WorkFlowDao.java
  78. 82 0
      src/main/java/com/nokia/tsl_data/entity/HighQualityData.java
  79. 26 0
      src/main/java/com/nokia/tsl_data/entity/ManagementDetail.java
  80. 53 0
      src/main/java/com/nokia/tsl_data/entity/SysDataDictionary.java
  81. 58 0
      src/main/java/com/nokia/tsl_data/entity/TaskRecord.java
  82. 44 0
      src/main/java/com/nokia/tsl_data/entity/UserCount.java
  83. 42 0
      src/main/java/com/nokia/tsl_data/entity/WorkFlowBasicData.java
  84. 19 0
      src/main/java/com/nokia/tsl_data/entity/converter/JSONObjectConverter.java
  85. 21 0
      src/main/java/com/nokia/tsl_data/entity/pojo/XSSFWorkbookWrapper.java
  86. 61 0
      src/main/java/com/nokia/tsl_data/entity/vo/R.java
  87. 12 0
      src/main/java/com/nokia/tsl_data/exception/LackOfDataRuntimeException.java
  88. 73 0
      src/main/java/com/nokia/tsl_data/properties/TslDataProperties.java
  89. 20 0
      src/main/java/com/nokia/tsl_data/push_message/config/PushMessageConfig.java
  90. 21 0
      src/main/java/com/nokia/tsl_data/push_message/properties/PushMessageProperties.java
  91. 193 0
      src/main/java/com/nokia/tsl_data/push_message/service/PushMessageService.java
  92. 75 0
      src/main/java/com/nokia/tsl_data/scheduling/entity/RunnableTask.java
  93. 149 0
      src/main/java/com/nokia/tsl_data/scheduling/service/TaskScheduleService.java
  94. 138 0
      src/main/java/com/nokia/tsl_data/service/CemMobileExperienceListService.java
  95. 431 0
      src/main/java/com/nokia/tsl_data/service/DataWarehouseService.java
  96. 269 0
      src/main/java/com/nokia/tsl_data/service/HighQualityCountService.java
  97. 219 0
      src/main/java/com/nokia/tsl_data/service/HighQualityDataService.java
  98. 272 0
      src/main/java/com/nokia/tsl_data/service/ManagementDetailService.java
  99. 31 0
      src/main/java/com/nokia/tsl_data/service/MessageService.java
  100. 1567 0
      src/main/java/com/nokia/tsl_data/service/ReportServiceV1.java

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# maven项目编译输出目录
+**/target/
+**/download/
+
+# 日志文件
+**/log/
+**/logs/
+# 上传目录
+**/upload/
+# 输出目录
+**/output/
+
+# vscode
+**/.vscode/
+
+# idea
+**.iml
+**/.idea/
+
+# office临时文件
+**/~$*
+**/.~*
+# draio临时文件
+**/.$*

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+# tsl-data
+
+nohup java -jar /data/tsl-data/tsl-data-exec.jar >/dev/null 2>&1 &
+
+## v2.3 更新20240611提出的需求1

BIN
doc/客户安排的更新/20240711-更新7月以后的服请目标/2024年不满诉求考核测算V7.xlsx


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

@@ -0,0 +1,260 @@
+# 2024年适配
+
+## sheet1 管理端-移网感知类
+
+统计数据在原有基础(河北_CEM移网质量投诉明细全量数据)上增加新订阅的 河北客户体验管理智能定责投诉明细月累计接口日 表中 日定责问题分类(duty_reason_id_day) 字段值为 业务使用>>基础业务使用>>移网主被叫>>手机无法主被叫 的部分。
+
+新建表 management_detail 用于
+
+```sql
+drop table if exists tsl_data.management_detail;
+CREATE TABLE tsl_data.management_detail (
+  id bigserial NOT NULL,
+  origin varchar(100) NOT NULL,
+  stat_day_id varchar(8) NOT NULL,
+  acct_date varchar(8) NOT NULL,
+  sheet_no varchar(150) NOT NULL,
+  compl_area_local varchar(300) NOT NULL,
+  compl_city_local varchar(300) NULL,
+  create_time timestamp NULL DEFAULT now(),
+  CONSTRAINT management_detail_pkey PRIMARY KEY (id)
+)
+```
+
+从 mobile_complaint_day 表中同步账期数据到 management_detail 河北_CEM移网质量投诉明细
+
+```sql
+insert into tsl_data.management_detail (origin, stat_day_id, acct_date, sheet_no, compl_area_local, compl_city_local)
+select 'mobile_complaint_day' as origin, concat(month_id, day_id) as stat_day_id, acct_date, sheet_no, compl_area_local, gis_city as compl_city_local
+from tsl_data.mobile_complaint_day mcd 
+where month_id = '202312' and day_id = '24'
+```
+
+从 complaint_details_fix_ywd_day 表中同步账期数据到 management_detail 河北客户体验管理智能定责投诉明细月累计接口日
+
+```sql
+insert into tsl_data.management_detail (origin, stat_day_id, acct_date, sheet_no, compl_area_local, compl_city_local)
+select 'complaint_details_fix_ywd_day' as origin, concat(month_id, day_id) as stat_day_id, concat(month_id1, day_id1) as acct_date, sheet_no, compl_area_local, compl_city_local
+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
+
+回单超时?
+
+回单未超时?
+
+### 超时工单
+
+```sql
+with t1 as (select sdd.real_name as city,kfsn, to_timestamp(kf_file_time, 'yyyy-mm-dd hh24:mi:ss') - to_timestamp(kd_accept_time, 'yyyy-mm-dd hh24:mi:ss') > interval '36hours' as is_timeout
+from tsl_data.work_flow_basic_data wfbd, tsl_data.sys_data_dictionary sdd 
+where kf_file_time is not null
+and re_is_status_id != '06678b79185349b5bf0c24490a978fbb'
+and wfbd.city_id = sdd.nick_code and to_timestamp(kd_accept_time, 'yyyy-mm-dd hh24:mi:ss') > to_timestamp('2023-12-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')),
+t2 as (select '全省' as city, count(1) as total_num, count(is_timeout or null) as timeout_num from t1),
+t3 as (select city, count(1) as total_num, count(is_timeout or null) as timeout_num from t1 group by city)
+select * from t2 union select* from t3
+```
+
+### 平均处理时长
+
+```sql
+with t1 as (select sdd.real_name,kfsn, (extract(epoch from to_timestamp(kf_file_time, 'YYYY-MM-DD HH24:MI:SS'))-extract(epoch from to_timestamp(kd_accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 as cost_time
+from tsl_data.work_flow_basic_data wfbd, tsl_data.sys_data_dictionary sdd 
+where wfbd.city_id = sdd.nick_code and kf_file_time is not null and re_is_status_id != '06678b79185349b5bf0c24490a978fbb'
+and to_timestamp(wfbd.kd_accept_time, 'yyyy-mm-dd hh24:mi:ss') > to_timestamp('2023-12-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
+select real_name as city, avg(cost_time) as avg_cost
+from t1 group by real_name order by real_name
+```
+
+## 接口测试
+
+### 1. 报表生成
+
+```http
+POST http://192.168.10.7:22222/tsl_data/report/generate/v3
+Content-Type: application/json
+
+20240118
+```
+
+### 2. 数据入库
+
+### 2.1 mobile_complaint
+
+```http
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/mobile_complaint
+Content-Type: application/json
+
+20240117
+```
+
+### 2.2 high_quality_count
+
+```http
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/high_quality_count
+Content-Type: application/json
+
+20240117
+```
+
+### 2.3 high_quality_list
+
+```http
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/high_quality_list
+Content-Type: application/json
+
+20240117
+```
+
+### 2.4 complaint_details_fix_ywd
+
+```http
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/complaint_details_fix_ywd
+Content-Type: application/json
+
+20240117
+```
+
+### 2.5 management_detail
+
+```http
+POST http://192.168.10.7:22222/tsl_data/source/update/management_detail
+Content-Type: application/json
+
+20240117
+```
+
+### 2.6 highQualityData
+
+```http
+POST http://192.168.10.7:22222/tsl_data/source/highQualityData/generate
+Content-Type: application/json
+
+20240117
+```
+
+### 2.7 work_flow_basic_data
+
+```http
+POST http://192.168.10.7:22222/tsl_data/source/update/work_flow_basic_data
+Content-Type: application/json
+
+```
+
+### 2.8 cem_mobile_experience_list
+
+```http
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/cem_mobile_experience_list
+Content-Type: application/json
+
+20240117
+```
+
+## 取消sheet2客户端考核, 新增 服务请求压降
+
+```sql
+create table tsl_data.cem_mobile_experience_list (
+  id bigserial NOT null,
+  month_id varchar(6) null,
+  day_id varchar(2) null,
+  service_id varchar(300) null,
+  contact_id varchar(300) null,
+  create_time varchar(8) null,
+  mobile_no varchar(300) null,
+  service_type_name1 varchar(300) null,
+  pro_name varchar(30) null,
+  code_cust_area varchar(30) null,
+  zyx varchar(300) null,
+  lwly_name varchar(300) null,
+  big_type_name varchar(300) null,
+  small_type_name varchar(300) null,
+  acct_month varchar(24) null,
+  day_id1 varchar(6) null,
+  sheet_no varchar(300) null,
+  compl_city_local varchar(300) null,
+  CONSTRAINT cem_mobile_experience_list_pkey PRIMARY KEY (id)
+)
+```

+ 925 - 0
doc/开发环境/SQL语句备份/tsl_data-bk-20231225.sql

@@ -0,0 +1,925 @@
+--
+-- PostgreSQL database dump
+--
+
+-- Dumped from database version 12.10
+-- Dumped by pg_dump version 12.10
+
+SET statement_timeout = 0;
+SET lock_timeout = 0;
+SET idle_in_transaction_session_timeout = 0;
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = on;
+SELECT pg_catalog.set_config('search_path', '', false);
+SET check_function_bodies = false;
+SET xmloption = content;
+SET client_min_messages = warning;
+SET row_security = off;
+
+--
+-- Name: tsl_data; Type: SCHEMA; Schema: -; Owner: postgres
+--
+
+CREATE SCHEMA tsl_data;
+
+
+ALTER SCHEMA tsl_data OWNER TO postgres;
+
+SET default_tablespace = '';
+
+SET default_table_access_method = heap;
+
+--
+-- Name: avg_duration; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.avg_duration (
+    id bigint NOT NULL,
+    month_id character varying(6),
+    city_name character varying(10),
+    avg_duration double precision NOT NULL,
+    cteate_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.avg_duration OWNER TO postgres;
+
+--
+-- Name: TABLE avg_duration; Type: COMMENT; Schema: tsl_data; Owner: postgres
+--
+
+COMMENT ON TABLE tsl_data.avg_duration IS '地市_月_平均处理时长';
+
+
+--
+-- Name: avg_duration_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE SEQUENCE tsl_data.avg_duration_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE tsl_data.avg_duration_id_seq OWNER TO postgres;
+
+--
+-- Name: avg_duration_id_seq; Type: SEQUENCE OWNED BY; Schema: tsl_data; Owner: postgres
+--
+
+ALTER SEQUENCE tsl_data.avg_duration_id_seq OWNED BY tsl_data.avg_duration.id;
+
+
+--
+-- Name: cron_task_record; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.cron_task_record (
+    id bigint NOT NULL,
+    create_date timestamp without time zone,
+    end_status character varying(255),
+    end_time timestamp without time zone,
+    last_update_date timestamp without time zone,
+    start_time timestamp without time zone,
+    task_info text,
+    task_name character varying(255),
+    time_cost bigint
+);
+
+
+ALTER TABLE tsl_data.cron_task_record OWNER TO postgres;
+
+--
+-- Name: cron_task_record_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE tsl_data.cron_task_record ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
+    SEQUENCE NAME tsl_data.cron_task_record_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1
+);
+
+
+--
+-- Name: high_quality_count_day; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.high_quality_count_day (
+    id bigint NOT NULL,
+    month_id character varying(6) NOT NULL,
+    day_id character varying(2) NOT NULL,
+    acct_date character varying(30) NOT NULL,
+    businoareaname character varying(15) NOT NULL,
+    profes_dep character varying(15) NOT NULL,
+    big_type_name character varying(60) NOT NULL,
+    small_type_name character varying(120) NOT NULL,
+    total_complaints character varying(30),
+    hotline_complaints character varying(30),
+    other_complaint character varying(30),
+    litigation_volume character varying(30),
+    satisfaction_rate character varying(30),
+    satisfaction_count character varying(30),
+    total_evaluation character varying(30),
+    complaint_satisfied character varying(30),
+    complaint_satisfied_list character varying(30),
+    complaint_satisfied_count character varying(30),
+    complaint_resolution character varying(30),
+    complaint_resolution_list character varying(30),
+    complaint_resolution_count character varying(30),
+    complaint_response character varying(30),
+    complaint_response_list character varying(30),
+    complaint_response_count character varying(30),
+    complaint character varying(30),
+    fault_satisfaction_rate character varying(30),
+    fault_satisfaction_list character varying(30),
+    fault_satisfaction_count character varying(30),
+    fault_resolution_rate character varying(30),
+    fault_resolution_list character varying(30),
+    fault_resolution_count character varying(30),
+    fault_response_rate character varying(30),
+    fault_response_list character varying(30),
+    fault_response_count character varying(30),
+    cteate_time timestamp without time zone DEFAULT now()
+);
+
+
+ALTER TABLE tsl_data.high_quality_count_day OWNER TO postgres;
+
+--
+-- Name: high_quality_count_day_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE SEQUENCE tsl_data.high_quality_count_day_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE tsl_data.high_quality_count_day_id_seq OWNER TO postgres;
+
+--
+-- Name: high_quality_count_day_id_seq; Type: SEQUENCE OWNED BY; Schema: tsl_data; Owner: postgres
+--
+
+ALTER SEQUENCE tsl_data.high_quality_count_day_id_seq OWNED BY tsl_data.high_quality_count_day.id;
+
+
+--
+-- Name: high_quality_data; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.high_quality_data (
+    id bigint NOT NULL,
+    area_id character varying(50),
+    area_name character varying(50),
+    checked_city character varying(50),
+    checked_region character varying(50),
+    checked_region_reason character varying(50),
+    city_id character varying(50),
+    city_name character varying(50),
+    cp_is_ok character varying(50),
+    cp_satisfaction character varying(50),
+    cp_timely_contact character varying(50),
+    create_date timestamp without time zone,
+    day_id character varying(8) NOT NULL,
+    last_update_date timestamp without time zone,
+    no_visit_tag character varying(50),
+    sheet_no character varying(50) NOT NULL,
+    checked_city_reason character varying(50)
+);
+
+
+ALTER TABLE tsl_data.high_quality_data OWNER TO postgres;
+
+--
+-- Name: high_quality_data_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE tsl_data.high_quality_data ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
+    SEQUENCE NAME tsl_data.high_quality_data_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1
+);
+
+
+--
+-- Name: high_quality_list_day; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.high_quality_list_day (
+    id bigint NOT NULL,
+    month_id character varying(18),
+    day_id character varying(6),
+    contact_id character varying(150),
+    busino_area_name character varying(90),
+    busino_prov_name character varying(90),
+    sheet_no character varying(90),
+    busi_number character varying(120),
+    cust_star_name character varying(30),
+    sheet_type_name character varying(150),
+    serv_type_name character varying(900),
+    last_deal_content character varying(4136),
+    accept_time character varying(150),
+    archived_time character varying(150),
+    data_type_name character varying(300),
+    channel_name character varying(150),
+    profes_dep character varying(300),
+    big_type_name character varying(300),
+    small_type_name character varying(300),
+    is_dispatch_cloud character varying(30),
+    accept_channel_name character varying(300),
+    duty_reason_name character varying(900),
+    duty_major_name character varying(300),
+    is_online_complete character varying(15),
+    is_call_complete character varying(15),
+    is_cusser_complete character varying(15),
+    is_distr_complete character varying(15),
+    caller_number character varying(90),
+    compl_area_name character varying(60),
+    compl_prov_name character varying(60),
+    submit_channel character varying(300),
+    solved_result_desc character varying(4136),
+    cust_level_name character varying(300),
+    busi_type_name character varying(300),
+    urgent_level_name character varying(300),
+    important_type_name character varying(300),
+    is_upgrade character varying(15),
+    actual_total_len character varying(60),
+    nature_actual_total_len character varying(60),
+    cust_satis_desc character varying(300),
+    auto_is_ok character varying(60),
+    auto_cust_satis_desc character varying(300),
+    nonauto_is_ok_name character varying(15),
+    nonauto_cust_satis_desc character varying(150),
+    prod_type_name character varying(300),
+    proc_name character varying(600),
+    merge_satis_desc character varying(60),
+    serv_type_name_new character varying(1500),
+    is_svip_keyman character varying(6),
+    customer_label character varying(120),
+    prov_name character varying(180),
+    area_id character varying(30),
+    area_name character varying(150),
+    city_id character varying(60),
+    city_name character varying(300),
+    grid_id character varying(60),
+    grid_name character varying(300),
+    is_distri_area character varying(15),
+    cp_satisfaction character varying(60),
+    cp_is_ok character varying(60),
+    cp_timely_contact character varying(60),
+    cp_type character varying(300),
+    novisit_tag character varying(30),
+    serv_content character varying(4136),
+    gis_area character varying(60),
+    gis_area_name character varying(60),
+    gis_city character varying(60),
+    gis_city_name character varying(60),
+    use_gis character varying(3),
+    month_date character varying(18),
+    day_date character varying(6)
+);
+
+
+ALTER TABLE tsl_data.high_quality_list_day OWNER TO postgres;
+
+--
+-- Name: high_quality_list_day_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE SEQUENCE tsl_data.high_quality_list_day_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE tsl_data.high_quality_list_day_id_seq OWNER TO postgres;
+
+--
+-- Name: high_quality_list_day_id_seq; Type: SEQUENCE OWNED BY; Schema: tsl_data; Owner: postgres
+--
+
+ALTER SEQUENCE tsl_data.high_quality_list_day_id_seq OWNED BY tsl_data.high_quality_list_day.id;
+
+
+--
+-- Name: mobile_complaint_day; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.mobile_complaint_day (
+    id bigint NOT NULL,
+    month_id character varying(6) NOT NULL,
+    day_id character varying(2) NOT NULL,
+    acct_date character varying(300) NOT NULL,
+    sheet_no character varying(150),
+    is_online_complete character varying(300),
+    contact_no character varying(300),
+    busi_no character varying(300),
+    serv_content character varying(4136),
+    last_deal_content character varying(4136),
+    deal_depart_name character varying(300),
+    deal_opinion character varying(4136),
+    serv_type character varying(600),
+    bus_type character varying(300),
+    duty_reason character varying(600),
+    accept_channel character varying(300),
+    submit_channel character varying(300),
+    compl_area_local character varying(300),
+    duty_major character varying(300),
+    product_name character varying(600),
+    sp_product_code character varying(600),
+    pre_repair_name character varying(300),
+    pre_repair_charges character varying(24),
+    fault_location character varying(300),
+    cust_level character varying(300),
+    satisfaction_in_reply character varying(300),
+    is_ok_in_reply character varying(300),
+    accept_time character varying(19),
+    end_time character varying(19),
+    proce_time character varying(19),
+    cust_area character varying(300),
+    is_cust_serv_complete character varying(300),
+    is_send_sheet_complete character varying(300),
+    is_repeat character varying(300),
+    is_upgrade character varying(300),
+    is_timeout character varying(300),
+    gis_city character varying(300),
+    process_nums character varying(24),
+    deal_depart_name_1 character varying(300),
+    deal_depart_name_2 character varying(300),
+    deal_depart_name_3 character varying(300),
+    first_call_back_time character varying(19),
+    proce_remark character varying(4136),
+    duty_major_day character varying(300),
+    duty_reason_id_day character varying(300),
+    duty_major_month character varying(300),
+    duty_reason_id_month character varying(300),
+    voice_text character varying(4136),
+    cteate_time timestamp without time zone DEFAULT now()
+);
+
+
+ALTER TABLE tsl_data.mobile_complaint_day OWNER TO postgres;
+
+--
+-- Name: mobile_complaint_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE SEQUENCE tsl_data.mobile_complaint_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE tsl_data.mobile_complaint_id_seq OWNER TO postgres;
+
+--
+-- Name: mobile_complaint_id_seq; Type: SEQUENCE OWNED BY; Schema: tsl_data; Owner: postgres
+--
+
+ALTER SEQUENCE tsl_data.mobile_complaint_id_seq OWNED BY tsl_data.mobile_complaint_day.id;
+
+
+--
+-- Name: sys_data_dictionary; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.sys_data_dictionary (
+    id bigint NOT NULL,
+    create_date timestamp without time zone,
+    last_update_date timestamp without time zone,
+    nick_code character varying(50),
+    nick_name character varying(100),
+    real_code character varying(50),
+    real_name character varying(100),
+    type character varying(50),
+    ord integer,
+    parent_id bigint
+);
+
+
+ALTER TABLE tsl_data.sys_data_dictionary OWNER TO postgres;
+
+--
+-- Name: sys_data_dictionary_id_seq1; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE tsl_data.sys_data_dictionary ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
+    SEQUENCE NAME tsl_data.sys_data_dictionary_id_seq1
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1
+);
+
+
+--
+-- Name: target_ts_ratio; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.target_ts_ratio (
+    id bigint NOT NULL,
+    city_name character varying(50) NOT NULL,
+    create_date timestamp without time zone,
+    customer_target_ratio double precision NOT NULL,
+    last_update_date timestamp without time zone,
+    management_target_ratio double precision NOT NULL,
+    month_id character varying(8) NOT NULL
+);
+
+
+ALTER TABLE tsl_data.target_ts_ratio OWNER TO postgres;
+
+--
+-- Name: target_ts_ratio_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE tsl_data.target_ts_ratio ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
+    SEQUENCE NAME tsl_data.target_ts_ratio_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1
+);
+
+
+--
+-- Name: task_record; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.task_record (
+    id bigint NOT NULL,
+    create_date timestamp without time zone,
+    end_status integer,
+    end_time timestamp without time zone,
+    last_update_date timestamp without time zone,
+    start_time timestamp without time zone,
+    task_info text,
+    task_name character varying(255),
+    time_cost bigint
+);
+
+
+ALTER TABLE tsl_data.task_record OWNER TO postgres;
+
+--
+-- Name: task_record_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE tsl_data.task_record ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
+    SEQUENCE NAME tsl_data.task_record_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1
+);
+
+
+--
+-- Name: user_count; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.user_count (
+    id bigint NOT NULL,
+    city_name character varying(50) NOT NULL,
+    create_date timestamp without time zone,
+    customer_user_count double precision,
+    last_update_date timestamp without time zone,
+    management_user_count double precision,
+    month_id character varying(8) NOT NULL
+);
+
+
+ALTER TABLE tsl_data.user_count OWNER TO postgres;
+
+--
+-- Name: user_count_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE tsl_data.user_count ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
+    SEQUENCE NAME tsl_data.user_count_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1
+);
+
+
+--
+-- Name: work_flow_basic_data; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data (
+    id bigint NOT NULL,
+    city_id character varying(50),
+    create_date timestamp without time zone,
+    kfsn character varying(50) NOT NULL,
+    last_update_date timestamp without time zone,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_id_seq; Type: SEQUENCE; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE tsl_data.work_flow_basic_data ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
+    SEQUENCE NAME tsl_data.work_flow_basic_data_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1
+);
+
+
+--
+-- Name: work_flow_basic_data_temp_1703433900116; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703433900116 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703433900116 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703437500106; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703437500106 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703437500106 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703441100143; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703441100143 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703441100143 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703444700110; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703444700110 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703444700110 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703448300101; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703448300101 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703448300101 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703451900113; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703451900113 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703451900113 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703455500188; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703455500188 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703455500188 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703459100110; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703459100110 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703459100110 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703462700104; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703462700104 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703462700104 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703466300104; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703466300104 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703466300104 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703469900114; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703469900114 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703469900114 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703473500109; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703473500109 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703473500109 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703477100107; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703477100107 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703477100107 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703480700107; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703480700107 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703480700107 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703484300146; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703484300146 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703484300146 OWNER TO postgres;
+
+--
+-- Name: work_flow_basic_data_temp_1703487900106; Type: TABLE; Schema: tsl_data; Owner: postgres
+--
+
+CREATE TABLE tsl_data.work_flow_basic_data_temp_1703487900106 (
+    city_id character varying(50),
+    kfsn character varying(50) NOT NULL,
+    region_id character varying(50),
+    work_flow_create_time timestamp without time zone,
+    work_flow_update_time timestamp without time zone
+);
+
+
+ALTER TABLE tsl_data.work_flow_basic_data_temp_1703487900106 OWNER TO postgres;
+
+--
+-- Name: avg_duration id; Type: DEFAULT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.avg_duration ALTER COLUMN id SET DEFAULT nextval('tsl_data.avg_duration_id_seq'::regclass);
+
+
+--
+-- Name: high_quality_count_day id; Type: DEFAULT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.high_quality_count_day ALTER COLUMN id SET DEFAULT nextval('tsl_data.high_quality_count_day_id_seq'::regclass);
+
+
+--
+-- Name: high_quality_list_day id; Type: DEFAULT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.high_quality_list_day ALTER COLUMN id SET DEFAULT nextval('tsl_data.high_quality_list_day_id_seq'::regclass);
+
+
+--
+-- Name: mobile_complaint_day id; Type: DEFAULT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.mobile_complaint_day ALTER COLUMN id SET DEFAULT nextval('tsl_data.mobile_complaint_id_seq'::regclass);
+
+
+--
+-- Name: cron_task_record cron_task_record_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.cron_task_record
+    ADD CONSTRAINT cron_task_record_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: high_quality_count_day high_quality_count_day_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.high_quality_count_day
+    ADD CONSTRAINT high_quality_count_day_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: high_quality_data high_quality_data_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.high_quality_data
+    ADD CONSTRAINT high_quality_data_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: high_quality_list_day high_quality_list_day_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.high_quality_list_day
+    ADD CONSTRAINT high_quality_list_day_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: mobile_complaint_day mobile_complaint_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.mobile_complaint_day
+    ADD CONSTRAINT mobile_complaint_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: user_count month_city_unique; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.user_count
+    ADD CONSTRAINT month_city_unique UNIQUE (month_id, city_name);
+
+
+--
+-- Name: sys_data_dictionary sys_data_dictionary_pkey1; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.sys_data_dictionary
+    ADD CONSTRAINT sys_data_dictionary_pkey1 PRIMARY KEY (id);
+
+
+--
+-- Name: target_ts_ratio target_ts_ratio_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.target_ts_ratio
+    ADD CONSTRAINT target_ts_ratio_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: task_record task_record_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.task_record
+    ADD CONSTRAINT task_record_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: user_count user_count_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.user_count
+    ADD CONSTRAINT user_count_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: work_flow_basic_data work_flow_basic_data_pkey; Type: CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.work_flow_basic_data
+    ADD CONSTRAINT work_flow_basic_data_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: sys_data_dictionary fk77q91mf3guu88esemsn77afkd; Type: FK CONSTRAINT; Schema: tsl_data; Owner: postgres
+--
+
+ALTER TABLE ONLY tsl_data.sys_data_dictionary
+    ADD CONSTRAINT fk77q91mf3guu88esemsn77afkd FOREIGN KEY (parent_id) REFERENCES tsl_data.sys_data_dictionary(id);
+
+
+--
+-- PostgreSQL database dump complete
+--
+

+ 87 - 0
doc/开发环境/SQL语句备份/一些语句.sql

@@ -0,0 +1,87 @@
+-- 重复工单
+with t1 as (select compl_area_local,busi_no from report_auto.he_d_mobile_comp hdmc where month_id = substring('20230420' from 1 for 6) and day_id <= substring('20230420' from 7 for 2)),
+t2 as (select distinct * from t1),
+t3 as (select compl_area_local, count(1) as total_num from t1 group by compl_area_local),
+t4 as (select compl_area_local, count(1) as distinct_num from t2 group by compl_area_local),
+t5 as (select t3.compl_area_local, t3.total_num, t3.total_num - t4.distinct_num as repeat_num, (t3.total_num - t4.distinct_num)/t3.total_num::numeric  as repeat_ratio from T3, t4 where t3.compl_area_local = t4.compl_area_local)
+select '全省' as compl_area_local, sum(total_num) as total_num, sum(repeat_num) as repeat_num, sum(repeat_num) /sum(total_num)::numeric as repeat_ratio from t5
+union select * from t5
+
+-- 超时工单
+with t1 as (select compl_area_local, is_timeout from report_auto.he_d_mobile_comp hdmc where month_id = substring('20230420' from 1 for 6) and day_id <= substring('20230420' from 7 for 2)),
+t2 as (select '全省' as compl_area_local, count(1) as total_num from t1),
+t3 as (select compl_area_local, count(1) as total_num from t1 group by compl_area_local),
+t4 as (select * from t2 union select * from t3),
+t5 as (select compl_area_local from t1 where is_timeout = '是'),
+t7 as (select '全省' as compl_area_local, count(1) as timeout_num from t5),
+t8 as (select compl_area_local, count(1) as timeout_num from t5 group by compl_area_local),
+t9 as (select * from t7 union select * from t8)
+select t4.compl_area_local, t4.total_num, t9.timeout_num, t9.timeout_num/t4.total_num::numeric as timeout_ratio from t4,t9 where t4.compl_area_local = t9.compl_area_local order by t9.timeout_num/t4.total_num::numeric desc
+
+-- 超时时间
+with t1 as (select compl_area_local, case when proce_time != '' then (extract('epoch' from to_timestamp(proce_time, 'YYYY-MM-DD HH24:MI:SS')) - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS')))/3600 when is_online_complete = '是' then 0
+else (extract('epoch' from to_timestamp(end_time, 'YYYY-MM-DD HH24:MI:SS')) - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS')))/3600 end as duration
+from report_auto.he_d_mobile_comp hdmc where month_id = substring('20230420' from 1 for 6) and day_id <= substring('20230420' from 7 for 2))
+select compl_area_local, avg(duration) as avg_duration from t1 group by compl_area_local
+
+-- 客户端统计
+with t1 as (select businoareaname, complaint_satisfied_list::numeric, complaint_satisfied_count::numeric,
+complaint_resolution_list::numeric, complaint_resolution_count::numeric,
+complaint_response_list::numeric, complaint_response_count::numeric from report_auto.he_d_high_quality hdhq 
+where acct_date = '2023-04-20'
+and profes_dep = '网络质量'
+and big_type_name = '移网网络体验'
+and small_type_name = '--')
+select '全省' as businoareaname, 
+sum(complaint_satisfied_list) / sum(complaint_satisfied_count) as complaint_satisfied,
+sum(complaint_resolution_list) / sum(complaint_resolution_count) as complaint_resolution,
+sum(complaint_response_list) / sum(complaint_response_count) as complaint_response from t1 union
+select businoareaname, 
+complaint_satisfied_list / complaint_satisfied_count as complaint_satisfied, 
+complaint_resolution_list / complaint_resolution_count as complaint_resolution, 
+complaint_response_list / complaint_response_count as complaint_response from t1
+
+select month_id, day_id, count(1)
+from tsl_data.high_quality_list_day
+group by month_id, day_id
+order by month_id, day_id;
+
+select month_id, day_id, count(1)
+from tsl_data.mobile_complaint_day
+group by month_id, day_id
+order by month_id, day_id;
+
+select day_id, count(1)
+from tsl_data.high_quality_data
+group by day_id
+order by day_id;
+
+select month_id, city_name, management_target_ts_ratio, target_ts_ratio
+from report_auto.target_ts_ratio
+where month_id = '202312'
+order by city_name;
+
+update report_auto.target_ts_ratio
+set management_target_ts_ratio = 4.10, target_ts_ratio =4.1
+where month_id = '202312'
+  and city_name in ('保定', '秦皇岛', '唐山');
+update report_auto.target_ts_ratio
+set management_target_ts_ratio = 3.90, target_ts_ratio =3.9
+where month_id = '202312'
+  and city_name in ('沧州', '承德', '邯郸', '邢台');
+update report_auto.target_ts_ratio
+set management_target_ts_ratio = 3.40, target_ts_ratio =3.4
+where month_id = '202312'
+  and city_name = '衡水';
+update report_auto.target_ts_ratio
+set management_target_ts_ratio = 4.20, target_ts_ratio =4.2
+where month_id = '202312'
+  and city_name = '廊坊';
+update report_auto.target_ts_ratio
+set management_target_ts_ratio = 4.00, target_ts_ratio =4.0
+where month_id = '202312'
+  and city_name in ('石家庄', '张家口', '全省');
+update report_auto.target_ts_ratio
+set management_target_ts_ratio = 4.90, target_ts_ratio =4.9
+where month_id = '202312'
+  and city_name = '雄安';

+ 308 - 0
doc/开发环境/任务分析.md

@@ -0,0 +1,308 @@
+# 任务特点分析
+
+<https://oapi.dingtalk.com/robot/send?access_token=b2f1424d6119affaacab614b184f043fcd2c73db2651bb86eff29992d66820bf>
+
+## 自动报表 定时任务特点分析
+
+1. 每天运行1次
+2. 以月为边界,每天执行的任务要求前一天的数据必须完整,中间不能间隔
+3. 数据源不是特别稳定
+
+因为第2、3点特点,需要:
+
+1. 运行时先检查前一天的数据入库是否完整,如果完整
+2. 运行时需要检查当天数据是否完备,如果不完备进入循环等待,每个一个时间间隔进行一次检查,每次检查如果缺少数据都需要发送一次提醒。
+
+先搞定第2步,第1个问题暂时未遇到
+
+```java
+/**
+     * 模拟检查等待
+     * 
+     * @throws Exception
+     */
+    @Test
+    void test() throws Exception {
+        String filePath = "D:/src/投诉清单各地市投诉率20230503.xlsx";
+        File file = new File(filePath);
+        while (!file.exists()) {
+            // 发送提醒
+            System.out.println("检查发现:文件" + filePath + "不存在");
+            // 等待5秒后再次检查
+            Thread.sleep(1000 * 5);
+        }
+        // 完成任务
+        System.out.println("done!");
+    }
+```
+
+## 一些sql
+
+- 查询 high_quality_count_day 帐期数据
+
+```sql
+select acct_date, count(1)
+from tsl_data.high_quality_count_day
+group by acct_date
+order by acct_date;
+```
+
+- management_detail
+
+```sql
+select stat_day_id, count(1)
+from tsl_data.management_detail md 
+group by stat_day_id 
+```
+
+## 接口测试记录
+
+```http
+### 任务调度==查询全部
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/list/all
+Content-Type: application/json
+
+### 任务调度==查询正在调度的任务
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/list/scheduled
+Content-Type: application/json
+
+### 任务调度-每天定时出报表
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 211648899235057664
+  },
+  "status": "ON",
+  "scheduledType": "CRON",
+  "scheduledParameter": {
+    "cronExpression": "0 45 13 * * *"
+  },
+  "description": "每天定时出报表"
+}
+
+### 任务调度-每天定时清理临时表
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 211598274090635264
+  },
+  "status": "ON",
+  "scheduledType": "CRON",
+  "scheduledParameter": {
+    "cronExpression": "0 15 14 * * *"
+  },
+  "description": "每天定时清理临时表"
+}
+
+### 任务调度-每小时从工单系统更新数据
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 211598274090635264
+  },
+  "status": "ON",
+  "scheduledType": "CRON",
+  "scheduledParameter": {
+    "cronExpression": "0 5 * * * *"
+  },
+  "description": "每小时从工单系统更新数据"
+}
+
+### 任务注册-查询列表
+POST http://127.0.0.1:22222/common/scheduling/task/registered/list/all
+Content-Type: application/json
+
+### 任务注册-清理临时表
+POST http://127.0.0.1:22222/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+  "beanName": "taskService",
+  "methodName": "tempTableCleanCronTask",
+  "withParameter": false,
+  "description": "清理临时表"
+}
+
+### 任务注册-从工单系统更新数据
+POST http://127.0.0.1:22222/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+  "beanName": "taskService",
+  "methodName": "updateWorkFlowBasicDataTask",
+  "withParameter": false,
+  "description": "从工单系统更新数据"
+}
+
+### 任务注册- 入库-生成报表-截图 单次任务
+POST http://127.0.0.1:22222/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+  "beanName": "taskService",
+  "methodName": "wareHouseTask",
+  "withParameter": true,
+  "description": "入库-生成报表-截图"
+}
+
+### 任务注册- 入库-生成报表-截图 定时任务
+POST http://127.0.0.1:22222/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+  "beanName": "taskService",
+  "methodName": "dailyWarehouseTask",
+  "withParameter": false,
+  "description": "入库-生成报表-截图 定时任务"
+}
+
+```
+
+```http
+### 任务注册-查询列表
+POST http://127.0.0.1:9999/common/scheduling/task/registered/list/all
+Content-Type: application/json
+
+### 任务注册-新增
+POST http://127.0.0.1:9999/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+"beanName": "demoService",
+"methodName": "test1",
+"withParameter": false,
+"description": "测试用..."
+}
+
+### 任务注册-删除
+POST http://127.0.0.1:22222/common/scheduling/task/registered/delete
+Content-Type: application/json
+
+211340469899235328
+```
+
+```http
+### 任务调度==查询全部
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/list/all
+Content-Type: application/json
+
+### 任务调度==查询正在调度的任务
+POST http://127.0.0.1:9999/common/scheduling/task/scheduled/list/scheduled
+Content-Type: application/json
+
+### 任务调度==删除
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/delete
+Content-Type: application/json
+
+211537692419297280
+
+### 任务调度==更新
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/update
+Content-Type: application/json
+
+{
+  "id": 211596024697982976,
+  "registeredTask": {
+    "id": 211593346244808704
+  },
+  "status": "ON",
+  "description": "abc"
+}
+
+### 任务调度==新增 1. 新增马上调度
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 211528578876182528
+  },
+  "status": "ON",
+  "scheduledType": "IMMEDIATELY",
+  "description": "测试用..."
+}
+
+### 2. 新增单次调度
+POST http://127.0.0.1:9999/demo/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 2
+  },
+  "status": "ON",
+  "scheduledType": "ONCE",
+  "methodParameter": "abc",
+  "scheduledParameter": {
+    "startTime": "2023-11-27 17:04:00"
+  }
+}
+
+### 2. 新增单次调度2
+POST http://127.0.0.1:9999/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 213707591296094208
+  },
+  "status": "ON",
+  "scheduledType": "ONCE",
+  "methodParameter": "abc",
+  "scheduledParameter": {
+    "delayOfSeconds": 5
+  }
+}
+
+### 3. 新增定时调度
+POST http://127.0.0.1:22222/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 211593346244808704
+  },
+  "status": "ON",
+  "scheduledType": "CRON",
+  "scheduledParameter": {
+    "cronExpression": "0 15 14 * * *"
+  }
+}
+
+### 4. 新增周期调度
+POST http://127.0.0.1:9999/demo/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 1
+  },
+  "status": "ON",
+  "scheduledType": "INTERVAL",
+  "scheduledParameter": {
+    "periodOfSeconds": 15
+  }
+}
+
+### 5. 新增固定延时调度
+POST http://127.0.0.1:9999/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 213707591296094208
+  },
+  "status": "ON",
+  "scheduledType": "FIXED_DELAY",
+  "scheduledParameter": {
+    "delayOfSeconds": 15
+  }
+}
+
+```

BIN
doc/数据订阅/客户提供/2024年日报考核指标修改20240106.xlsx


+ 13 - 0
doc/数据订阅/客户提供/abc.txt

@@ -0,0 +1,13 @@
+保定	22.7	22.2	19.7	21.0	21.2	21.0	21.0	26.7	20.5	16.5	15.3	18.1
+沧州	20.0	17.4	18.2	21.5	25.6	21.5	21.5	19.3	16.8	15.3	13.7	16.1
+承德	34.5	18.0	21.6	24.5	24.0	24.5	24.5	25.4	19.7	17.0	15.9	21.0
+邯郸	22.8	16.2	17.9	17.9	18.0	17.9	17.9	17.4	16.3	13.6	13.1	20.1
+衡水	17.4	14.7	15.0	17.0	16.4	17.0	17.0	17.4	13.3	11.2	10.0	13.2
+廊坊	20.5	16.9	19.5	20.5	21.5	20.5	20.5	22.3	17.7	15.2	14.1	16.3
+秦皇岛	23.3	16.8	21.0	20.4	20.6	20.4	20.4	20.1	18.3	15.8	16.9	23.2
+石家庄	23.6	23.0	24.7	25.7	23.1	25.7	25.7	24.6	21.7	17.7	17.6	21.8
+唐山	25.3	19.5	21.4	19.8	20.8	19.8	19.8	22.0	19.7	16.8	15.6	20.2
+邢台	20.4	14.6	15.6	16.0	18.2	16.0	16.0	17.4	16.0	13.5	13.0	17.8
+雄安	15.5	14.3	14.9	17.5	18.2	17.5	17.5	16.8	12.9	10.8	10.8	13.9
+张家口	20.3	14.4	16.2	18.9	19.9	18.9	18.9	19.7	15.9	13.2	12.0	15.3
+全省	22.5	21.1	21.9	22.6	22.8	22.6	22.6	22.4	19.1	16.1	15.2	19.5

BIN
doc/数据订阅/客户提供/地市目标.xlsx


+ 67 - 0
doc/数据订阅/客户提供/更新用户数等.md

@@ -0,0 +1,67 @@
+# 更新
+
+## 更新用户数
+
+```java
+@Autowired
+private UserCountService userCountService;
+
+@Test
+void test2() {
+    String path = "/home/lfq/Documents/code/tsl-data/doc/数据订阅/客户提供/abc.txt";
+    List<String> lines = TextUtil.readLines(path);
+    lines.forEach(
+        line -> {
+            String[] split = line.split("\t");
+            userCountService.updateManagementUserCount("202401", split[0], Double.parseDouble(split[2]));
+            System.out.println(Arrays.toString(split));
+        }
+    );
+}
+```
+
+## 更新移网感知万投比
+
+```java
+@Autowired
+private TargetTsRatioMapper targetTsRatioMapper;
+
+@Test
+void test2() {
+    String path = "/home/lfq/Documents/code/tsl-data/doc/数据订阅/客户提供/abc.txt";
+    List<String> lines = TextUtil.readLines(path);
+    lines.forEach(line -> {
+        String[] split = line.split("\t");
+        String city = split[0];
+        for (int i = 1; i < split.length; i++) {
+            String month = "2024" + (i >= 10 ? i : "0" + i);
+            double target = Double.parseDouble(split[i]);
+            System.out.println(city + "==" + month + "==" + target);
+            targetTsRatioMapper.updateManagementTargetRatio(month, city, target);
+        }
+    });
+}
+```
+
+## 更新移网感知服务请求率 service_request_target_ratio
+
+```java
+@Autowired
+private TargetTsRatioMapper targetTsRatioMapper;
+
+@Test
+void test2() {
+    String path = "/home/lfq/Documents/code/tsl-data/doc/数据订阅/客户提供/abc.txt";
+    List<String> lines = TextUtil.readLines(path);
+    lines.forEach(line -> {
+        String[] split = line.split("\t");
+        String city = split[0];
+        for (int i = 1; i < split.length; i++) {
+            String month = "2024" + (i >= 10 ? i : "0" + i);
+            double target = Double.parseDouble(split[i]);
+            System.out.println(city + "==" + month + "==" + target);
+            targetTsRatioMapper.updateServiceRequestTargetRatio(month, city, target);
+        }
+    });
+}
+```

+ 54 - 0
doc/数据订阅/能力商店/河北_CEM移网质量投诉明细/mobile_complaint_day建表.sql

@@ -0,0 +1,54 @@
+-- tsl_data.mobile_complaint
+CREATE TABLE tsl_data.mobile_complaint_day
+(
+    id                     bigserial     NOT NULL,
+    month_id               varchar(6)    NOT NULL,
+    day_id                 varchar(2)    NOT NULL,
+    acct_date              varchar(300)  NOT NULL,
+    sheet_no               varchar(150)  NULL,
+    is_online_complete     varchar(300)  NULL,
+    contact_no             varchar(300)  NULL,
+    busi_no                varchar(300)  NULL,
+    serv_content           varchar(4136) NULL,
+    last_deal_content      varchar(4136) NULL,
+    deal_depart_name       varchar(300)  NULL,
+    deal_opinion           varchar(4136) NULL,
+    serv_type              varchar(600)  NULL,
+    bus_type               varchar(300)  NULL,
+    duty_reason            varchar(600)  NULL,
+    accept_channel         varchar(300)  NULL,
+    submit_channel         varchar(300)  NULL,
+    compl_area_local       varchar(300)  NULL,
+    duty_major             varchar(300)  NULL,
+    product_name           varchar(600)  NULL,
+    sp_product_code        varchar(600)  NULL,
+    pre_repair_name        varchar(300)  NULL,
+    pre_repair_charges     varchar(24)   NULL,
+    fault_location         varchar(300)  NULL,
+    cust_level             varchar(300)  NULL,
+    satisfaction_in_reply  varchar(300)  NULL,
+    is_ok_in_reply         varchar(300)  NULL,
+    accept_time            varchar(19)   NULL,
+    end_time               varchar(19)   NULL,
+    proce_time             varchar(19)   NULL,
+    cust_area              varchar(300)  NULL,
+    is_cust_serv_complete  varchar(300)  NULL,
+    is_send_sheet_complete varchar(300)  NULL,
+    is_repeat              varchar(300)  NULL,
+    is_upgrade             varchar(300)  NULL,
+    is_timeout             varchar(300)  NULL,
+    gis_city               varchar(300)  NULL,
+    process_nums           varchar(24)   NULL,
+    deal_depart_name_1     varchar(300)  NULL,
+    deal_depart_name_2     varchar(300)  NULL,
+    deal_depart_name_3     varchar(300)  NULL,
+    first_call_back_time   varchar(19)   NULL,
+    proce_remark           varchar(4136) NULL,
+    duty_major_day         varchar(300)  NULL,
+    duty_reason_id_day     varchar(300)  NULL,
+    duty_major_month       varchar(300)  NULL,
+    duty_reason_id_month   varchar(300)  NULL,
+    voice_text             varchar(4136) NULL,
+    cteate_time            timestamp     NULL DEFAULT now(),
+    CONSTRAINT mobile_complaint_pkey PRIMARY KEY (id)
+);

BIN
doc/数据订阅/能力商店/河北_CEM移网质量投诉明细/河北_CEM移网质量投诉明细_HE_D_MOBILE_COMP.xlsx


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
doc/数据订阅/能力商店/河北_CEM高品质2日明细/HE_D_HIGH_QUALITY_LIST_DAY_1114200095151828992_20230615.csv


+ 75 - 0
doc/数据订阅/能力商店/河北_CEM高品质2日明细/high_quality_list_day建表.sql

@@ -0,0 +1,75 @@
+-- tsl_data.high_quality_list_day
+create table tsl_data.high_quality_list_day (
+    id bigserial NOT NULL,
+    month_id	varchar(18) NULL,
+    day_id	varchar(6) NULL,
+    contact_id	varchar(150) NULL,
+    busino_area_name	varchar(90) NULL,
+    busino_prov_name	varchar(90) NULL,
+    sheet_no	varchar(90) NULL,
+    busi_number	varchar(120) NULL,
+    cust_star_name	varchar(30) NULL,
+    sheet_type_name	varchar(150) NULL,
+    serv_type_name	varchar(900) NULL,
+    last_deal_content	varchar(4136) NULL,
+    accept_time	varchar(150) NULL,
+    archived_time	varchar(150) NULL,
+    data_type_name	varchar(300) NULL,
+    channel_name	varchar(150) NULL,
+    profes_dep	varchar(300) NULL,
+    big_type_name	varchar(300) NULL,
+    small_type_name	varchar(300) NULL,
+    is_dispatch_cloud	varchar(30) NULL,
+    accept_channel_name	varchar(300) NULL,
+    duty_reason_name	varchar(900) NULL,
+    duty_major_name	varchar(300) NULL,
+    is_online_complete	varchar(15) NULL,
+    is_call_complete	varchar(15) NULL,
+    is_cusser_complete	varchar(15) NULL,
+    is_distr_complete	varchar(15) NULL,
+    caller_number	varchar(90) NULL,
+    compl_area_name	varchar(60) NULL,
+    compl_prov_name	varchar(60) NULL,
+    submit_channel	varchar(300) NULL,
+    solved_result_desc	varchar(4136) NULL,
+    cust_level_name	varchar(300) NULL,
+    busi_type_name	varchar(300) NULL,
+    urgent_level_name	varchar(300) NULL,
+    important_type_name	varchar(300) NULL,
+    is_upgrade	varchar(15) NULL,
+    actual_total_len	varchar(60) NULL,
+    nature_actual_total_len	varchar(60) NULL,
+    cust_satis_desc	varchar(300) NULL,
+    auto_is_ok	varchar(60) NULL,
+    auto_cust_satis_desc	varchar(300) NULL,
+    nonauto_is_ok_name	varchar(15) NULL,
+    nonauto_cust_satis_desc	varchar(150) NULL,
+    prod_type_name	varchar(300) NULL,
+    proc_name	varchar(600) NULL,
+    merge_satis_desc	varchar(60) NULL,
+    serv_type_name_new	varchar(1500) NULL,
+    is_svip_keyman	varchar(6) NULL,
+    customer_label	varchar(120) NULL,
+    prov_name	varchar(180) NULL,
+    area_id	varchar(30) NULL,
+    area_name	varchar(150) NULL,
+    city_id	varchar(60) NULL,
+    city_name	varchar(300) NULL,
+    grid_id	varchar(60) NULL,
+    grid_name	varchar(300) NULL,
+    is_distri_area	varchar(15) NULL,
+    cp_satisfaction	varchar(60) NULL,
+    cp_is_ok	varchar(60) NULL,
+    cp_timely_contact	varchar(60) NULL,
+    cp_type	varchar(300) NULL,
+    novisit_tag	varchar(30) NULL,
+    serv_content	varchar(4136) NULL,
+    gis_area	varchar(60) NULL,
+    gis_area_name	varchar(60) NULL,
+    gis_city	varchar(60) NULL,
+    gis_city_name	varchar(60) NULL,
+    use_gis	varchar(3) NULL,
+    month_date	varchar(18) NULL,
+    day_date	varchar(6) NULL,
+    CONSTRAINT high_quality_list_day_pkey PRIMARY KEY (id)
+)

BIN
doc/数据订阅/能力商店/河北_CEM高品质2日明细/河北客户体验管理高品质日明细.xlsx


BIN
doc/数据订阅/能力商店/河北_CEM高品质2日明细/高品质2.0日明细接口规范(1).docx


+ 40 - 0
doc/数据订阅/能力商店/河北_CEM高品质2日统计/high_quality_count_day建表.sql

@@ -0,0 +1,40 @@
+-- tsl_data.high_quality_count_day
+CREATE TABLE tsl_data.high_quality_count_day
+(
+    id                         bigserial    NOT NULL,
+    month_id                   varchar(6)   NOT NULL,
+    day_id                     varchar(2)   NOT NULL,
+    acct_date                  varchar(30)  NOT NULL,
+    businoareaname             varchar(15)  NOT NULL,
+    profes_dep                 varchar(15)  NOT NULL,
+    big_type_name              varchar(60)  NOT NULL,
+    small_type_name            varchar(120) NOT NULL,
+    total_complaints           varchar(30)  NULL,
+    hotline_complaints         varchar(30)  NULL,
+    other_complaint            varchar(30)  NULL,
+    litigation_volume          varchar(30)  NULL,
+    satisfaction_rate          varchar(30)  NULL,
+    satisfaction_count         varchar(30)  NULL,
+    total_evaluation           varchar(30)  NULL,
+    complaint_satisfied        varchar(30)  NULL,
+    complaint_satisfied_list   varchar(30)  NULL,
+    complaint_satisfied_count  varchar(30)  NULL,
+    complaint_resolution       varchar(30)  NULL,
+    complaint_resolution_list  varchar(30)  NULL,
+    complaint_resolution_count varchar(30)  NULL,
+    complaint_response         varchar(30)  NULL,
+    complaint_response_list    varchar(30)  NULL,
+    complaint_response_count   varchar(30)  NULL,
+    complaint                  varchar(30)  NULL,
+    fault_satisfaction_rate    varchar(30)  NULL,
+    fault_satisfaction_list    varchar(30)  NULL,
+    fault_satisfaction_count   varchar(30)  NULL,
+    fault_resolution_rate      varchar(30)  NULL,
+    fault_resolution_list      varchar(30)  NULL,
+    fault_resolution_count     varchar(30)  NULL,
+    fault_response_rate        varchar(30)  NULL,
+    fault_response_list        varchar(30)  NULL,
+    fault_response_count       varchar(30)  NULL,
+    cteate_time                timestamp    NULL DEFAULT now(),
+    CONSTRAINT high_quality_count_day_pkey PRIMARY KEY (id)
+);

BIN
doc/数据订阅/能力商店/河北_CEM高品质2日统计/河北_CEM高品质2日统计_HE_D_HIGH_QUALITY.xlsx


BIN
doc/数据订阅/能力商店/河北_用户常驻基站数据/河北_用户常驻基站数据_018_DWA_S_D_RES_USER_RESIDENT_ALL.xlsx


+ 238 - 0
doc/数据订阅/能力商店/河北客户体验管理智能定责投诉明细月累计接口日/complaint_details_fix_ywd_day表相关语句.md

@@ -0,0 +1,238 @@
+# complaint_details_fix_ywd_day
+
+## 插入语句
+
+```sql
+INSERT INTO tsl_data.complaint_details_fix_ywd_day
+(id, month_id, day_id, month_id1, day_id1, sheet_no, is_online_complete, contact_no, busi_no, serv_content, last_deal_content, deal_depart_name, deal_user, deal_opinion, complete_user_code, serv_type, bus_type, duty_reason, problem_duty, problem_solving_con, code_urgent_level, code_important_type, cust_name, accept_user, cust_level, accept_channel, submit_channel, compl_area_local, compl_city_local, compl_grid_local, cust_province, accept_time, end_time, cust_area, duty_major, is_call_center_complete, is_cust_serv_complete, is_send_sheet_complete, sp_code, sp_name, is_repeat, is_timeout, duty_dept, proce_user_code, proce_time, proce_depart_name, proce_remark, satisfaction, is_ok, is_ok_in_reply, product_name, pre_repair_name, pre_repair_charges, actual_total_len, timeout_len, timeout_depart_name, sp_product_code, sp_product_name, process_nums, deal_depart_name_1, deal_user_1, sended_user_1, deal_depart_name_2, deal_user_2, sended_user_2, deal_depart_name_3, deal_user_3, sended_user_3, deal_depart_name_end, deal_user_end, sended_depart_name_last, first_call_back_time, duty_reason_id_day, duty_major_day, duty_reason_id_month, duty_major_month, voice_text, fault_location, satisfaction_in_reply, is_upgrade, gis_city, data_source, code_answer_way, success_call_back, last_satisfaction, last_is_ok, design_profess, deal_unit, province_deal_unit, if_direct, is_proself, timely_contact, is_repeat_5times_m, is_repeat_2times_m, is_repeat_2times_2m, is_repeat_2times_3m, create_time)
+VALUES(0, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', now());
+```
+
+## 建表语句
+
+- 一个语句完成
+
+```sql
+CREATE TABLE tsl_data.complaint_details_fix_ywd_day (
+  id bigserial NOT NULL,
+  month_id character varying(6),
+  day_id character varying(6),
+  month_id1 character varying(18),
+  day_id1 character varying(6),
+  sheet_no character varying(150),
+  is_online_complete character varying(6),
+  contact_no character varying(150),
+  busi_no character varying(150),
+  serv_content character varying(4136),
+  last_deal_content character varying(4136),
+  deal_depart_name character varying(300),
+  deal_user character varying(60),
+  deal_opinion character varying(4136),
+  complete_user_code character varying(150),
+  serv_type character varying(600),
+  bus_type character varying(30),
+  duty_reason character varying(600),
+  problem_duty character varying(300),
+  problem_solving_con character varying(300),
+  code_urgent_level character varying(300),
+  code_important_type character varying(300),
+  cust_name character varying(300),
+  accept_user character varying(600),
+  cust_level character varying(300),
+  accept_channel character varying(300),
+  submit_channel character varying(300),
+  compl_area_local character varying(300),
+  compl_city_local character varying(90),
+  compl_grid_local character varying(210),
+  cust_province character varying(300),
+  accept_time character varying(300),
+  end_time character varying(300),
+  cust_area character varying(300),
+  duty_major character varying(300),
+  is_call_center_complete character varying(6),
+  is_cust_serv_complete character varying(6),
+  is_send_sheet_complete character varying(6),
+  sp_code character varying(600),
+  sp_name character varying(600),
+  is_repeat character varying(50),
+  is_timeout character varying(50),
+  duty_dept character varying(300),
+  proce_user_code character varying(60),
+  proce_time character varying(300),
+  proce_depart_name character varying(1500),
+  proce_remark character varying(4136),
+  satisfaction character varying(50),
+  is_ok character varying(50),
+  is_ok_in_reply character varying(300),
+  product_name character varying(600),
+  pre_repair_name character varying(60),
+  pre_repair_charges character varying(60),
+  actual_total_len character varying(60),
+  timeout_len character varying(60),
+  timeout_depart_name character varying(300),
+  sp_product_code character varying(600),
+  sp_product_name character varying(600),
+  process_nums character varying(60),
+  deal_depart_name_1 character varying(300),
+  deal_user_1 character varying(150),
+  sended_user_1 character varying(150),
+  deal_depart_name_2 character varying(300),
+  deal_user_2 character varying(150),
+  sended_user_2 character varying(150),
+  deal_depart_name_3 character varying(300),
+  deal_user_3 character varying(150),
+  sended_user_3 character varying(150),
+  deal_depart_name_end character varying(300),
+  deal_user_end character varying(60),
+  sended_depart_name_last character varying(300),
+  first_call_back_time character varying(60),
+  duty_reason_id_day character varying(300),
+  duty_major_day character varying(50),
+  duty_reason_id_month character varying(300),
+  duty_major_month character varying(50),
+  voice_text character varying(4136),
+  fault_location character varying(500),
+  satisfaction_in_reply character varying(300),
+  is_upgrade character varying(6),
+  gis_city character varying(210),
+  data_source character varying(60),
+  code_answer_way character varying(15),
+  success_call_back character varying(50),
+  last_satisfaction character varying(50),
+  last_is_ok character varying(50),
+  design_profess character varying(300),
+  deal_unit character varying(150),
+  province_deal_unit character varying(150),
+  if_direct character varying(50),
+  is_proself character varying(50),
+  timely_contact character varying(50),
+  is_repeat_5times_m character varying(50),
+  is_repeat_2times_m character varying(50),
+  is_repeat_2times_2m character varying(50),
+  is_repeat_2times_3m character varying(50),
+  create_time timestamp without time zone DEFAULT now(),
+  CONSTRAINT complaint_details_fix_ywd_day_pkey PRIMARY KEY (id)
+)
+```
+
+- 分步建表
+
+```sql
+CREATE TABLE tsl_data.complaint_details_fix_ywd_day (
+  id bigint NOT NULL,
+  month_id character varying(6),
+  day_id character varying(6),
+  month_id1 character varying(18),
+  day_id1 character varying(6),
+  sheet_no character varying(150),
+  is_online_complete character varying(6),
+  contact_no character varying(150),
+  busi_no character varying(150),
+  serv_content character varying(4136),
+  last_deal_content character varying(4136),
+  deal_depart_name character varying(300),
+  deal_user character varying(60),
+  deal_opinion character varying(4136),
+  complete_user_code character varying(150),
+  serv_type character varying(600),
+  bus_type character varying(30),
+  duty_reason character varying(600),
+  problem_duty character varying(300),
+  problem_solving_con character varying(300),
+  code_urgent_level character varying(300),
+  code_important_type character varying(300),
+  cust_name character varying(300),
+  accept_user character varying(600),
+  cust_level character varying(300),
+  accept_channel character varying(300),
+  submit_channel character varying(300),
+  compl_area_local character varying(300),
+  compl_city_local character varying(90),
+  compl_grid_local character varying(210),
+  cust_province character varying(300),
+  accept_time character varying(300),
+  end_time character varying(300),
+  cust_area character varying(300),
+  duty_major character varying(300),
+  is_call_center_complete character varying(6),
+  is_cust_serv_complete character varying(6),
+  is_send_sheet_complete character varying(6),
+  sp_code character varying(600),
+  sp_name character varying(600),
+  is_repeat character varying(50),
+  is_timeout character varying(50),
+  duty_dept character varying(300),
+  proce_user_code character varying(60),
+  proce_time character varying(300),
+  proce_depart_name character varying(1500),
+  proce_remark character varying(4136),
+  satisfaction character varying(50),
+  is_ok character varying(50),
+  is_ok_in_reply character varying(300),
+  product_name character varying(600),
+  pre_repair_name character varying(60),
+  pre_repair_charges character varying(60),
+  actual_total_len character varying(60),
+  timeout_len character varying(60),
+  timeout_depart_name character varying(300),
+  sp_product_code character varying(600),
+  sp_product_name character varying(600),
+  process_nums character varying(60),
+  deal_depart_name_1 character varying(300),
+  deal_user_1 character varying(150),
+  sended_user_1 character varying(150),
+  deal_depart_name_2 character varying(300),
+  deal_user_2 character varying(150),
+  sended_user_2 character varying(150),
+  deal_depart_name_3 character varying(300),
+  deal_user_3 character varying(150),
+  sended_user_3 character varying(150),
+  deal_depart_name_end character varying(300),
+  deal_user_end character varying(60),
+  sended_depart_name_last character varying(300),
+  first_call_back_time character varying(60),
+  duty_reason_id_day character varying(300),
+  duty_major_day character varying(50),
+  duty_reason_id_month character varying(300),
+  duty_major_month character varying(50),
+  voice_text character varying(4136),
+  fault_location character varying(500),
+  satisfaction_in_reply character varying(300),
+  is_upgrade character varying(6),
+  gis_city character varying(210),
+  data_source character varying(60),
+  code_answer_way character varying(15),
+  success_call_back character varying(50),
+  last_satisfaction character varying(50),
+  last_is_ok character varying(50),
+  design_profess character varying(300),
+  deal_unit character varying(150),
+  province_deal_unit character varying(150),
+  if_direct character varying(50),
+  is_proself character varying(50),
+  timely_contact character varying(50),
+  is_repeat_5times_m character varying(50),
+  is_repeat_2times_m character varying(50),
+  is_repeat_2times_2m character varying(50),
+  is_repeat_2times_3m character varying(50),
+  create_time timestamp without time zone DEFAULT now()
+)
+
+-- 创建seq
+CREATE SEQUENCE tsl_data.complaint_details_fix_ywd_day_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+-- 将seq指配给id字段
+ALTER SEQUENCE tsl_data.complaint_details_fix_ywd_day_id_seq OWNED BY tsl_data.complaint_details_fix_ywd_day.id;
+
+-- id 的default值
+ALTER TABLE ONLY tsl_data.complaint_details_fix_ywd_day ALTER COLUMN id SET DEFAULT nextval('tsl_data.complaint_details_fix_ywd_day_id_seq'::regclass);
+
+-- 将id作为主键
+ALTER TABLE ONLY tsl_data.complaint_details_fix_ywd_day
+    ADD CONSTRAINT complaint_details_fix_ywd_day_pkey PRIMARY KEY (id);
+```

BIN
doc/数据订阅/能力商店/河北客户体验管理智能定责投诉明细月累计接口日/河北客户体验管理智能定责投诉明细月累计接口日.xlsx


BIN
doc/数据订阅/能力商店/河北客户体验管理系统移网网络体验明细日信息/河北客户体验管理系统移网网络体验明细日信息_HE_D_CEM_MOBILE_EXPERIENCE_LIST.xlsx


BIN
doc/立项材料/钉钉群报表自动化(地市需求)/[河北联通]关于投诉工单日报表自动推送增加区县三率的需求.docx


BIN
doc/立项材料/钉钉群报表自动化(地市需求)/投诉工单日报表增加县区三率的需求.xlsx


BIN
doc/立项材料/钉钉群报表自动化/[河北联通]关于投诉工单日报表自动推送的需求.docx


BIN
doc/立项材料/钉钉群报表自动化/投诉工单日报表自动推送需求工作量.xlsx


BIN
doc/立项材料/钉钉群报表自动化2024年适配需求-20231225/[河北联通]关于投诉工单日报表2024年适配的需求.docx


+ 47 - 0
doc/部署环境/任务注册-部署环境.http

@@ -0,0 +1,47 @@
+### 任务注册-查询列表
+POST http://192.168.10.7:22222/common/scheduling/task/registered/list/all
+Content-Type: application/json
+
+### 任务注册-清理临时表
+POST http://192.168.10.7:22222/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+  "beanName": "taskService",
+  "methodName": "tempTableCleanCronTask",
+  "withParameter": false,
+  "description": "清理临时表"
+}
+
+### 任务注册-从工单系统更新数据
+POST http://192.168.10.7:22222/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+  "beanName": "taskService",
+  "methodName": "updateWorkFlowBasicDataTask",
+  "withParameter": false,
+  "description": "从工单系统更新数据"
+}
+
+### 任务注册- 入库-生成报表-截图 单次任务
+POST http://192.168.10.7:22222/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+  "beanName": "taskService",
+  "methodName": "wareHouseTask",
+  "withParameter": true,
+  "description": "入库-生成报表-截图"
+}
+
+### 任务注册- 入库-生成报表-截图 定时任务
+POST http://192.168.10.7:22222/common/scheduling/task/registered/add
+Content-Type: application/json
+
+{
+  "beanName": "taskService",
+  "methodName": "dailyWarehouseTask",
+  "withParameter": false,
+  "description": "入库-生成报表-截图 定时任务"
+}

+ 70 - 0
doc/部署环境/任务调度-部署环境.http

@@ -0,0 +1,70 @@
+### 任务调度==查询全部
+POST http://192.168.10.7:22222/common/scheduling/task/scheduled/list/all
+Content-Type: application/json
+
+### 任务调度==查询正在调度的任务
+POST http://192.168.10.7:22222/common/scheduling/task/scheduled/list/scheduled
+Content-Type: application/json
+
+### 修改
+POST http://192.168.10.7:22222/common/scheduling/task/scheduled/update
+Content-Type: application/json
+
+{
+  "id": 212694981268344832,
+  "registeredTask": {
+  "id": 211894487067267072
+  },
+  "status": "ON",
+  "description": "重新调度1201任务"
+}
+
+### 以下3条为日常任务调度
+### 任务调度-每天定时出报表
+POST http://192.168.10.7:22222/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "id": 211895896529571840,
+  "registeredTask": {
+    "id": 211894517467582464
+  },
+  "status": "ON",
+  "scheduledType": "CRON",
+  "scheduledParameter": {
+    "cronExpression": "0 00 15 * * *"
+  },
+  "description": "每天定时出报表"
+}
+
+### 任务调度-每天定时清理临时表
+POST http://192.168.10.7:22222/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 211895428466216960
+  },
+  "status": "ON",
+  "scheduledType": "CRON",
+  "scheduledParameter": {
+    "cronExpression": "0 20 1 * * *"
+  },
+  "description": "每天定时清理临时表"
+}
+
+### 任务调度-每小时从工单系统更新数据
+POST http://192.168.10.7:22222/common/scheduling/task/scheduled/add
+Content-Type: application/json
+
+{
+  "registeredTask": {
+    "id": 211894454016151552
+  },
+  "status": "ON",
+  "scheduledType": "CRON",
+  "scheduledParameter": {
+    "cronExpression": "0 5 * * * *"
+  },
+  "description": "每小时从工单系统更新数据"
+}

BIN
doc/部署环境/字段梳理.xlsx


+ 158 - 0
doc/部署环境/手动入库数据.md

@@ -0,0 +1,158 @@
+# 手动入库数据
+
+## 手动发送
+
+```http
+POST http://192.168.31.10:11111/tsl/task/send HTTP/1.1
+Content-Type:application/json
+
+20231016
+```
+
+> 注意:文件缺失时不需要手动入库和生成报表,当前已经加入了等待,检查的机制。发送的逻辑后续需要修改一下。
+
+## 手动生成报表
+
+```http
+POST http://192.168.10.7:22222/tsl_data/report/generate/v3 HTTP/1.1
+Content-Type:application/json
+
+20241101
+```
+
+```http
+POST http://192.168.10.7:22222/tsl_data/task/redo HTTP/1.1
+Content-Type:application/json
+
+20241101
+```
+
+curl -X POST http://127.0.0.1:22222/tsl_data/report/generate/v3 -H "Content-Type: application/json" -d '"20241031"'
+
+
+curl -X POST http://127.0.0.1:22222/tsl_data/task/redo -H "Content-Type: application/json" -d '"20241031"'
+
+## 数据手动入库
+
+- cem_mobile_experience_list
+
+```http
+### 入库 cem_mobile_experience_list
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/cem_mobile_experience_list HTTP/1.1
+Content-Type:application/json
+
+20241104
+```
+
+curl -X POST http://127.0.0.1:22222/tsl_data/report/generate/v3 -H "Content-Type: application/json" -d '"20241031"'
+
+```http
+### 删除 cem_mobile_experience_list
+POST http://192.168.10.7:22222/tsl_data/source/delete/cem_mobile_experience_list HTTP/1.1
+Content-Type:application/json
+
+20240325
+```
+
+- report_auto.he_d_mobile_comp
+
+```http
+POST http://192.168.10.7:29100/tsl/task/warahouse/mobilecomp HTTP/1.1
+Content-Type:application/json
+
+20230623
+```
+
+- report_auto.he_d_high_quality
+
+```http
+POST http://192.168.10.7:29100/tsl/task/warahouse/highquality HTTP/1.1
+Content-Type:application/json
+
+20231015
+```
+
+## 查询数据
+
+```http
+POST http://192.168.10.7:29100/tsl/task/check/srcfile HTTP/1.1
+Content-Type:application/json
+
+20230815
+```
+
+## 删除数据
+
+- report_auto.he_d_mobile_comp
+
+```http
+POST http://192.168.10.7:29100/tsl/task/delete/mobilecomp HTTP/1.1
+Content-Type:application/json
+
+20230815
+```
+
+- report_auto.he_d_high_quality
+
+```http
+POST http://192.168.10.7:29100/tsl/task/delete/highquality HTTP/1.1
+Content-Type:application/json
+
+20231015
+```
+
+```http
+### 入库 mobile_complaint 数据
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/mobile_complaint
+Content-Type: application/json
+
+20231203
+
+### 删除 mobile_complaint 数据
+POST http://192.168.10.7:22222/tsl_data/source/delete/mobile_complaint
+Content-Type: application/json
+
+20231203
+
+### 入库 high_quality_count 数据
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/high_quality_count
+Content-Type: application/json
+
+20231203
+
+### 删除 high_quality_count 数据
+POST http://192.168.10.7:22222/tsl_data/source/delete/high_quality_count
+Content-Type: application/json
+
+20231203
+
+### 入库 high_quality_list 数据
+POST http://192.168.10.7:22222/tsl_data/source/warehouse/high_quality_list
+Content-Type: application/json
+
+20231203
+
+### 删除 high_quality_list 数据
+POST http://192.168.10.7:22222/tsl_data/source/delete/high_quality_list
+Content-Type: application/json
+
+20231203
+
+### 删除 high_quality_list 数据
+POST http://192.168.10.7:22222/tsl_data/highQualityData/generate
+Content-Type: application/json
+
+20231203
+
+### 删除 high_quality_list 数据
+POST http://192.168.10.7:22222/tsl_data/highQualityData/delete
+Content-Type: application/json
+
+20231203
+
+### 生成报表
+POST http://192.168.10.7:22222/tsl_data/report/generate
+Content-Type: application/json
+
+20231203
+```

+ 0 - 0
doc/部署环境/数据入库-部署环境.http


+ 34 - 0
doc/部署环境/用户更新.md

@@ -0,0 +1,34 @@
+# 用户更新
+
+```java
+@Autowired
+private UserCountService userCountService;
+
+/**
+ * 更新管理端用户数--当前阶段需要修改一下模式
+ */
+@Test
+void test1() throws IOException {
+    String path = "D:/src/管理端用户数.txt";
+    Files.lines(Paths.get(path), StandardCharsets.UTF_8)
+            .forEach(line -> {
+                String[] split = line.split("\t");
+                System.out.println(split[0] + Double.parseDouble(split[2]));
+                userCountService.updateManagementUserCount("202311", split[0], Double.parseDouble(split[2]));
+            });
+}
+
+/**
+ * 更新客户端用户数
+ */
+@Test
+void test2() throws IOException {
+    String path = "D:/src/客户端用户数.txt";
+    Files.lines(Paths.get(path), StandardCharsets.UTF_8)
+            .forEach(line -> {
+                String[] split = line.split("\t");
+                System.out.println(split[0] + Double.parseDouble(split[1]));
+                userCountService.updateCustomerUserCount("202311", split[0], Double.parseDouble(split[1]));
+            });
+}
+```

+ 94 - 0
doc/部署环境/部署环境接口测试-old.md

@@ -0,0 +1,94 @@
+# 接口测试
+
+nohup java -jar tsl_data-1.5-exec.jar >output.log 2>&1 &
+
+nohup java -jar dingtalk_auto-1.1-exec.jar >output.log 2>&1 &
+
+## 手动发送
+
+```http
+POST http://192.168.31.182:11111/tsl/task/send HTTP/1.1
+Content-Type:application/json
+
+20230716
+```
+
+## 数据手动入库
+
+```http
+POST http://192.168.10.7:29100/tsl/task/warahouse/mobilecomp HTTP/1.1
+Content-Type:application/json
+
+20231029
+```
+
+```http
+POST http://192.168.10.7:29100/tsl/task/warahouse/highquality HTTP/1.1
+Content-Type:application/json
+
+20231029
+```
+
+```http
+POST http://192.168.10.7:29100/tsl/task/delete/mobilecomp HTTP/1.1
+Content-Type:application/json
+
+20231029
+```
+
+```http
+POST http://192.168.10.7:29100/tsl/task/delete/highquality HTTP/1.1
+Content-Type:application/json
+
+20231029
+```
+
+## 手动生成报表
+
+```http
+POST http://192.168.10.7:29100/tsl/task/report/generate HTTP/1.1
+Content-Type:application/json
+
+20231029
+```
+
+## 查询数据
+
+```http
+POST http://192.168.10.7:29100/tsl/task/check/srcfile HTTP/1.1
+Content-Type:application/json
+
+20230531
+```
+
+## 定时任务
+
+### 查询定时任务
+
+```http
+POST http://127.0.0.1:29100/tsl/task/cron/status HTTP/1.1
+Content-Type:application/json
+```
+
+### 停止定时任务
+
+```http
+POST http://192.168.10.7:29100/tsl/task/cron/stop HTTP/1.1
+Content-Type:application/json
+```
+
+### 启动定时任务
+
+```http
+POST http://192.168.10.7:29100/tsl/task/cron/start HTTP/1.1
+Content-Type:application/json
+```
+
+### 修改定时表达式
+
+```http
+POST http://127.0.0.1:29100/tsl/task/cron/expression HTTP/1.1
+Content-Type:application/json
+
+0/25 * * * 
+```

+ 5 - 0
doc/部署环境/部署记录/20240102部署记录.md

@@ -0,0 +1,5 @@
+# 20240102部署记录
+
+## 1. 停止report_auto
+
+停止 /data/report_auto/tsl_data 下的任务

+ 52 - 0
doc/需求文档/20240408/20240408.md

@@ -0,0 +1,52 @@
+# 20240408
+
+```sql
+select day_id1, count(1)
+from tsl_data.complaint_details_fix_ywd_day cdfyd 
+where month_id = '202404' and day_id = '06'
+and duty_reason_id_day in ('移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能', '移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简')
+group by day_id1 
+
+select day_id1, count(1)
+from tsl_data.complaint_details_fix_ywd_day cdfyd 
+where month_id = '202403' and day_id = '31'
+and duty_reason_id_day in ('移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能', '移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简')
+group by day_id1 
+
+SELECT acct_date, count(1)
+FROM tsl_data.mobile_complaint_day mcd 
+where month_id = '202403' and day_id = '31'
+and duty_reason_id_day in ('移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能', '移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简')
+group by acct_date   
+
+SELECT acct_date, count(1)
+FROM tsl_data.mobile_complaint_day mcd 
+where month_id = '202403' and day_id = '31'
+and duty_reason_id_day in ('移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能')
+group by acct_date
+order by acct_date 
+
+SELECT acct_date, count(1)
+FROM tsl_data.mobile_complaint_day mcd 
+where month_id = '202403' and day_id = '31'
+and duty_reason_id_day in ('移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能', '移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简')
+group by acct_date
+order by acct_date 
+
+
+select *
+from tsl_data.mobile_complaint_day mcd 
+where sheet_no in ('TS202403011513473716')
+
+select count(1)
+FROM tsl_data.mobile_complaint_day mcd 
+where month_id = '202403' and day_id = '31'
+order by acct_date 
+
+select acct_date, count(1)
+FROM tsl_data.mobile_complaint_day mcd 
+where month_id = '202403' and day_id = '31'
+and duty_reason_id_day = '移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能'
+group by acct_date
+order by acct_date 
+```

+ 19 - 0
doc/需求文档/20240611提出的需求/20240611提出的需求.md

@@ -0,0 +1,19 @@
+# 20240611提出的需求
+
+## 1. 在第一个sheet(管理端-移网感知类), 回退到去年的同口径,即删除无法被叫的投诉
+
+之前的口径变化如下:
+
+*2024年适配需求 统计数据在原有基础(河北_CEM移网质量投诉明细全量数据)上增加新订阅的 河北客户体验管理智能定责投诉明细月累计接口日 表中日定责问题分类(duty_reason_id_day) 字段值为 业务使用>>基础业务使用>>移网主被叫>>手机无法主被叫 的部分。*
+
+在之前修改时,增加了一个新表,用来把以上两个表的内容进行聚合,在新表中标识了数据来源。
+
+本次修改采用不改变流程,在从新表查询数据时添加限制的方式实现。
+
+5月份的目标值不做更改,更改6月之后的目标值。
+
+第一个需求要求尽快实现。
+
+## 2. 三率的计算要做修改,采用详表数据,按照自己的原则计算三率
+
+## 3. 集团对于超时工单会有数据直接生成,后续需要订阅集团超时工单相关数据,并修改超时工单通报数据源

BIN
doc/需求文档/20240611提出的需求/2024年移网质量投诉率目标值(1).xlsx


BIN
doc/需求文档/20240611提出的需求/修改sheetname.png


BIN
doc/需求文档/20240611提出的需求/目标按这个,无法主被叫的那一项投诉删除,回退到去年的同口径福全6月11日 1611.png


BIN
doc/需求文档/2024年适配/2024年日报考核指标.xlsx


BIN
doc/需求文档/2024年适配/2024年适配-工作量.xlsx


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

@@ -0,0 +1,83 @@
+# 关于投诉工单日报表2024年适配的需求
+
+2023年12月19日
+
+## 1. 需求名称
+
+## 2. 适用范围
+
+河北省分公司
+
+## 3. 需求背景
+
+2024年开始由于考核内容有变需要调整日报表以适应新需求
+
+## 4. 名词或术语解释
+
+无
+
+## 5. 应用支撑现状
+
+## 6. 需求预期目标
+
+## 7. 需求描述
+
+### 7.1 管理端-移网感知类调整
+
+1. sheet1 管理端-移网质量类 重新命名为 管理端-移网感知类。
+
+2. 统计数据在原有基础(河北_CEM移网质量投诉明细全量数据)上增加新订阅的 河北客户体验管理智能定责投诉明细月累计接口日 表中 日定责问题分类(duty_reason_id_day) 字段值为 业务使用>>基础业务使用>>移网主被叫>>手机无法主被叫 的部分。
+
+3. sheet3 管理端-重复投诉率 按照第2点中的全量数据进行统计
+
+### 7.2 取消sheet2客户端考核, 新增 服务请求压降
+
+增加服务请求压降:数据源是移网网络体验明细,计算方式同移网感知类(需提供目标值)
+
+> 需要订阅新数据--尚未提供新数据的服务
+
+### 7.3 投诉处理时长、超时工单概况调整
+
+投诉处理时长、超时工单概况:数据源更换成进入TOP的工单(剔除退单),计算各地市的平均处理时长(归档时间-受理时间),如果归档时间缺失(……),()目标值36小时
+
+修改投诉处理时长、超时工单概况处理逻辑:
+
+1. 数据源修改为进入工单流程系统(TOP)的工单。
+    - 排除已退单(re_is_status)状态的工单
+    - 统计范围为所有存在归档时间的工单
+2. 处理时长 = 客服归档时间 - 客服受理时间,处理时长超过36小时为超时。
+3. 月底未回单的工单要计入下月工单--所以应该按归档时间计算工单范围
+
+处理时长,30小时是达标,30-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%
+
+目标值93%,解决率≤90%不得分,90%至93%线性得分。
+
+### 7.5 发送顺序
+
+顺序还是投诉率、三率、超时、时长
+
+### 7.6 支撑地市日报功能
+
+支撑地市发送日报功能,可实现地市更新区县后,
+
+## 8. 业务推荐路线
+
+## 9. 时间要求
+
+期望上线时间为 2024年1月20日

BIN
doc/需求文档/用户数/各区县用户数_增加区县编码-12月.xlsx


BIN
doc/需求文档/用户数/各区县用户数_增加区县编码-4月.xlsx


BIN
doc/需求文档/用户数/各区县用户数_增加区县编码-5月.xlsx


BIN
doc/需求文档/用户数/各区县用户数_增加区县编码-6月.xlsx


BIN
doc/需求文档/用户数/各区县用户数_增加区县编码-8月.xlsx


BIN
doc/需求文档/输出样本/投诉清单各地市投诉率0322(1).xlsx


BIN
doc/需求文档/输出样本/投诉清单各地市投诉率20230911.xlsx


BIN
doc/需求文档/输出样本/投诉清单各地市投诉率20231016.xlsx


+ 42 - 0
doc/需求文档/钉钉自动报表需求.md

@@ -0,0 +1,42 @@
+# 钉钉自动报表需求
+
+## 1. 需求概述
+
+- 订阅能力商店河北\_CEM 高品质 2 日统计\_HE_D_HIGH_QUALITY_COUNT_DAY 和河北\_CEM 移网质量投诉明细\_HE_D_MOBILE_COMPLAINT_DETAILS_DAY 两项数据
+- 每天定时根据要求处理数据,生成日报表
+- 每天定时按照制定的格式将日报表内容发送到指定的钉钉群
+
+## 2. 需求详情
+
+### 2.1 报表发送
+
+报表发送基本上可以定义为一个定时任务,按照指定的 cron 字符串定时执行,一个报表发送的定时任务应包含一个
+
+1. 一项发送任务
+2. 需要接口支持改变在运行的任务及状态
+3. 任务可以由代码写死,每次增加新的任务时直接更新版本。
+4. 本次要发送的任务可以由 cron 字符串(由配置文件提供)、钉钉群 token(由配置文件提供)、要发送的内容路径(内网地址、路径、用户名、密码这些信息直接写死在代码里)定义。
+
+### 2.2 报表生成(独立程序)
+
+#### 2.2.1 数据源
+
+#### 2.2.2 数据入库
+
+#### 2.2.3 最终产出
+
+2023年4月截至23日移动网投诉情况统计:管理端-移网质量类:投诉率:石家庄、承德、雄安、张家口、沧州、衡水、唐山未达到目标值,石家庄、雄安、承德排名靠后;重复投诉率:唐山、沧州、廊坊增长较快。客户端-移网网络体验:投诉问题解决满意率:雄安、张家口、唐山较低,与达标值差距较大;投诉问题解决率:雄安、张家口、唐山较低,与达标值差距较大;投诉问题响应率:雄安、张家口、石家庄较低,与达标值差距较大。投诉处理时长、超时工单概况:超时工单:雄安、石家庄、张家口分公司超时工单占比较高,石家庄、唐山、邯郸超时工单数量较多。平均处理时长:本月相对较长的地市为雄安、石家庄、张家口;与3月比雄安时长增幅较大。
+
+##### 2.2.3.1 客服投诉清单各地市投诉情况
+
+##### 2.2.3.2 重复投诉率
+
+##### 2.2.3.3 超时工单情况
+
+##### 2.2.3.4 平均处理时长
+
+##### 2.2.3.5 投诉问题解决翻译率
+
+##### 2.2.3.6 投诉问题解决率
+
+##### 2.2.3.7 投诉问题相应率

+ 126 - 0
pom.xml

@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.nokia</groupId>
+    <artifactId>tsl-data</artifactId>
+    <version>2.3</version>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-parent</artifactId>
+        <version>2.7.18</version>
+        <relativePath />
+    </parent>
+
+    <properties>
+        <!-- 跳过测试代码 -->
+        <skipTests>true</skipTests>
+        <!-- 指定java的版本 -->
+        <java.version>1.8</java.version>
+        <!-- 文件拷贝时的编码 -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <!-- 指定maven-compiler-plugin的配置属性开始 -->
+        <!-- 编译时的编码 -->
+        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
+        <!-- 指定编译的版本 -->
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <!-- 指定maven-compiler-plugin的配置属性结束 -->
+    </properties>
+
+    <dependencies>
+        <!-- poi -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>5.2.3</version>
+        </dependency>
+        <!-- poi 2007 -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>5.2.3</version>
+        </dependency>
+        <!-- commons-csv -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-csv</artifactId>
+            <version>1.10.0</version>
+        </dependency>
+        <!-- fastjson -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>2.0.40</version>
+        </dependency>
+        <!-- postgresql驱动,版本由spring-boot-parent指定 -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <!-- mybatis-spring-boot-starter -->
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <!-- spring-boot-starter-jdbc -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <!-- spring-boot-starter-data-jpa -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <!-- spring-boot-starter-web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- spring-boot-starter-test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!--lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>annotations</artifactId>
+            <version>3.0.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>tsl-data</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.6.14</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <classifier>exec</classifier>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 14 - 0
src/main/java/com/nokia/tsl_data/TslDataApplication.java

@@ -0,0 +1,14 @@
+package com.nokia.tsl_data;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+@SpringBootApplication
+@EnableJpaAuditing
+public class TslDataApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(TslDataApplication.class, args);
+    }
+}

+ 34 - 0
src/main/java/com/nokia/tsl_data/config/RequestLogConfig.java

@@ -0,0 +1,34 @@
+package com.nokia.tsl_data.config;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import com.nokia.tsl_data.util.logging.RequestLogDispatcherServlet;
+import com.nokia.tsl_data.util.logging.RequestLogHandlerInterceptor;
+
+@Configuration
+public class RequestLogConfig implements WebMvcConfigurer {
+
+    /**
+     * 添加拦截器
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 添加日志拦截器
+        registry.addInterceptor(new RequestLogHandlerInterceptor()).addPathPatterns("/**");
+    }
+
+    /**
+     * 注入自定义的RequestLogDispatcherServlet替代默认的DispatcherServlet
+     */
+    @Bean
+    @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
+    public DispatcherServlet dispatcherServlet() {
+        return new RequestLogDispatcherServlet();
+    }
+}

+ 24 - 0
src/main/java/com/nokia/tsl_data/config/SpringContextHolder.java

@@ -0,0 +1,24 @@
+package com.nokia.tsl_data.config;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * 注入以后可以通过静态方式使用Spring的上下文
+ */
+@Component
+public class SpringContextHolder implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        SpringContextHolder.applicationContext = applicationContext;
+    }
+    
+    public static Object getBean(String beanName) {
+        return applicationContext.getBean(beanName);
+    }
+}

+ 53 - 0
src/main/java/com/nokia/tsl_data/config/TslDataConfig.java

@@ -0,0 +1,53 @@
+package com.nokia.tsl_data.config;
+
+import com.nokia.tsl_data.properties.TslDataProperties;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import javax.sql.DataSource;
+
+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.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@Configuration
+@EnableScheduling
+@EnableConfigurationProperties(TslDataProperties.class)
+public class TslDataConfig {
+
+    private final int poolSize = 3;
+
+    @Bean
+    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
+        return new JdbcTemplate(dataSource);
+    }
+
+    /**
+     * 注入工作流查询专用的workFlowJdbcTemplate
+     * 连接到工作流数据库的jdbcTemplate
+     */
+    @Bean("workFlowJdbcTemplate")
+    public JdbcTemplate workFlowJdbcTemplate() {
+        HikariConfig config = new HikariConfig();
+        config.setDriverClassName("org.postgresql.Driver");
+        config.setJdbcUrl("jdbc:postgresql://192.168.70.136:9999/flow_hb?characterEncoding=utf8");
+        config.setUsername("flow_web");
+        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;
+    }
+}

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

@@ -0,0 +1,199 @@
+package com.nokia.tsl_data.controller;
+
+import com.nokia.tsl_data.entity.vo.R;
+import com.nokia.tsl_data.service.DataWarehouseService;
+import com.nokia.tsl_data.service.HighQualityDataService;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequestMapping("tsl_data/source")
+public class DataWarehouseController {
+
+    @Autowired
+    private DataWarehouseService dataWarehouseService;
+    @Autowired
+    private HighQualityDataService highQualityDataService;
+
+    @PostMapping("highQualityData/generate")
+    public R generateHighQualityData(@RequestBody String day) {
+        try {
+            highQualityDataService.generateHighQualityData(day);
+            return R.ok().message(String.format("HighQualityData 账期 %s 生成成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("HighQualityData 账期 %s 生成失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("highQualityData/delete")
+    public R deleteHighQualityData(@RequestBody String day) {
+        try {
+            highQualityDataService.deleteHighQualityData(day);
+            return R.ok().message(String.format("HighQualityData 账期 %s 删除成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("HighQualityData 账期 %s 删除失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("warehouse/mobile_complaint")
+    public R warehouseMobileComplaint(@RequestBody String day) {
+        try {
+            dataWarehouseService.warehouseMobileComplaintDay(day);
+            return R.ok().message(String.format("管理端详表 MobileComplaint 数据账期 %s 入库成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("管理端详表 MobileComplaint 数据账期 %s 入库失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("delete/mobile_complaint")
+    public R deleteMobileComplaint(@RequestBody String day) {
+        try {
+            int count = dataWarehouseService.deleteMobileComplaintDay(day);
+            return R.ok().message(String.format("管理端详表 MobileComplaint 数据账期 %s 删除 %s 条", day, count));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("管理端详表 MobileComplaint 数据账期 %s 删除失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("warehouse/high_quality_count")
+    public R warehouseHighQualityCount(@RequestBody String day) {
+        try {
+            dataWarehouseService.warehouseHighQualityCountDay(day);
+            return R.ok().message(String.format("客户端统计 HighQualityCount 数据账期 %s 入库成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("客户端统计 HighQualityCount 数据账期 %s 入库失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("delete/high_quality_count")
+    public R deleteHighQualityCount(@RequestBody String day) {
+        try {
+            int count = dataWarehouseService.deleteHighQualityCountDay(day);
+            return R.ok().message(String.format("客户端统计 HighQualityCount 数据账期 %s 删除 %s 条", day, count));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("客户端统计 HighQualityCount 数据账期 %s 删除失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("warehouse/high_quality_list")
+    public R warehouseHighQualityList(@RequestBody String day) {
+        try {
+            dataWarehouseService.warehouseHighQualityListDay(day);
+            return R.ok().message(String.format("客户端详表 HighQualityList 数据账期 %s 入库成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("客户端详表 HighQualityList 数据账期 %s 入库失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("delete/high_quality_list")
+    public R deleteHighQualityList(@RequestBody String day) {
+        try {
+            int count = dataWarehouseService.deleteHighQualityListDay(day);
+            return R.ok().message(String.format("客户端详表 HighQualityList 数据账期 %s 删除 %s 条", day, count));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("客户端详表 HighQualityList 数据账期 %s 删除失败: %s", day, 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()));
+        }
+    }
+
+    @PostMapping("warehouse/cem_mobile_experience_list")
+    public R warehouseCemMobileExperienceList(@RequestBody String day) {
+        try {
+            dataWarehouseService.warehouseCemMobileExperienceList(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/cem_mobile_experience_list")
+    public R deleteCemMobileExperienceList(@RequestBody String day) {
+        try {
+            int count = dataWarehouseService.deleteCemMobileExperienceList(day);
+            return R.ok().message(
+                    String.format("CemMobileExperienceList(河北客户体验管理系统移网网络体验明细日信息) 数据账期 %s 删除 %s 条", day, count));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("CemMobileExperienceList(河北客户体验管理系统移网网络体验明细日信息) 数据账期 %s 删除失败: %s",
+                    day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("update/management_detail")
+    public R updateManagementDetail(@RequestBody String day) {
+        try {
+            dataWarehouseService.updateManagementDetail(day);
+            return R.ok().message(String.format("ManagementDetail 帐期 %s 更新成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("ManagementDetail 帐期 %s 更新失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("delete/management_detail")
+    public R deleteManagementDetail(String day) {
+        try {
+            int count = dataWarehouseService.deleteManagementDetail(day);
+            return R.ok().message(
+                    String.format("客户端详表 ManagementDetail 数据账期 %s 删除 %s 条", day, count));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("客户端详表 ManagementDetail 数据账期 %s 删除失败: %s",
+                    day, e.getMessage()));
+        }
+    }
+}

+ 44 - 0
src/main/java/com/nokia/tsl_data/controller/ReportGenerateController.java

@@ -0,0 +1,44 @@
+package com.nokia.tsl_data.controller;
+
+import com.nokia.tsl_data.entity.vo.R;
+import com.nokia.tsl_data.service.TaskService;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequestMapping("tsl_data/")
+public class ReportGenerateController {
+
+    @Autowired
+    private TaskService taskService;
+    
+    @PostMapping("report/generate/v3")
+    public R generateReport(@RequestBody String day) {
+        try {
+            taskService.generateReportV3(day);
+            return R.ok().message(String.format("v3报表账期 %s 生成成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("v3报表账期 %s 生成失败: %s", day, e.getMessage()));
+        }
+    }
+
+    @PostMapping("task/redo")
+    public R redoTask(@RequestBody String day) {
+        try {
+            taskService.runTaskForDay(day);
+            return R.ok().message(String.format("v3报表账期 %s 生成成功", day));
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return R.error().message(String.format("v3报表账期 %s 生成失败: %s", day, e.getMessage()));
+        }
+    }
+}

+ 35 - 0
src/main/java/com/nokia/tsl_data/dao/AvgDurationMapper.java

@@ -0,0 +1,35 @@
+package com.nokia.tsl_data.dao;
+
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+import java.util.Map;
+
+/* cspell: disable */
+@Mapper
+public interface AvgDurationMapper {
+
+    /**
+     * 插入上月的平均处理时长
+     */
+    @Insert("with t1 as (select compl_area_local, " +
+            "  case when proce_time != '' then (extract('epoch' from to_timestamp(proce_time, 'YYYY-MM-DD HH24:MI:SS')) " +
+            "            - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 " +
+            "       when is_online_complete = '是' then 0 " +
+            "       else (extract('epoch' from to_timestamp(end_time, 'YYYY-MM-DD HH24:MI:SS')) " +
+            "            - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 end as duration " +
+            "from tsl_data.mobile_complaint_day hdmc where month_id = #{month_id} " +
+            "and day_id::float8 = extract('day' from to_timestamp(#{month_id}, 'YYYYMM') + interval '1 month' - interval '1 day')) " +
+            "insert into tsl_data.avg_duration (month_id, city_name, avg_duration) " +
+            "select #{month_id} as month_id, compl_area_local as city_name, avg(duration) as avg_duration " +
+            "from t1 group by compl_area_local ")
+    void insertOldTsDurationForMonth(String monthId);
+
+    /**
+     * 查询历史处理时长
+     */
+    @Select("select city_name, avg_duration from tsl_data.avg_duration where month_id = #{monthId}")
+    List<Map<String, Object>> selectOldTsDurationForMonth(String monthId);
+}

+ 46 - 0
src/main/java/com/nokia/tsl_data/dao/AvgDurationMapperV3.java

@@ -0,0 +1,46 @@
+package com.nokia.tsl_data.dao;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * tsl_data.avg_duration_v3 表
+ */
+/* cspell: disable */
+@Mapper
+public interface AvgDurationMapperV3 {
+
+    /**
+     * 插入上月的平均处理时长
+     */
+    @Deprecated
+    @Insert("with t1 as (select sdd.real_name,kfsn, (extract(epoch from to_timestamp(kf_file_time, 'YYYY-MM-DD HH24:MI:SS'))"
+            + " - extract(epoch from to_timestamp(kd_accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 as cost_time\n"
+            + "from tsl_data.work_flow_basic_data wfbd, tsl_data.sys_data_dictionary sdd \n" + //
+            " where wfbd.city_id = sdd.nick_code and kf_file_time is not null" +
+            " and re_is_status_id != '06678b79185349b5bf0c24490a978fbb'\n"
+            + "and to_timestamp(wfbd.kf_file_time, 'yyyy-mm-dd hh24:mi:ss') >= to_timestamp(concat(#{month_id}, '01000000'), 'yyyymmddhh24miss') "
+            + " and to_timestamp(wfbd.kf_file_time, 'yyyy-mm-dd hh24:mi:ss') <= " +
+            " (to_timestamp(concat(#{month_id}, '01000000'), 'yyyymmddhh24miss') + interval '1 month'))," +
+            " t2 as (select real_name as city, avg(cost_time) as avg_duration\n" + //
+            " from t1 group by real_name order by real_name), " +
+            " t3 as (select '全省' as city, avg(cost_time) as avg_duration from t1)\n" + //
+            " insert into tsl_data.avg_duration_v3 (month_id, city_name, avg_duration) " +
+            " select #{month_id} as month_id, city as city_name, avg_duration from t2" +
+            " union select #{month_id} as month_id, city as city_name, avg_duration from t3")
+    void insertOldTsDurationForMonth(String monthId);
+
+    @Insert("insert into tsl_data.avg_duration_v3 (month_id, city_name, avg_duration) " +
+            "values (#{month_id}, #{city}, #{avg_duration})")
+    void insertOne(Map<String, Object> map);
+
+    /**
+     * 查询历史处理时长
+     */
+    @Select("select city_name, avg_duration from tsl_data.avg_duration_v3 where month_id = #{monthId}")
+    List<Map<String, Object>> selectOldTsDurationForMonth(String monthId);
+}

+ 61 - 0
src/main/java/com/nokia/tsl_data/dao/CemMobileExperienceListMapper.java

@@ -0,0 +1,61 @@
+package com.nokia.tsl_data.dao;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface CemMobileExperienceListMapper {
+
+    @Select("select count(1) from tsl_data.cem_mobile_experience_list where month_id = #{month_id} and day_id = #{day_id}")
+    int countForDay(@Param("month_id") String monthId, @Param("day_id") String dayId);
+
+    default int countForDay(String day) {
+        return countForDay(day.substring(0, 6), day.substring(6));
+    }
+
+    @Select("select day_id1 as day_id , code_cust_area as city, count(1) as num\n" + //
+            " from tsl_data.cem_mobile_experience_list cmel\n" + //
+            " where month_id = #{monthId} and day_id = #{dayId} and code_cust_area != '异地'\n" + //
+            " group by day_id1, code_cust_area\n" + //
+            " order by day_id1, code_cust_area")
+    List<Map<String, Object>> selectCityCountForStat(@Param("monthId") String monthId, @Param("dayId") String dayId);
+
+    default List<Map<String, Object>> selectCityCountForStat(String day) {
+        return selectCityCountForStat(day.substring(0, 6), day.substring(6));
+    }
+
+    @Select("select day_id1 as day_id, count(1) as num \n" + //
+            " from tsl_data.cem_mobile_experience_list cmel\n" + //
+            " where month_id = #{monthId} and day_id = #{dayId} " + //
+            " group by day_id1 " + //
+            " order by day_id1 ")
+    List<Map<String, Object>> selectAllCountForStat(@Param("monthId") String monthId, @Param("dayId") String dayId);
+
+    default List<Map<String, Object>> selectAllCountForStat(String day) {
+        return selectAllCountForStat(day.substring(0, 6), day.substring(6));
+    }
+
+    @Select("select code_cust_area as city, count(1) as num\n" + //
+            " from tsl_data.cem_mobile_experience_list cmel\n" + //
+            " where month_id = #{monthId} and day_id = #{dayId} and code_cust_area != '异地'\n" + //
+            " group by code_cust_area\n" + //
+            " order by code_cust_area")
+    List<Map<String, Object>> selectCityAllForStat(@Param("monthId") String monthId, @Param("dayId") String dayId);
+
+    default List<Map<String, Object>> selectCityAllForStat(String day) {
+        return selectCityAllForStat(day.substring(0, 6), day.substring(6));
+    }
+
+    @Select("select count(1)\n" + //
+            " from tsl_data.cem_mobile_experience_list cmel\n" + //
+            " where month_id = #{monthId} and day_id = #{dayId} ")
+    int selectCountForStat(@Param("monthId") String monthId, @Param("dayId") String dayId);
+
+    default int selectCountForStat(String day) {
+        return selectCountForStat(day.substring(0, 6), day.substring(6));
+    }
+}

+ 16 - 0
src/main/java/com/nokia/tsl_data/dao/ComplaintDetailsFixYwdDayMapper.java

@@ -0,0 +1,16 @@
+package com.nokia.tsl_data.dao;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface ComplaintDetailsFixYwdDayMapper {
+
+    @Select("select count(1) from tsl_data.complaint_details_fix_ywd_day where month_id = #{month_id} and day_id = #{day_id}")
+    int countForDay(@Param("month_id") String monthId, @Param("day_id") String dayId);
+
+    default int countForDay(String day) {
+        return countForDay(day.substring(0, 6), day.substring(6));
+    }
+}

+ 128 - 0
src/main/java/com/nokia/tsl_data/dao/HighQualityCountMapper.java

@@ -0,0 +1,128 @@
+package com.nokia.tsl_data.dao;
+
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 客户端数据表 高质量统计表
+ * 对应主表 tsl_data.high_quality_count_day
+ */
+@Mapper
+public interface HighQualityCountMapper {
+
+        /**
+         * 查询河北_CEM高品质2日统计数据量
+         */
+        @Select("select count(1) from tsl_data.high_quality_count_day where acct_date = #{day} ")
+        int selectQualityCountForDay(String day);
+
+        /**
+         * 查询客户端地市响应率数据 参数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" +
+                        " case when complaint_response_count::float8 + complaint::float8 = 0 then 1 else " +
+                        " complaint_response_list::float8 / (complaint_response_count::float8 + complaint::float8) end as rate "
+                        + " 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" +
+                        "  and profes_dep = '网络质量'\n" +
+                        "  and big_type_name = '移网网络体验'\n" +
+                        "  and small_type_name = '--'")
+        List<Map<String, Object>> selectResponseOfCity(@Param("day_id") String day);
+
+        /**
+         * 查询客户端地市解决率 参数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" +
+                        " case when complaint_resolution_count::float8 + complaint::float8 = 0 then 1 else " +
+                        " complaint_resolution_list::float8 / (complaint_resolution_count::float8 + complaint::float8) end as rate "
+                        + "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" +
+                        "  and profes_dep = '网络质量'\n" +
+                        "  and big_type_name = '移网网络体验'\n" +
+                        "  and small_type_name = '--'")
+        List<Map<String, Object>> selectResolutionOfCity(@Param("day_id") String day);
+
+        /**
+         * 查询客户端地市满意度 参数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"
+                        + " case when complaint_satisfied_count::float8 + complaint::float8 = 0 then 1 else" +
+                        " complaint_satisfied_list::float8 / (complaint_satisfied_count::float8 + complaint::float8) end as rate "
+                        + " 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" +
+                        "  and profes_dep = '网络质量'\n" +
+                        "  and big_type_name = '移网网络体验'\n" +
+                        "  and small_type_name = '--' ")
+        List<Map<String, Object>> selectSatisfiedOfCity(@Param("day_id") String day);
+
+        /**
+         * 客户端-投诉问题解决满意度 客户端-投诉问题解决率 客户端-投诉问题响应率
+         */
+        @Select("with t1 as (select businoareaname, complaint_satisfied_list::float8, complaint_satisfied_count::float8, "
+                        + "complaint_resolution_list::float8, complaint_resolution_count::float8, complaint_response_list::float8, "
+                        + "complaint_response_count::float8, complaint::float8 " +
+                        "from tsl_data.high_quality_count_day 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 "
+                        + "union select businoareaname, " +
+                        "case when (complaint_satisfied_count + complaint) = 0 then 1 " +
+                        "  else complaint_satisfied_list / (complaint_satisfied_count + complaint) end as complaint_satisfied, "
+                        + "case when (complaint_resolution_count + complaint) = 0 then 1 " +
+                        "  else complaint_resolution_list / (complaint_resolution_count + complaint) end as complaint_resolution, "
+                        + "case when (complaint_response_count + complaint) = 0 then 1 " +
+                        "  else complaint_response_list / (complaint_response_count + complaint) end     as complaint_response from t1")
+        List<Map<String, Object>> selectClientRatioForDay(String day);
+
+        /**
+         * 查询某个日期各个地市的客户端总数(总投诉量(办结量))(所得结果是当月0日到当天的一个累加量)
+         */
+        default List<Map<String, Object>> selectTotalComplaintsForDay(String day) {
+                return selectTotalComplaintsForMonthIdAndDay(day.substring(0, 6), day.substring(6));
+        }
+
+        /**
+         * 查询某个日期各个地市的客户端总数(总投诉量(办结量))(所得结果是当月0日到当天的一个累加量)
+         */
+        @Select("select businoareaname, total_complaints from tsl_data.high_quality_count_day " +
+                        "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);
+
+        @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);
+
+        default int deleteHighQualityCountForDay(String day) {
+                return deleteHighQualityCountForMonthIdAndDayId(day.substring(0, 6), day.substring(6));
+        }
+
+        @Select("select count(1) from tsl_data.high_quality_count_day where month_id = #{month_id} and day_id = #{day_id}")
+        int countForDay(@Param("month_id") String monthId, @Param("day_id") String dayId);
+
+        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));
+        }
+}

+ 148 - 0
src/main/java/com/nokia/tsl_data/dao/HighQualityDataMapper.java

@@ -0,0 +1,148 @@
+package com.nokia.tsl_data.dao;
+
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface HighQualityDataMapper {
+
+    /**
+     * 区县投诉问题解决率
+     */
+    @Select("select checked_region as region, count(cp_is_ok = '解决' or null)::float / count(1) as rate " +
+            "from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_is_ok  in ('解决', '未解决') or no_visit_tag = '未回访') " +
+            "group by checked_region ")
+    List<Map<String, Object>> selectRateOfCpIsOkOfRegion(@Param("day_id") String day);
+
+    /**
+     * 区县投诉问题本月累计未解决工单
+     */
+    @Select("select checked_region as region, count(1) as count " +
+            "from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_is_ok  = '未解决' or no_visit_tag = '未回访') " +
+            "group by checked_region ")
+    List<Map<String, Object>> selectCountOfCpNotOkOfRegion(@Param("day_id") String day);
+
+    /**
+     * 区县投诉问题满意度 满意 包括 满意 不满意包括 一般和不满意
+     */
+    @Select("select checked_region as region, count(cp_satisfaction = '满意' or null)::float / count(1) as rate " +
+            "from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_satisfaction  in ('满意', '一般', '不满意') or no_visit_tag = '未回访') " +
+            "group by checked_region ")
+    List<Map<String, Object>> selectRateOfCpSatisfactionOfRegion(@Param("day_id") String day);
+
+    /**
+     * 区县投诉问题本月累计不满意工单 不满意包括 一般和不满意
+     */
+    @Select("select checked_region as region, count(1) as count " +
+            "from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_satisfaction in ('不满意', '一般') or no_visit_tag = '未回访') " +
+            "group by checked_region ")
+    List<Map<String, Object>> selectCountOfCpNotSatisfactionOfRegion(@Param("day_id") String day);
+
+    /**
+     * 区县投诉问题响应率
+     */
+    @Select("select checked_region as region, count(cp_timely_contact = '是' or null)::float / count(1) as rate " +
+            "from tsl_data.high_quality_data hqd where day_id = #{day_id} " +
+            "and (cp_timely_contact in ('是', '否') or no_visit_tag = '未回访') group by checked_region ")
+    List<Map<String, Object>> selectRateOfCpTimelyContactOfRegion(@Param("day_id") String day);
+
+    /**
+     * 获取本月累计未响应工单量,这里不会每个区县都有
+     */
+    @Select("select checked_region as region, count(1) as count from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_timely_contact = '否' or no_visit_tag = '未回访') group by checked_region")
+    List<Map<String, Object>> selectCountOfCpNotTimelyContactOfRegion(@Param("day_id") String day);
+
+    /**
+     * 依据工单流转的信息更新HighQualityData的地市信息
+     */
+    @Update("update tsl_data.high_quality_data t1\n" +
+            "set checked_city = t2.city, checked_city_reason = 'work_flow', last_update_date = now() \n" +
+            "from (select w.kfsn as kfsn, s.real_name as city from tsl_data.work_flow_basic_data w, \n" +
+            " tsl_data.sys_data_dictionary s where s.type = 'work_flow_city' and s.nick_code = w.city_id) t2\n" +
+            "where t2.kfsn = t1.sheet_no and t1.day_id = #{day}")
+    int updateHighQualityDataCityFromWorkFlowBasicData(String day);
+
+    /**
+     * 依据工单流转的信息更新HighQualityData的区县信息
+     */
+    @Update("update tsl_data.high_quality_data t1\n" +
+            "set checked_region  = t2.region, checked_region_reason = 'work_flow', last_update_date = now()\n" +
+            "from (select w.kfsn as kfsn, s.real_name as region from tsl_data.work_flow_basic_data w,\n" +
+            "tsl_data.sys_data_dictionary s where s.type = 'work_flow_region' and s.nick_code = w.region_id) t2\n" +
+            "where t2.kfsn = t1.sheet_no and t1.day_id = #{day}")
+    int updateHighQualityDataRegionFromWorkFlowBasicData(String day);
+
+    /**
+     * 依据高品质工单详表信息更新HighQualityData的地市信息
+     */
+    @Update("update tsl_data.high_quality_data t1\n" +
+            "set checked_city = t2.real_name, checked_city_reason = 'high_quality_list', last_update_date = now()\n" +
+            "from (select nick_code, real_name from tsl_data.sys_data_dictionary where type = 'high_quality_list_city') t2\n" +
+            "where t2.nick_code = t1.area_id and t1.checked_city is null and t1.day_id = #{day}")
+    int updateHighQualityDataCityFromHighQualityListDay(String day);
+
+    /**
+     * 依据高品质工单详表信息更新HighQualityData的区县信息
+     */
+    @Update("update tsl_data.high_quality_data t1\n" +
+            "set checked_region = t2.real_name, checked_region_reason = 'high_quality_list', last_update_date = now()\n" +
+            "from (select nick_code, real_name from tsl_data.sys_data_dictionary where type = 'high_quality_list_region') t2\n" +
+            "where t2.nick_code = t1.city_id and t1.checked_region is null and t1.day_id = #{day}")
+    int updateHighQualityDataRegionFromHighQualityListDay(String day);
+
+    /**
+     * 根据默认规则更新HighQualityData的区县信息
+     */
+    @Update("update tsl_data.high_quality_data t1\n" +
+            "set checked_region = concat(checked_city, '市区'), checked_region_reason = 'default', last_update_date = now()\n" +
+            "where t1.checked_region is null and t1.day_id = #{day}")
+    int updateHighQualityDataRegionFromDefault(String day);
+
+    /**
+     * 按照日期查询区县的响应率
+     */
+    @Select("select checked_city, checked_region, count(cp_timely_contact = '是' or null)::float / count(1) as rate, count(cp_timely_contact = '否' or null) as not_timely_contact_count \n" +
+            "from tsl_data.high_quality_data hqd \n" +
+            "where day_id = #{day} and (cp_timely_contact in ('是', '否') or no_visit_tag = '未回访')\n" +
+            "group by checked_city, checked_region \n" +
+            "order by count(cp_timely_contact = '是' or null)::float / count(1)")
+    List<Map<String, Object>> selectRegionTimelyContactRate(String day);
+
+    /**
+     * 查询某日期月累计未响应工单数量
+     */
+    @Select("select checked_city, checked_region, count(cp_timely_contact = '否' or null) as not_timely_contact_count \n" +
+            "from tsl_data.high_quality_data hqd \n" +
+            "where day_id = #{day} and (cp_timely_contact in ('是', '否') or no_visit_tag = '未回访')\n" +
+            "group by checked_city, checked_region \n" +
+            "order by count(cp_timely_contact = '是' or null)::float / count(1)")
+    List<Map<String, Object>> selectRegionNotTimelyContactCount(String day);
+
+    /**
+     * 按照日期查询区县的满意率
+     */
+    @Select("select checked_city, checked_region, count(1) as total, count(cp_satisfaction in ('满意', '一般') or null) as satisfaction, count(cp_satisfaction in ('满意', '一般') or null)::float / count(1) as rate \n" +
+            "from tsl_data.high_quality_data hqd \n" +
+            "where day_id = #{day} and (cp_satisfaction in ('满意', '一般', '不满意') or no_visit_tag = '未回访')\n" +
+            "group by checked_city, checked_region")
+    List<Map<String, Object>> selectRegionSatisfactionRate(String day);
+
+    /**
+     * 按照日期查询解决率
+     */
+    @Select("select checked_city, checked_region, count(1) as total, count(cp_is_ok = '解决' or null) as is_ok, count(cp_is_ok = '解决' or null)::float / count(1) as rate \n" +
+            "from tsl_data.high_quality_data hqd \n" +
+            "where day_id = #{day} and (cp_is_ok in ('解决', '未解决') or no_visit_tag = '未回访') \n" +
+            "group by checked_city, checked_region")
+    List<Map<String, Object>> selectRegionIsOkRate(String day);
+
+    @Delete("delete from tsl_data.high_quality_data where day_id = #{day} ")
+    int deleteForDay(String day);
+}

+ 12 - 0
src/main/java/com/nokia/tsl_data/dao/HighQualityDataRepository.java

@@ -0,0 +1,12 @@
+package com.nokia.tsl_data.dao;
+
+import com.nokia.tsl_data.entity.HighQualityData;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface HighQualityDataRepository extends JpaRepository<HighQualityData, Long> {
+    void findByDayId(String dayId);
+
+    int countByDayId(String dayId);
+}

+ 47 - 0
src/main/java/com/nokia/tsl_data/dao/HighQualityListDayMapper.java

@@ -0,0 +1,47 @@
+package com.nokia.tsl_data.dao;
+
+import com.nokia.tsl_data.entity.HighQualityData;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * 高品质详表数据库交互
+ */
+@Mapper
+public interface HighQualityListDayMapper {
+
+    @Select("select sheet_no from tsl_data.high_quality_list_day")
+    List<String> findOrderOf(String day);
+
+    /**
+     * 从 tsl_data.high_quality_list_day 表获取符合条件数据
+     */
+    @ResultMap("highQualityDataResultMap")
+    @Select("select concat(month_id, day_id) as day_id, sheet_no, cp_satisfaction, cp_is_ok,"
+            + " cp_timely_contact, cp_timely_contact, area_id, area_name, city_id, city_name,"
+            + " novisit_tag from tsl_data.high_quality_list_day "
+            + " where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)"
+            + " and novisit_tag in ('已回访', '未回访')")
+    List<HighQualityData> selectHighQualityDataOfDay(String day);
+
+    /**
+     * 按照month_id和day_id字段删除数据
+     */
+    @Delete("delete from tsl_data.high_quality_list_day where month_id = #{month_id} and day_id = #{day_id} ")
+    int deleteHighQualityListForMonthIdAndDayId(@Param("month_id") String monthId, @Param("day_id") String dayId);
+
+    /**
+     * 用于删除某天已入库的高品质详表数据
+     */
+    default int deleteHighQualityListForDay(String day) {
+        return deleteHighQualityListForMonthIdAndDayId(day.substring(0, 6), day.substring(6));
+    }
+
+    @Select("select count(1) from tsl_data.high_quality_list_day where month_id = #{month_id} and day_id = #{day_id} ")
+    int countForDay(@Param("month_id") String monthId, @Param("day_id") String dayId);
+
+    default int countForDay(String day) {
+        return countForDay(day.substring(0, 6), day.substring(6));
+    }
+}

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

@@ -0,0 +1,97 @@
+package com.nokia.tsl_data.dao;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface ManagementDetailMapper {
+
+        /**
+         * 投诉清单分日期分地市计数 按账期查询
+         */
+        // 20240611 更新,增加类型限制
+        @Select("select compl_area_local, substring(acct_date from 7 for 2) as day_id, count(1) as num " + //
+                        " from tsl_data.management_detail\r\n" + //
+                        " where stat_day_id = #{day}  and origin = 'mobile_complaint_day' " +
+                        " group by compl_area_local, substring(acct_date from 7 for 2)\r\n" + //
+                        " order by compl_area_local, substring(acct_date from 7 for 2)")
+        List<Map<String, Object>> selectCityTslForMonth(@Param("day") String day);
+
+        /**
+         * 投诉清单全省分日计数 按账期查询
+         */
+        @Select("select substring(acct_date from 7 for 2) as day_id, count(1) as num" + //
+                        " from tsl_data.management_detail\r\n" + //
+                        " where stat_day_id = #{day} and origin = 'mobile_complaint_day' " + //
+                        " group by substring(acct_date from 7 for 2)\r\n" + //
+                        " order by substring(acct_date from 7 for 2)")
+        List<Map<String, Object>> selectAllTslForMonth(@Param("day") String day);
+
+        /**
+         * 投诉清单地市总数 按账期查询
+         */
+        @Select("select compl_area_local, count(1) as num " + //
+                        " from tsl_data.management_detail\r\n" + //
+                        " where stat_day_id = #{day} and origin = 'mobile_complaint_day' " + //
+                        " group by compl_area_local\r\n" + //
+                        " order by compl_area_local")
+        List<Map<String, Object>> selectCityAllForMonth(@Param("day") String day);
+
+        /**
+         * 全省总数 按账期查询
+         */
+        @Select("select count(1) as num\r\n" + //
+                        "from tsl_data.management_detail\r\n" + //
+                        "where stat_day_id = #{day} and origin = 'mobile_complaint_day' ")
+        int selectAllForMonth(@Param("day") String day);
+
+        /**
+         * 从 河北_CEM移网质量投诉明细 mobile_complaint_day 中同步数据
+         */
+        @Insert("insert into tsl_data.management_detail (origin, stat_day_id, acct_date, sheet_no," +
+                        " compl_area_local, compl_city_local, busi_no) " +
+                        "select 'mobile_complaint_day' as origin, concat(month_id, day_id) as stat_day_id," +
+                        " acct_date, sheet_no, compl_area_local, gis_city as compl_city_local, busi_no " +
+                        " from tsl_data.mobile_complaint_day mcd \r\n" +
+                        " where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2) ")
+        int insertFromMobileComplaint(@Param("day") String day);
+
+        /**
+         * 从 河北客户体验管理智能定责投诉明细月累计接口日 complaint_details_fix_ywd_day 同步数据
+         */
+        @Insert("insert into tsl_data.management_detail (origin, stat_day_id, acct_date, sheet_no," +
+                        " compl_area_local, compl_city_local, busi_no) " +
+                        "select 'complaint_details_fix_ywd_day' as origin, concat(month_id, day_id) as stat_day_id," +
+                        " concat(month_id1, day_id1) as acct_date, sheet_no, compl_area_local, compl_city_local, busi_no "
+                        + " from tsl_data.complaint_details_fix_ywd_day cdfyd \r\n" +
+                        " 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(@Param("day") String day);
+
+        /**
+         * 按照账期统计某个账期的数据量
+         */
+        @Select("select count(1) from tsl_data.management_detail where stat_day_id = #{day} ")
+        int selectCountofStatDay(@Param("day") String day);
+
+        @Delete("delete from tsl_data.management_detail where stat_day_id = #{day}")
+        int deleteForStatDay(@Param("day") String day);
+
+        @Select("with t1 as (select compl_area_local, busi_no from tsl_data.management_detail md  \n" + //
+                        " where stat_day_id = #{day}), \n" + //
+                        " t2 as (select distinct * from t1), \n" + //
+                        " t3 as (select compl_area_local, count(1) as total_num from t1 group by compl_area_local), \n"
+                        + " t4 as (select compl_area_local, count(1) as distinct_num from t2 group by compl_area_local), \n"
+                        + " t5 as (select t3.compl_area_local, t3.total_num, t3.total_num - t4.distinct_num::float8 as repeat_num, \n"
+                        + " (t3.total_num - t4.distinct_num) / t3.total_num::float8 as repeat_ratio from T3, t4 \n" + //
+                        " where t3.compl_area_local = t4.compl_area_local) \n" + //
+                        " select '全省' as compl_area_local, sum(total_num) as total_num, sum(repeat_num) as repeat_num, \n"
+                        + " sum(repeat_num) / sum(total_num)::float8 as repeat_ratio from t5 union select * from t5 ")
+        List<Map<String, Object>> selectRepeatTsCountForDay(String day);
+}

+ 183 - 0
src/main/java/com/nokia/tsl_data/dao/MobileComplaintMapper.java

@@ -0,0 +1,183 @@
+package com.nokia.tsl_data.dao;
+
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface MobileComplaintMapper {
+        /**
+         * 统计某天入库工单数量
+         */
+        @Select("select count(1) from tsl_data.mobile_complaint_day " +
+                        "where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)")
+        int selectCompCountForDay(String day);
+
+        /**
+         * 超时工单统计
+         */
+        @Select("with t1 as (select compl_area_local, is_timeout from tsl_data.mobile_complaint_day " +
+                        " where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)), "
+                        +
+                        "t2 as (select '全省' as compl_area_local, count(1) as total_num from t1), " +
+                        "t3 as (select compl_area_local, count(1) as total_num from t1 group by compl_area_local), " +
+                        "t4 as (select * from t2 union select * from t3), " +
+                        "t5 as (select compl_area_local from t1 where is_timeout = '是'), " +
+                        "t7 as (select '全省' as compl_area_local, count(1) as timeout_num from t5), " +
+                        "t8 as (select compl_area_local, count(1) as timeout_num from t5 group by compl_area_local), " +
+                        "t9 as (select * from t7 union select * from t8) " +
+                        "select t4.compl_area_local, t4.total_num, t9.timeout_num, t9.timeout_num / t4.total_num::float8 as timeout_ratio "
+                        +
+                        "from t4, t9 where t4.compl_area_local = t9.compl_area_local")
+        List<Map<String, Object>> selectTimeoutTsCountForDay(String day);
+
+        /**
+         * 处理时长统计
+         */
+        @Select("with t1 as (select compl_area_local, " +
+                        "case when proce_time != '' then (extract('epoch' from to_timestamp( proce_time, 'YYYY-MM-DD HH24:MI:SS'))"
+                        +
+                        "          - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600" +
+                        "     when is_online_complete = '是' then 0" +
+                        "     else (extract('epoch' from to_timestamp(end_time, 'YYYY-MM-DD HH24:MI:SS'))" +
+                        "          - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 "
+                        +
+                        "     end as duration from tsl_data.mobile_complaint_day " +
+                        "where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)) "
+                        +
+                        "select compl_area_local, avg(duration)::float8 as avg_duration from t1 group by compl_area_local")
+        List<Map<String, Object>> selectTsDurationForDay(String day);
+
+        /**
+         * 重复工单
+         */
+        @Select("with t1 as (select compl_area_local, busi_no from tsl_data.mobile_complaint_day " +
+                        " where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)), "
+                        +
+                        "t2 as (select distinct * from t1), " +
+                        "t3 as (select compl_area_local, count(1) as total_num from t1 group by compl_area_local), " +
+                        "t4 as (select compl_area_local, count(1) as distinct_num from t2 group by compl_area_local), "
+                        +
+                        "t5 as (select t3.compl_area_local, t3.total_num, t3.total_num - t4.distinct_num::float8 as repeat_num, "
+                        +
+                        "       (t3.total_num - t4.distinct_num) / t3.total_num::float8 as repeat_ratio from T3, t4 " +
+                        "       where t3.compl_area_local = t4.compl_area_local) " +
+                        "select '全省' as compl_area_local, sum(total_num) as total_num, sum(repeat_num) as repeat_num, "
+                        +
+                        "sum(repeat_num) / sum(total_num)::float8 as repeat_ratio from t5 union select * from t5 ")
+        List<Map<String, Object>> selectRepeatTsCountForDay(String day);
+
+        /**
+         * 投诉清单按日按地市计数
+         */
+        @Select("select compl_area_local, substring(acct_date from 7 for 2) as day_id, count(1) as num " +
+                        " from tsl_data.mobile_complaint_day where month_id = substring(#{day} from 1 for 6) " +
+                        " and day_id = substring(#{day} from 7 for 2) group by compl_area_local, substring(acct_date from 7 for 2) "
+                        +
+                        " order by compl_area_local, substring(acct_date from 7 for 2)")
+        List<Map<String, Object>> selectCityTslForMonth(String day);
+
+        /**
+         * 投诉清单全省计数
+         */
+        @Select("select substring(acct_date from 7 for 2) as day_id, count(1) as num " +
+                        "from tsl_data.mobile_complaint_day where month_id = substring(#{day} from 1 for 6) " +
+                        "and day_id = substring(#{day} from 7 for 2) group by substring(acct_date from 7 for 2) " +
+                        "order by substring(acct_date from 7 for 2)")
+        List<Map<String, Object>> selectAllTslForMonth(String day);
+
+        /**
+         * 投诉清单地市总数
+         */
+        @Select("select compl_area_local, count(1) as num from tsl_data.mobile_complaint_day " +
+                        "where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2) " +
+                        " group by compl_area_local order by compl_area_local")
+        List<Map<String, Object>> selectCityAllForMonth(String day);
+
+        /**
+         * 投诉清单按月计算总数
+         */
+        @Select("select count(1) as num from tsl_data.mobile_complaint_day hdmc " +
+                        "where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)")
+        int selectAllForMonth(String day);
+
+        @Delete("delete from tsl_data.mobile_complaint_day where month_id = #{month_id}  and day_id = #{day_id} ")
+        int deleteMobileComplaintForMonthIdAndDayId(@Param("month_id") String monthId, @Param("day_id") String dayId);
+
+        /**
+         * 删除某天的管理端投诉清单
+         */
+        default int deleteMobileCompForDay(String day) {
+                return deleteMobileComplaintForMonthIdAndDayId(day.substring(0, 6), day.substring(6));
+        }
+
+        @Select("select count(1) from tsl_data.mobile_complaint_day where month_id = #{month_id}  and day_id = #{day_id} ")
+        int countForDay(@Param("month_id") String monthId, @Param("day_id") String dayId);
+
+        default int countForDay(String day) {
+                return countForDay(day.substring(0, 6), day.substring(6));
+        }
+
+        // 3G简网投诉情况
+        // 移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能
+        // 移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简
+        // 日定责问题分类 DUTY_REASON_ID_DAY
+        // 地市按日统计 20240409 新增
+        @Select("select compl_area_local, substring(acct_date from 7 for 2) as day_id, count(1) as num " +
+                        "from tsl_data.mobile_complaint_day " +
+                        "where month_id = #{month_id} and day_id = #{day_id} " +
+                        "and duty_reason_id_day in ( " +
+                        "'移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能', " +
+                        "'移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简') " +
+                        "group by compl_area_local, substring(acct_date from 7 for 2) " +
+                        "order by compl_area_local, substring(acct_date from 7 for 2)")
+        List<Map<String, Object>> selectCity3GjwForMonth(@Param("month_id") String monthId,
+                        @Param("day_id") String dayId);
+
+        default List<Map<String, Object>> selectCity3GjwForMonth(String day) {
+                return selectCity3GjwForMonth(day.substring(0, 6), day.substring(6));
+        }
+
+        // 全省按日统计 20240409 新增
+        @Select("select substring(acct_date from 7 for 2) as day_id, count(1) as num " +
+                        "from tsl_data.mobile_complaint_day " +
+                        "where month_id = #{month_id} and day_id = #{day_id} " +
+                        "and duty_reason_id_day in ( " +
+                        "'移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能', " +
+                        "'移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简') " +
+                        "group by substring(acct_date from 7 for 2) " +
+                        "order by substring(acct_date from 7 for 2)")
+        List<Map<String, Object>> selectAll3GjwGroupByDayForMonth(@Param("month_id") String monthId,
+                        @Param("day_id") String dayId);
+
+        default List<Map<String, Object>> selectAll3GjwGroupByDayForMonth(String day) {
+                return selectAll3GjwGroupByDayForMonth(day.substring(0, 6), day.substring(6));
+        }
+
+        @Select("select compl_area_local, count(1) as num " +
+                        "from tsl_data.mobile_complaint_day " +
+                        "where month_id = #{month_id} and day_id = #{day_id} " +
+                        "and duty_reason_id_day in ( " +
+                        "'移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能', " +
+                        "'移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简') " +
+                        "group by compl_area_local " +
+                        "order by compl_area_local ")
+        List<Map<String, Object>> selectCityAll3GjwForMonth(@Param("month_id") String monthId,
+                        @Param("day_id") String dayId);
+
+        default List<Map<String, Object>> selectCityAll3GjwForMonth(String day) {
+                return selectCityAll3GjwForMonth(day.substring(0, 6), day.substring(6));
+        }
+
+        @Select("select count(1)  from tsl_data.mobile_complaint_day " +
+                        "where month_id = #{month_id} and day_id = #{day_id} " +
+                        "and duty_reason_id_day in ( " +
+                        "'移网质量>>网络覆盖>>Vo-LTE>>未开通/不支撑Vo-LTE功能', " +
+                        "'移网质量>>网络覆盖>>2/3G网络精简>>2/3G网络精简')")
+        int selectAll3GjwForMonth(@Param("month_id") String monthId, @Param("day_id") String dayId);
+
+        default int selectAll3GjwForMonth(String day) {
+                return selectAll3GjwForMonth(day.substring(0, 6), day.substring(6));
+        }
+}

+ 78 - 0
src/main/java/com/nokia/tsl_data/dao/SysDataDictionaryRepository.java

@@ -0,0 +1,78 @@
+package com.nokia.tsl_data.dao;
+
+import com.nokia.tsl_data.entity.SysDataDictionary;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.*;
+
+/* cspell: disable */
+@Repository
+public interface SysDataDictionaryRepository extends JpaRepository<SysDataDictionary, Long> {
+
+    List<SysDataDictionary> findByTypeOrderByOrd(String type);
+
+    SysDataDictionary findByRealNameAndType(String realName, String type);
+    SysDataDictionary findByNickNameAndType(String nickName, String type);
+    SysDataDictionary findByNickCodeAndType(String nickCode, String type);
+
+    default List<String> findAllCityName() {
+        List<String> result = new ArrayList<>();
+        List<SysDataDictionary> dictionaries = findByTypeOrderByOrd("work_flow_city");
+        for (SysDataDictionary dictionary : dictionaries) {
+            result.add(dictionary.getRealName());
+        }
+        return result;
+    }
+
+    default Map<String, String> findWorkFlowCityMap() {
+        Map<String, String> result = new HashMap<>();
+        List<SysDataDictionary> dictionaries = findByTypeOrderByOrd("work_flow_city");
+        for (SysDataDictionary dictionary : dictionaries) {
+            result.put(dictionary.getNickCode(), dictionary.getRealName());
+        }
+        return result;
+    }
+
+    default Set<String> findAllServTypeNames() {
+        Set<String> result = new HashSet<>();
+        List<SysDataDictionary> dictionaries = findByTypeOrderByOrd("serv_type_name");
+        for (SysDataDictionary dictionary : dictionaries) {
+            result.add(dictionary.getRealName());
+        }
+        return result;
+    }
+
+    default Map<String, String> findHighQualityListCityMap() {
+        Map<String, String> result = new HashMap<>();
+        List<SysDataDictionary> cities = findByTypeOrderByOrd("high_quality_list_city");
+        for (SysDataDictionary city : cities) {
+            result.put(city.getNickCode(), city.getRealName());
+        }
+        return result;
+    }
+
+    default Map<String, String> findHighQualityListRegionMap() {
+        Map<String, String> result = new HashMap<>();
+        List<SysDataDictionary> regions = findByTypeOrderByOrd("high_quality_list_region");
+        for (SysDataDictionary region : regions) {
+            result.put(region.getNickCode(), region.getRealName());
+        }
+        return result;
+    }
+
+    /**
+     * 获取全部区县的map key是区县的realName value是地市/区县组成的列表
+     */
+    default Map<String, List<Object>> findAllRegion() {
+        List<SysDataDictionary> regions = findByTypeOrderByOrd("high_quality_list_region");
+        Map<String, List<Object>> result = new HashMap<>();
+        for (SysDataDictionary region : regions) {
+            List<Object> objects = new ArrayList<>();
+            objects.add(region.getParent().getRealName());
+            objects.add(region.getRealName());
+            result.put(region.getRealName(), objects);
+        }
+        return result;
+    }
+}

+ 28 - 0
src/main/java/com/nokia/tsl_data/dao/TargetTsRatioMapper.java

@@ -0,0 +1,28 @@
+package com.nokia.tsl_data.dao;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+@Mapper
+public interface TargetTsRatioMapper {
+
+        @Select("select * from tsl_data.target_ts_ratio where month_id = #{monthId}")
+        List<Map<String, Object>> findByMonthId(String monthId);
+
+        @Update("UPDATE tsl_data.target_ts_ratio\n" + //
+                        " SET management_target_ratio = #{target} \n" + //
+                        " WHERE month_id = #{monthId} and city_name = #{city} ")
+        void updateManagementTargetRatio(@Param("monthId") String monthId,
+                        @Param("city") String city, @Param("target") double target);
+
+        @Update("UPDATE tsl_data.target_ts_ratio\n" + //
+                        " SET service_request_target_ratio = #{target} \n" + //
+                        " WHERE month_id = #{monthId} and city_name = #{city} ")
+        void updateServiceRequestTargetRatio(@Param("monthId") String monthId,
+                        @Param("city") String city, @Param("target") double target);
+}

+ 25 - 0
src/main/java/com/nokia/tsl_data/dao/TaskRecordRepository.java

@@ -0,0 +1,25 @@
+package com.nokia.tsl_data.dao;
+
+import com.nokia.tsl_data.entity.TaskRecord;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.time.Instant;
+import java.util.List;
+
+/*
+ * 20240205 弃用
+ */
+@Repository
+@Deprecated
+public interface TaskRecordRepository extends JpaRepository<TaskRecord, Long> {
+
+    TaskRecord findFirstByTaskNameAndEndStatusOrderByStartTimeDesc(String taskName, Integer endStatus);
+
+    default Instant findLastSuccessUpdateWorkFlowBasicDataTaskEndInstant() {
+        TaskRecord taskRecord = findFirstByTaskNameAndEndStatusOrderByStartTimeDesc("更新工作流数据", 0);
+        return taskRecord.getTaskInfo().getInstant("end");
+    }
+
+    List<TaskRecord> findTop2ByOrderByStartTimeDesc();
+}

+ 221 - 0
src/main/java/com/nokia/tsl_data/dao/TslDataDao.java

@@ -0,0 +1,221 @@
+package com.nokia.tsl_data.dao;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * tslData 直接使用jdbcTemplate完成的操作
+ */
+/* cSpell: disable */
+@Component
+public class TslDataDao {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    public TslDataDao(JdbcTemplate jdbcTemplate) {
+        this.jdbcTemplate = jdbcTemplate;
+    }
+
+    /**
+     * 创建临时表
+     * 20240205 不再使用
+     */
+    @Deprecated
+    public void createTempTableOfWorkFlowBasicData(String tempTableName) {
+        String sql = "CREATE TABLE %s (  " + //
+                " order_no varchar(50) not null,  " + //
+                " is_effctive 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.update(String.format(sql, tempTableName));
+    }
+
+    /**
+     * 删除临时表
+     * 20240205 不再使用
+     */
+    @Deprecated
+    public void dropTempTable(String tempTableName) {
+        if (!tempTableName.contains("_temp_")) {
+            throw new RuntimeException("仅支持删除命名带有 _temp_ 的临时表");
+        }
+        jdbcTemplate.execute("drop table tsl_data." + tempTableName);
+    }
+
+    public List<String> selectTempTableNames() {
+        String sql = "select tablename  from pg_catalog.pg_tables where schemaname = 'tsl_data' and tablename like '%_temp_%'";
+        return jdbcTemplate.queryForList(sql, String.class);
+    }
+
+    /**
+     * 清空表
+     */
+    public void truncate(String tableName) {
+        jdbcTemplate.execute("truncate table tsl_data." + tableName);
+    }
+
+    /**
+     * 从临时表中查找新增内容并插入work_flow_basic_data表
+     * 20240205 不再使用
+     */
+    @Deprecated
+    public int insertWorkFlowBasicDataFromTempTable(String tempTableName) {
+        String sqlFormat = "insert into tsl_data.work_flow_basic_data" +
+                " (order_no, is_effctive, 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.is_effctive, 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.order_no = b.order_no" +
+                " where b.order_no is null";
+        return jdbcTemplate.update(String.format(sqlFormat, tempTableName));
+    }
+
+    /**
+     * 从临时表中查找需要更新内容并更新到work_flow_basic_data表
+     * 20240205 不再使用
+     */
+    @Deprecated
+    public int updateWorkFlowBasicDataFromTempTable(String tempTableName) {
+        String sqlFormat = "update tsl_data.work_flow_basic_data b\n" +
+                "set kfsn = t.kfsn, is_effctive = t.is_effctive, 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, is_effctive, 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.order_no = t.order_no";
+        return jdbcTemplate.update(String.format(sqlFormat, tempTableName));
+    }
+
+    /**
+     * high_quality_list_day 河北_CEM高品质2日明细
+     */
+    public void batchInsertHighQualityListDay(List<Object[]> data) {
+        String sql = "INSERT INTO tsl_data.high_quality_list_day\n" +
+                "(month_id, day_id, contact_id, busino_area_name, busino_prov_name, sheet_no, busi_number," +
+                " cust_star_name, sheet_type_name, serv_type_name, last_deal_content, accept_time, archived_time," +
+                " data_type_name, channel_name, profes_dep, big_type_name, small_type_name, is_dispatch_cloud," +
+                " accept_channel_name, duty_reason_name, duty_major_name, is_online_complete, is_call_complete," +
+                " is_cusser_complete, is_distr_complete, caller_number, compl_area_name, compl_prov_name," +
+                " submit_channel, solved_result_desc, cust_level_name, busi_type_name, urgent_level_name," +
+                " important_type_name, is_upgrade, actual_total_len, nature_actual_total_len, cust_satis_desc," +
+                " auto_is_ok, auto_cust_satis_desc, nonauto_is_ok_name, nonauto_cust_satis_desc, prod_type_name," +
+                " proc_name, merge_satis_desc, serv_type_name_new, is_svip_keyman, customer_label, prov_name," +
+                " area_id, area_name, city_id, city_name, grid_id, grid_name, is_distri_area, cp_satisfaction," +
+                " cp_is_ok, cp_timely_contact, cp_type, novisit_tag, serv_content, gis_area, gis_area_name," +
+                " gis_city, gis_city_name, use_gis, month_date, day_date)" +
+                " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
+                " ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
+                " ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+        jdbcTemplate.batchUpdate(sql, data);
+    }
+
+    /**
+     * high_quality_count_day 河北_CEM高品质2日统计 数据入库
+     */
+    public void batchInsertHighQualityCountDay(List<Object[]> data) {
+        String sql = "insert into tsl_data.high_quality_count_day (month_id,day_id,acct_date,businoareaname," +
+                " profes_dep,big_type_name,small_type_name,total_complaints,hotline_complaints,other_complaint," +
+                " litigation_volume,satisfaction_rate,satisfaction_count,total_evaluation,complaint_satisfied," +
+                " complaint_satisfied_list,complaint_satisfied_count,complaint_resolution,complaint_resolution_list," +
+                " complaint_resolution_count,complaint_response,complaint_response_list,complaint_response_count," +
+                " complaint,fault_satisfaction_rate,fault_satisfaction_list,fault_satisfaction_count," +
+                " fault_resolution_rate,fault_resolution_list,fault_resolution_count,fault_response_rate," +
+                " fault_response_list,fault_response_count)" +
+                " values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
+        jdbcTemplate.batchUpdate(sql, data);
+    }
+
+    /**
+     * MOBILE_COMPLAINT_DETAILS_DAY 河北_CEM移网质量投诉明细 数据入库
+     */
+    public void batchInsertMobileComplaintDay(List<Object[]> data) {
+        String sql = "insert into tsl_data.mobile_complaint_day (month_id,day_id,acct_date,sheet_no," +
+                " is_online_complete,contact_no,busi_no,serv_content,last_deal_content,deal_depart_name," +
+                " deal_opinion,serv_type,bus_type,duty_reason,accept_channel,submit_channel,compl_area_local," +
+                " duty_major,product_name,sp_product_code,pre_repair_name,pre_repair_charges,fault_location," +
+                " cust_level,satisfaction_in_reply,is_ok_in_reply,accept_time,end_time,proce_time,cust_area," +
+                " is_cust_serv_complete,is_send_sheet_complete,is_repeat,is_upgrade,is_timeout,gis_city," +
+                " process_nums,deal_depart_name_1,deal_depart_name_2,deal_depart_name_3,first_call_back_time," +
+                " proce_remark,duty_major_day,duty_reason_id_day,duty_major_month,duty_reason_id_month,voice_text)" +
+                " values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
+        jdbcTemplate.batchUpdate(sql, data);
+    }
+
+    /**
+     * COMPLAINT_DETAILS_FIX_YWD_DAY 河北客户体验管理智能定责投诉明细月累计接口日 数据入库
+     */
+    public void batchInsertComplaintDetailsFixYwdDay(List<Object[]> data) {
+        String sql = "INSERT INTO tsl_data.complaint_details_fix_ywd_day " +
+                "(month_id, day_id, month_id1, day_id1, sheet_no, is_online_complete, contact_no, busi_no," +
+                " serv_content, last_deal_content, deal_depart_name, deal_user, deal_opinion, complete_user_code," +
+                " serv_type, bus_type, duty_reason, problem_duty, problem_solving_con, code_urgent_level," +
+                " code_important_type, cust_name, accept_user, cust_level, accept_channel, submit_channel," +
+                " compl_area_local, compl_city_local, compl_grid_local, cust_province, accept_time, end_time," +
+                " cust_area, duty_major, is_call_center_complete, is_cust_serv_complete, is_send_sheet_complete," +
+                " sp_code, sp_name, is_repeat, is_timeout, duty_dept, proce_user_code, proce_time," +
+                " proce_depart_name, proce_remark, satisfaction, is_ok, is_ok_in_reply, product_name," +
+                " pre_repair_name, pre_repair_charges, actual_total_len, timeout_len, timeout_depart_name," +
+                " sp_product_code, sp_product_name, process_nums, deal_depart_name_1, deal_user_1, sended_user_1," +
+                " deal_depart_name_2, deal_user_2, sended_user_2, deal_depart_name_3, deal_user_3, sended_user_3," +
+                " deal_depart_name_end, deal_user_end, sended_depart_name_last, first_call_back_time," +
+                " duty_reason_id_day, duty_major_day, duty_reason_id_month, duty_major_month, voice_text," +
+                " fault_location, satisfaction_in_reply, is_upgrade, gis_city, data_source, code_answer_way," +
+                " success_call_back, last_satisfaction, last_is_ok, design_profess, deal_unit, province_deal_unit," +
+                " if_direct, is_proself, timely_contact, is_repeat_5times_m, is_repeat_2times_m, is_repeat_2times_2m," +
+                " is_repeat_2times_3m, create_time)" +
+                "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
+                " ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
+                " ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 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));
+        });
+    }
+
+    /**
+     * 河北客户体验管理系统移网网络体验明细日信息 cem_mobile_experience_list 数据入库
+     */
+    public void batchInsertCemMobileExperienceList(List<Object[]> data) {
+        String sql = "INSERT INTO tsl_data.cem_mobile_experience_list " + //
+                "(month_id, day_id, service_id, contact_id, create_time, mobile_no, service_type_name1," +
+                " pro_name, code_cust_area, zyx, lwly_name, big_type_name, small_type_name, acct_month," +
+                " day_id1, sheet_no, compl_city_local) " + //
+                "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+        jdbcTemplate.batchUpdate(sql, data);
+    }
+
+    /**
+     * 河北客户体验管理系统移网网络体验明细日信息 cem_mobile_experience_list 按照帐期清空
+     */
+    public int deleteCemMobileExperienceList(String day) {
+        String sql = "delete from tsl_data.cem_mobile_experience_list 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));
+        });
+    }
+}

+ 23 - 0
src/main/java/com/nokia/tsl_data/dao/UserCountRepository.java

@@ -0,0 +1,23 @@
+package com.nokia.tsl_data.dao;
+
+import com.nokia.tsl_data.entity.UserCount;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface UserCountRepository extends JpaRepository<UserCount, Long> {
+
+    @Query("select e from UserCount e where e.monthId = :month and e.cityName = :city")
+    UserCount findByMonthAndCity(@Param("month") String month, @Param("city") String city);
+
+    /**
+     * 按月查询客户端用户数
+     */
+    @Query("select t from UserCount t where t.monthId = :monthId")
+    List<UserCount> selectUserCountForMonth(@Param("monthId") String monthId);
+
+}

+ 51 - 0
src/main/java/com/nokia/tsl_data/dao/WorkFlowBasicDataMapper.java

@@ -0,0 +1,51 @@
+package com.nokia.tsl_data.dao;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+/*
+ * 20240205删除
+ */
+/* cspell: disable */
+@Deprecated
+@Mapper
+public interface WorkFlowBasicDataMapper {
+
+        /**
+         * 超时工单统计
+         */
+        @Select("with t1 as (select sdd.real_name as city,kfsn, to_timestamp(kf_file_time, 'yyyy-mm-dd hh24:mi:ss')" +
+                        " - to_timestamp(kd_accept_time, 'yyyy-mm-dd hh24:mi:ss') > make_interval(hours => #{target}) as is_timeout\n"
+                        + "from tsl_data.work_flow_basic_data wfbd, tsl_data.sys_data_dictionary sdd \n" + //
+                        "where kf_file_time is not null and re_is_status_id != '06678b79185349b5bf0c24490a978fbb'\n" + //
+                        "and wfbd.city_id = sdd.nick_code and is_effctive = '1' " +
+                        " and to_timestamp(kf_file_time, 'yyyy-mm-dd hh24:mi:ss') >= #{start}"
+                        + " and to_timestamp(kf_file_time, 'yyyy-mm-dd hh24:mi:ss') <= #{end})," +
+                        "t2 as (select '全省' as city, count(1) as total_num, count(is_timeout or null) as timeout_num, "
+                        + " count(is_timeout or null)::float8 / count(1) as timeout_ratio from t1),"
+                        + " t3 as (select city, count(1) as total_num, count(is_timeout or null) as timeout_num, " +
+                        " count(is_timeout or null)::float8 / count(1) as timeout_ratio from t1 group by city) " +
+                        " select * from t2 union select * from t3 ")
+        List<Map<String, Object>> selectTimeoutTsCountForDay(@Param("start") Date start, @Param("end") Date end, @Param("target") int target);
+
+        /**
+         * 处理时长统计
+         */
+        @Select("with t1 as (select sdd.real_name,kfsn, (extract(epoch from to_timestamp(kf_file_time, 'YYYY-MM-DD HH24:MI:SS'))"
+                        + " - extract(epoch from to_timestamp(kd_accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 as cost_time "
+                        + "from tsl_data.work_flow_basic_data wfbd, tsl_data.sys_data_dictionary sdd \n" + //
+                        " where wfbd.city_id = sdd.nick_code and kf_file_time is not null" +
+                        " and re_is_status_id != '06678b79185349b5bf0c24490a978fbb' and is_effctive = '1' "
+                        + "and to_timestamp(wfbd.kf_file_time, 'yyyy-mm-dd hh24:mi:ss') >= #{start}" +
+                        " and to_timestamp(wfbd.kf_file_time, 'yyyy-mm-dd hh24:mi:ss') <= #{end})," +
+                        " t2 as (select real_name as city, avg(cost_time) as avg_duration\n" + //
+                        " from t1 group by real_name order by real_name), " +
+                        " t3 as (select '全省' as city, avg(cost_time) as avg_duration from t1)\n" + //
+                        " select * from t2 union select * from t3 ")
+        List<Map<String, Object>> selectTsDurationForDay(@Param("start") Date start, @Param("end") Date end);
+}

+ 291 - 0
src/main/java/com/nokia/tsl_data/dao/WorkFlowDao.java

@@ -0,0 +1,291 @@
+package com.nokia.tsl_data.dao;
+
+import com.nokia.tsl_data.entity.SysDataDictionary;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import java.sql.Types;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 从 工作流数据库 读取
+ */
+/* cSpell: disable */
+@Slf4j
+@Component
+public class WorkFlowDao {
+
+        private final JdbcTemplate jdbcTemplate;
+
+        public WorkFlowDao(@Qualifier("workFlowJdbcTemplate") JdbcTemplate jdbcTemplate) {
+                this.jdbcTemplate = jdbcTemplate;
+        }
+
+        // 获取全部地市信息
+        public List<SysDataDictionary> findSysDataDictionaryForCity() {
+                return findSysDataDictionaries("city");
+        }
+
+        // 获取全部区县信息
+        public List<SysDataDictionary> findSysDataDictionaryForRegion() {
+                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在两个范围之间的全部工单信息
+        // 20240205 不再使用
+        @Deprecated
+        public List<Map<String, Object>> findWorkFlowBasicDataByUpdateTime(Instant start, Instant end) {
+                // 20240112 修改 使用 serv_order 替换了kfsn
+                String sql = "select order_no, " + //
+                                "city as city_id, " + //
+                                "is_effctive as is_effctive, " + //
+                                "form_basic_data->>'yh_region' as region_id, " + //
+                                "form_basic_data->>'serv_order' 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.queryForList(sql, objects, argTypes);
+        }
+
+        /**
+         * 直接从工作流读取处理时长数据
+         */
+        @Deprecated
+        public List<Map<String, Object>> selectTsDurationBasicData(Instant start, Instant end) {
+                String sql = "with t1 as (select city as city_id, \r\n" + //
+                                "form_basic_data->>'serv_order' as kfsn,\r\n" + //
+                                "(extract(epoch from to_timestamp(form_basic_data->>'kf_file_time', 'YYYY-MM-DD HH24:MI:SS')) -\r\n"
+                                + //
+                                "extract(epoch from to_timestamp(form_basic_data->>'kd_accepttime', 'YYYY-MM-DD HH24:MI:SS'))) /\r\n"
+                                + //
+                                "3600 as cost_time \r\n" + //
+                                "from flow_form_basic\r\n" + //
+                                "where form_basic_data->>'re_is_status' != '06678b79185349b5bf0c24490a978fbb' \r\n" + //
+                                "and form_basic_data->>'kf_file_time' is not null \r\n" + //
+                                "and is_effctive = '1'\r\n" + //
+                                "and to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') >= ? \r\n"
+                                + //
+                                "and to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') < ? \r\n" + //
+                                "select city_id, kfsn, min(cost_time) as cost_time from t1 group by city_id, kfsn";
+                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 };
+                Long startLong = System.currentTimeMillis();
+                List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, objects, argTypes);
+                log.debug("从工作流读取处理时长数据, 耗时 {} ms...", System.currentTimeMillis() - startLong);
+                return result;
+        }
+
+        // 20240304 修改平均处理时长算法
+        // 20240313 增加过滤条件 客服工单号不以 ZHX 开头
+        public List<Map<String, Object>> selectTsDurationForDayV2(String start, String end) {
+                String sql = "with t1 as (select order_no, city as city_id, form_basic_data->>'serv_order' as kfsn " +
+                                ", form_basic_data->>'kd_accepttime' as kd_accepttime " +
+                                "from flow_form_basic where is_effctive = '1' " +
+                                "and substring(form_basic_data->>'serv_order' from 1 for 3) != 'ZHX' " +
+                                "and create_time >= ? and create_time <= ? " +
+                                "and form_basic_data->>'kd_accepttime' >= ? " +
+                                "and form_basic_data->>'kd_accepttime' <= ?) " +
+                                ", t2 as (select order_no, res_date " +
+                                "from complaint.kf_log), " +
+                                "t3 as (select t1.order_no, t1.city_id, t1.kfsn," +
+                                "extract(epoch from coalesce(to_timestamp(max(t2.res_date), 'yyyy-mm-dd hh24:mi:ss'), now())) "
+                                +
+                                "- extract(epoch from to_timestamp(min(t1.kd_accepttime), 'yyyy-mm-dd hh24:mi:ss')) as cost_time "
+                                +
+                                "from t1 left join t2 " +
+                                "on t1.order_no = t2.order_no " +
+                                "group by t1.order_no, t1.city_id, t1.kfsn) " +
+                                ", t4 as (select city_id, kfsn, min(cost_time) as cost_time from t3 group by city_id, kfsn) "
+                                +
+                                ", t5 as (select city_id, avg(cost_time)::float8/3600 as avg_duration from t4 group by city_id) "
+                                +
+                                ", t6 as (select '全省', avg(cost_time)::float8/3600 as avg_duration from t4) " +
+                                "select * from t5 union select * from t6";
+                Object[] objects = new Object[] { start, end, start, end };
+                int[] argTypes = new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.TIMESTAMP, Types.TIMESTAMP };
+                Long startLong = System.currentTimeMillis();
+                List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, objects, argTypes);
+                log.debug("从工作流读取{}-{}平均处理时长数据, 耗时 {} ms, sql: {}",
+                                start,
+                                end,
+                                System.currentTimeMillis() - startLong,
+                                sql);
+                return result;
+        }
+
+        // 20240304 之前获取平均处理时长的sql语句
+        @Deprecated
+        public List<Map<String, Object>> selectTsDurationForDay(Instant start, Instant end) {
+                String sql = "with t1 as (select city as city_id, \r\n" + //
+                                "form_basic_data->>'serv_order' as kfsn,\r\n" + //
+                                "(extract(epoch from to_timestamp(form_basic_data->>'kf_file_time', 'YYYY-MM-DD HH24:MI:SS'))\r\n"
+                                + //
+                                "- extract(epoch from to_timestamp(form_basic_data->>'kd_accepttime', 'YYYY-MM-DD HH24:MI:SS')))\r\n"
+                                + //
+                                "/ 3600 as cost_time \r\n" + //
+                                "from flow_form_basic\r\n" + //
+                                "where form_basic_data->>'re_is_status' != '06678b79185349b5bf0c24490a978fbb'\r\n" + //
+                                "and form_basic_data->>'kf_file_time' is not null \r\n" + //
+                                "and is_effctive = '1'\r\n" + //
+                                "and to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') >= ?\r\n" + //
+                                "and to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') < ?),\r\n"
+                                + //
+                                "t2 as (select city_id, kfsn, min(cost_time) as cost_time from t1 group by city_id, kfsn),\r\n"
+                                + //
+                                "t3 as (select city_id, avg(cost_time)::float8 as avg_duration from t2 group by city_id),\r\n"
+                                + //
+                                "t4 as (select '全省' as city_id, avg(cost_time)::float8 as avg_duration from t2)\r\n" + //
+                                "select * from  t3 union select * from t4";
+                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 };
+                Long startLong = System.currentTimeMillis();
+                List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, objects, argTypes);
+                log.debug("从工作流读取{}-{}平均处理时长数据, 耗时 {} ms...",
+                                start,
+                                end,
+                                System.currentTimeMillis() - startLong);
+                return result;
+        }
+
+        // 20240326 要求修改为 使用 回单答复超时 字段统计,排除ZHX开头的工单
+        // 
+        public List<Map<String, Object>> selectTimeoutTsCountForDayV2(Instant start, Instant end) {
+                String sql = "with t1 as (select order_no, city as city_id, \r\n" + //
+                                "form_basic_data->>'serv_order' as kfno,\r\n" + //
+                                "form_basic_data ->> 'res_is_timeout' as is_timeout, \r\n" + //
+                                "form_basic_data->>'kd_accepttime' as kd_accepttime\r\n" + //
+                                "from flow_form_basic\r\n" + //
+                                "where is_effctive = '1'\r\n" + //
+                                "and substring(form_basic_data->>'serv_order' from 1 for 3) != 'ZHX'\r\n" + //
+                                "and form_basic_data->>'kd_accepttime' >= ? " + //
+                                "and form_basic_data->>'kd_accepttime' <= ? ), " + //
+                                "t2 as (select '全省' as city_id, count(1) as total_num, \r\n" + //
+                                "count(is_timeout = 'ca9d5f1f8363440699f11b802150eb77' or null) as timeout_num,\r\n" + //
+                                "count(is_timeout = 'ca9d5f1f8363440699f11b802150eb77' or null)::float8 / count(1) as timeout_ratio\r\n"
+                                + "from t1),\r\n" + //
+                                "t3 as (select city_id, count(1) as total_num, \r\n" + //
+                                "count(is_timeout = 'ca9d5f1f8363440699f11b802150eb77' or null) as timeout_num,\r\n" + //
+                                "count(is_timeout = 'ca9d5f1f8363440699f11b802150eb77' or null)::float8 / count(1) as timeout_ratio\r\n"
+                                + "from t1 group by city_id)\r\n" + //
+                                "select * from t2 union select * from t3";
+                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 };
+                Long startLong = System.currentTimeMillis();
+                List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, objects, argTypes);
+                log.debug("从工作流读取{}-{}超时工单数据, 耗时 {} ms...",
+                                start,
+                                end,
+                                System.currentTimeMillis() - startLong);
+                return result;
+        }
+
+        // 20240326 要求修改为 使用 回单答复超时 字段统计,排除ZHX开头的工单
+        @Deprecated
+        public List<Map<String, Object>> selectTimeoutTsCountForDay(Instant start, Instant end, int target) {
+                String sql = "with t1 as (select distinct city as city_id, \r\n" + //
+                                "form_basic_data->>'serv_order' as kfsn,\r\n" + //
+                                "to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') - \r\n" + //
+                                "to_timestamp(form_basic_data->>'kd_accepttime', 'yyyy-mm-dd hh24:mi:ss') \r\n" + //
+                                "> make_interval(hours => ?) as is_timeout \r\n" + //
+                                "from flow_form_basic\r\n" + //
+                                "where form_basic_data->>'re_is_status' != '06678b79185349b5bf0c24490a978fbb'\r\n" + //
+                                "and form_basic_data->>'kf_file_time' is not null \r\n" + //
+                                "and is_effctive = '1'\r\n" + //
+                                "and to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') >= ?\r\n" + //
+                                "and to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') <= ?),\r\n"
+                                + //
+                                "t2 as (select '全省' as city_id, count(1) as total_num, count(is_timeout or null) as timeout_num,\r\n"
+                                + //
+                                "count(is_timeout or null)::float8 / count(1) as timeout_ratio from t1),\r\n" + //
+                                "t3 as (select city_id, count(1) as total_num, count(is_timeout or null) as timeout_num,\r\n"
+                                + //
+                                "count(is_timeout or null)::float8 / count(1) as timeout_ratio from t1 group by city_id)\r\n"
+                                + //
+                                "select * from  t2 union select * from t3";
+                DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
+                                .withZone(ZoneId.of("Asia/Shanghai"));
+                Object[] objects = new Object[] { target, dateFormat.format(start), dateFormat.format(end) };
+                int[] argTypes = new int[] { Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP };
+                Long startLong = System.currentTimeMillis();
+                List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, objects, argTypes);
+                log.debug("从工作流读取{}-{}超时工单数据, 耗时 {} ms...",
+                                start,
+                                end,
+                                System.currentTimeMillis() - startLong);
+                return result;
+        }
+
+        @Deprecated
+        public List<Map<String, Object>> selectTsDurationForMonth(String month) {
+                String sql = "with t1 as (select city as city_id, \r\n" + //
+                                "form_basic_data->>'serv_order' as kfsn,\r\n" + //
+                                "(extract(epoch from to_timestamp(form_basic_data->>'kf_file_time', 'YYYY-MM-DD HH24:MI:SS'))\r\n"
+                                + //
+                                "- extract(epoch from to_timestamp(form_basic_data->>'kd_accepttime', 'YYYY-MM-DD HH24:MI:SS')))\r\n"
+                                + //
+                                "/ 3600 as cost_time \r\n" + //
+                                "from flow_form_basic\r\n" + //
+                                "where form_basic_data->>'re_is_status' != '06678b79185349b5bf0c24490a978fbb'\r\n" + //
+                                "and form_basic_data->>'kf_file_time' is not null \r\n" + //
+                                "and is_effctive = '1'\r\n" + //
+                                "and to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') >= " +
+                                "to_timestamp(concat(?, '01000000'), 'yyyymmddhh24miss') " +
+                                "and to_timestamp(form_basic_data->>'kf_file_time', 'yyyy-mm-dd hh24:mi:ss') < " +
+                                "(to_timestamp(concat(?, '01000000'), 'yyyymmddhh24miss') + interval '1 month'))," +
+                                "t2 as (select city_id, kfsn, min(cost_time) as cost_time from t1 group by city_id, kfsn),\r\n"
+                                + //
+                                "t3 as (select ? as month_id, city_id, avg(cost_time)::float8 as avg_duration from t2 group by city_id),"
+                                + "t4 as (select ? as month_id, '全省' as city_id, avg(cost_time)::float8 as avg_duration from t2)"
+                                +
+                                "select * from  t3 union select * from t4";
+                Object[] objects = new Object[] { month, month, month, month };
+                int[] argTypes = new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR };
+                Long startLong = System.currentTimeMillis();
+                List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, objects, argTypes);
+                log.debug("从工作流读取{}月平均处理时长数据, 耗时 {} ms...",
+                                month,
+                                System.currentTimeMillis() - startLong);
+                return result;
+        }
+}

+ 82 - 0
src/main/java/com/nokia/tsl_data/entity/HighQualityData.java

@@ -0,0 +1,82 @@
+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;
+
+@Data
+@Entity
+@EntityListeners(AuditingEntityListener.class)
+@Table(name = "high_quality_data", schema = "tsl_data")
+public class HighQualityData {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    // 账期
+    @Column(name = "day_id", columnDefinition = "varchar(8)", nullable = false)
+    private String dayId;
+
+    // 工单号
+    @Column(name = "sheet_no", columnDefinition = "varchar(50)", nullable = false)
+    private String sheetNo;
+
+    // 测评-满意度
+    @Column(name = "cp_satisfaction", columnDefinition = "varchar(50)")
+    private String cpSatisfaction;
+
+    // 测评-解决情况
+    @Column(name = "cp_is_ok", columnDefinition = "varchar(50)")
+    private String cpIsOk;
+
+    // 测评-响应情况
+    @Column(name = "cp_timely_contact", columnDefinition = "varchar(50)")
+    private String cpTimelyContact;
+
+    // 未回访标识 已回访/未回访
+    @Column(name = "no_visit_tag", columnDefinition = "varchar(50)")
+    private String noVisitTag;
+
+    // 地市编码
+    @Column(name = "area_id", columnDefinition = "varchar(50)")
+    private String areaId;
+
+    // 地市名
+    @Column(name = "area_name", columnDefinition = "varchar(50)")
+    private String areaName;
+
+    // 区县编码
+    @Column(name = "city_id", columnDefinition = "varchar(50)")
+    private String cityId;
+
+    // 区县名
+    @Column(name = "city_name", columnDefinition = "varchar(50)")
+    private String cityName;
+
+    // 最后确认的地市
+    @Column(name = "checked_city", columnDefinition = "varchar(50)")
+    private String checkedCity;
+
+    // 确认地市的原因
+    @Column(name = "checked_city_reason", columnDefinition = "varchar(50)")
+    private String checkedCityReason;
+
+    // 最后确认的区县
+    @Column(name = "checked_region", columnDefinition = "varchar(50)")
+    private String checkedRegion;
+
+    // 确认区县的原因
+    @Column(name = "checked_region_reason", columnDefinition = "varchar(50)")
+    private String checkedRegionReason;
+
+    @CreatedDate
+    private Instant createDate;
+
+    @LastModifiedDate
+    private Instant lastUpdateDate;
+}

+ 26 - 0
src/main/java/com/nokia/tsl_data/entity/ManagementDetail.java

@@ -0,0 +1,26 @@
+package com.nokia.tsl_data.entity;
+
+import java.time.Instant;
+
+import lombok.Data;
+
+/**
+ * 管理端统计详表
+ */
+@Data
+public class ManagementDetail {
+    private Long id;
+    // 数据来源
+    private String origin;
+    // 账期 20231224
+    private String statDayId;
+    // 工单归属日期
+    private String acctDate;
+    // 工单流水号
+    private String sheetNo;
+    // 投诉归属地市 COMPL_AREA_LOCAL
+    private String complAreaLocal;
+    // 投诉归属区县 COMPL_CITY_LOCAL
+    private String complCityLocal;
+    private Instant createTime;
+}

+ 53 - 0
src/main/java/com/nokia/tsl_data/entity/SysDataDictionary.java

@@ -0,0 +1,53 @@
+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;
+
+@Data
+@Entity
+@EntityListeners(AuditingEntityListener.class)
+@Table(name = "sys_data_dictionary", schema = "tsl_data")
+public class SysDataDictionary {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    // 类型名
+    @Column(name = "type", columnDefinition = "varchar(50)")
+    private String type;
+
+    // 真实码
+    @Column(name = "real_code", columnDefinition = "varchar(50)")
+    private String realCode;
+
+    // 真实名
+    @Column(name = "real_name", columnDefinition = "varchar(100)")
+    private String realName;
+
+    // 其他码
+    @Column(name = "nick_code", columnDefinition = "varchar(50)")
+    private String nickCode;
+
+    // 其他名
+    @Column(name = "nick_name", columnDefinition = "varchar(100)")
+    private String nickName;
+
+    // 用于排序
+    @Column(name = "ord", columnDefinition = "int4")
+    private Integer ord;
+
+    @OneToOne
+    @JoinColumn(name = "parent_id", referencedColumnName = "id")
+    private SysDataDictionary parent;
+
+    @CreatedDate
+    private Instant createDate;
+
+    @LastModifiedDate
+    private Instant lastUpdateDate;
+}

+ 58 - 0
src/main/java/com/nokia/tsl_data/entity/TaskRecord.java

@@ -0,0 +1,58 @@
+package com.nokia.tsl_data.entity;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.nokia.tsl_data.entity.converter.JSONObjectConverter;
+
+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;
+
+/**
+ * 20240205 不再记录
+ */
+@Deprecated
+@Data
+@Entity
+@EntityListeners(AuditingEntityListener.class)
+@Table(name = "task_record", schema = "tsl_data")
+public class TaskRecord {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    // 任务名称
+    @Column
+    private String taskName;
+
+    // 任务执行开始时间
+    @Column
+    private Instant startTime;
+
+    // 任务执行结束时间
+    @Column
+    private Instant endTime;
+
+    // 任务结束状态 非0即为失败
+    @Column
+    private Integer endStatus;
+
+    // 任务耗时
+    @Column
+    private Long timeCost;
+
+    // 任务详情,采用json字符串记录
+    @Column(name = "task_info", columnDefinition = "text")
+    @Convert(converter = JSONObjectConverter.class)
+    private JSONObject taskInfo;
+
+    @CreatedDate
+    private Instant createDate;
+
+    @LastModifiedDate
+    private Instant lastUpdateDate;
+}

+ 44 - 0
src/main/java/com/nokia/tsl_data/entity/UserCount.java

@@ -0,0 +1,44 @@
+package com.nokia.tsl_data.entity;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+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;
+
+@Data
+@Entity
+@Accessors(chain = true)
+@EntityListeners(AuditingEntityListener.class)
+@Table(name = "user_count", schema = "tsl_data", indexes = {
+        @Index(name = "month_city_unique", columnList = "month_id, city_name", unique = true)
+})
+public class UserCount {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(name = "month_id", columnDefinition = "varchar(6)", nullable = false)
+    private String monthId;
+
+    @Column(name = "city_name", columnDefinition = "varchar(10)", nullable = false)
+    private String cityName;
+
+    // 管理端用户数统计
+    @Column
+    private Double managementUserCount;
+
+    // 客户端用户数统计
+    @Column
+    private Double customerUserCount;
+
+    @CreatedDate
+    private Instant createDate;
+
+    @LastModifiedDate
+    private Instant lastUpdateDate;
+}

+ 42 - 0
src/main/java/com/nokia/tsl_data/entity/WorkFlowBasicData.java

@@ -0,0 +1,42 @@
+package com.nokia.tsl_data.entity;
+
+import lombok.Data;
+import java.time.Instant;
+
+/**
+ * 数据源是工单流程的flow_form_basic表 码表 sys_data_dictionary
+ * tsl_data.work_flow_basic_data
+ * 20240205开始不再更新
+ */
+/* cSpell: disable */
+@Deprecated
+@Data
+public class WorkFlowBasicData {
+
+    private Long id;
+    // 流程系统内工单号 唯一
+    private String orderNo;
+    // is_effctive = '1' 
+    // 1 表示有效工单,0表示无效工单
+    private String isEffctive;
+    // 客服工单号 理论上不应有重复,但是实际上会出现重复
+    private String kfsn;
+    // 归属地市
+    private String cityId;
+    // 归属区县
+    private String regionId;
+    // 客服受理时间
+    private String kdAcceptTime;
+    // 客服要求答复时间
+    private String kdRequestReplyTime;
+    // 客服答复状态
+    private String reIsStatusId;
+    // 客服归档时间
+    private String kfFileTime;
+    // 客服归档类型
+    private String kfFileType;
+    // 工单创建时间
+    private Instant workFlowCreateTime;
+    // 工单更新时间
+    private Instant workFlowUpdateTime;
+}

+ 19 - 0
src/main/java/com/nokia/tsl_data/entity/converter/JSONObjectConverter.java

@@ -0,0 +1,19 @@
+package com.nokia.tsl_data.entity.converter;
+
+import com.alibaba.fastjson2.JSONObject;
+
+import javax.persistence.AttributeConverter;
+import javax.persistence.Converter;
+
+@Converter
+public class JSONObjectConverter implements AttributeConverter<JSONObject, String>{
+    @Override
+    public String convertToDatabaseColumn(JSONObject jsonObject) {
+        return jsonObject.toString();
+    }
+
+    @Override
+    public JSONObject convertToEntityAttribute(String s) {
+        return JSONObject.parseObject(s);
+    }
+}

+ 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;
+}

+ 61 - 0
src/main/java/com/nokia/tsl_data/entity/vo/R.java

@@ -0,0 +1,61 @@
+package com.nokia.tsl_data.entity.vo;
+
+import lombok.Data;
+
+/**
+ * 返回值的统一包装
+ */
+@Data
+public class R {
+    private Boolean success;
+    private Integer code = 0;
+    private String message;
+    private Object data;
+
+    /**
+     * 私有化构造方法,不允许在外部实例化
+     */
+    private R() {
+    }
+
+    /**
+     * 成功的静态方法
+     *
+     * @return R实例
+     */
+    public static R ok() {
+        R r = new R();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage("成功");
+        return r;
+    }
+
+    /**
+     * 失败的静态方法
+     *
+     * @return R实例
+     */
+    public static R error() {
+        R r = new R();
+        r.setSuccess(false);
+        r.setCode(500);
+        r.setMessage("失败");
+        return r;
+    }
+
+    public R code(int code) {
+        this.setCode(code);
+        return this;
+    }
+
+    public R message(String message) {
+        this.setMessage(message);
+        return this;
+    }
+
+    public R data(Object data) {
+        this.setData(data);
+        return this;
+    }
+}

+ 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));
+    }
+}

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

@@ -0,0 +1,73 @@
+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 = "投诉清单各地市投诉率";
+
+    /**
+     * 目标值
+     */
+    // 满意度目标
+    private double satisfiedCompliance = 0.92d;
+    // 解决率目标
+    private double resolutionCompliance = 0.85d;
+    // 响应率目标
+    private double responseCompliance = 0.99d;
+    // 移网综合投诉评价 目标
+    private double evaluation = 0.95d;
+    // 处理时长达标值 单位是小时
+    private int duration = 30;
+
+    /**
+     * 河北_CEM高品质2日详表
+     */
+    // HighQualityListDay 文件存储路径
+    private String dirOfHighQualityListDay = "/data/nenglishangdian/high_quality_list_day";
+    // HighQualityListDay 文件前缀
+    private String prefixOfHighQualityListDay = "HE_D_HIGH_QUALITY_LIST_DAY_1114200095151828992_";
+    // HighQualityListDay 字段数量
+    private Integer filedNumOfHighQualityListDay = 70;
+
+    /**
+     * 河北_CEM高品质2日统计
+     */
+    // HighQualityCountDay 文件存储路径
+    private String dirOfHighQualityCountDay = "/data/nenglishangdian/high_quality_count/";
+    // HighQualityCountDay 文件前缀
+    private String prefixOfHighQualityCountDay = "HE_D_HIGH_QUALITY_COUNT_DAY_1087387382887477248_";
+    // HighQualityCountDay 字段数量
+    private Integer filedNumOfHighQualityCountDay = 33;
+
+    /**
+     * 河北_CEM移网质量投诉明细
+     */
+    // HighQualityCountDay 文件存储路径
+    private String dirOfMobileComplaint = "/data/nenglishangdian/mobile_complaint/";
+    // HighQualityCountDay 文件前缀
+    private String prefixOfMobileComplaint = "HE_D_MOBILE_COMPLAINT_DETAILS_DAY_1087468015013851136_";
+    // HighQualityCountDay 字段数量
+    private Integer filedNumOfMobileComplaintDay = 47;
+
+    /**
+     * 河北客户体验管理智能定责投诉明细月累计接口日
+     */
+    private String dirOfComplaintDetailsFixYwdDay = "/data/nenglishangdian/complaint_details_fix_ywd_day/";
+    private String prefixOfComplaintDetailsFixYwdDay = "HE_D_COMPLAINT_DETAILS_FIX_YWD_DAY_1186327221372567552_";
+    private Integer filedNumOfComplaintDetailsFixYwdDay = 95;
+
+    /**
+     * 河北客户体验管理系统移网网络体验明细日信息
+     */
+    private String dirOfCemMobileExperienceList = "/data/nenglishangdian/HE_D_CEM_MOBILE_EXPERIENCE_LIST/";
+    private String prefixOfCemMobileExperienceList = "HE_D_CEM_MOBILE_EXPERIENCE_LIST_1193846275255566336_";
+    private Integer filedNumOfCemMobileExperienceList = 17;
+}

+ 20 - 0
src/main/java/com/nokia/tsl_data/push_message/config/PushMessageConfig.java

@@ -0,0 +1,20 @@
+package com.nokia.tsl_data.push_message.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+import com.nokia.tsl_data.push_message.properties.PushMessageProperties;
+
+@Configuration
+@EnableConfigurationProperties(PushMessageProperties.class)
+public class PushMessageConfig {
+
+    @Bean
+    @ConditionalOnMissingBean(RestTemplate.class)
+    public RestTemplate restTemplate() {
+        return new RestTemplate();
+    }
+}

+ 21 - 0
src/main/java/com/nokia/tsl_data/push_message/properties/PushMessageProperties.java

@@ -0,0 +1,21 @@
+package com.nokia.tsl_data.push_message.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "common.pushmessage")
+public class PushMessageProperties {
+    // api-url,不增加转发时应该时能力商店的url
+    private String url = "http://10.244.18.105:8000/api/chinaUnicom/microservice/notice/pushMessage/v1";
+    // 能力商店的 appId
+    private String appId = "ENWaB7YdUD";
+    // 能力商店正式环境密钥
+    private String appSecret = "oz4OgKBaMNwi4LWfLPbhrPbbuCS8T0Rb";
+    // 系统编号
+    private String systemId = "10000078";
+    // 模块编号
+    private String moduleId = "20000156";
+    // 业务编码
+    private String busiCode = "30000111";
+}

+ 193 - 0
src/main/java/com/nokia/tsl_data/push_message/service/PushMessageService.java

@@ -0,0 +1,193 @@
+package com.nokia.tsl_data.push_message.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.nokia.tsl_data.push_message.properties.PushMessageProperties;
+
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class PushMessageService {
+
+    private final PushMessageProperties properties;
+    private final RestTemplate restTemplate;
+
+    private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
+
+    public PushMessageService(PushMessageProperties properties, RestTemplate restTemplate) {
+        this.properties = properties;
+        this.restTemplate = restTemplate;
+    }
+
+    /**
+     * 发送文本消息
+     */
+    public void sendTextMessage(String accessToken, String prefix, String message) {
+        sendTextMessage(accessToken, prefix, message, null, null);
+    }
+
+    /**
+     * 发送文本消息
+     */
+    public void sendTextMessage(String accessToken, String prefix, String message, @Nullable List<Integer> atList) {
+        sendTextMessage(accessToken, prefix, message, atList, null);
+    }
+
+    /**
+     * 发送文本消息
+     */
+    public void sendTextMessage(String accessToken, String prefix, String message, @Nullable Boolean atAll) {
+        sendTextMessage(accessToken, prefix, message, null, atAll);
+    }
+
+    /**
+     * 发送文本消息
+     */
+    public void sendTextMessage(String accessToken, String prefix, String message, @Nullable List<Integer> atList,
+                                @Nullable Boolean atAll) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("MSG_TYPE", "text");
+        Map<String, Object> data = new HashMap<>();
+        map.put("DATA", data);
+        Map<String, Object> text = new HashMap<>();
+        data.put("TEXT", text);
+        message = message.startsWith(prefix) ? message : prefix + message;
+        text.put("CONTENT", message);
+        if (atList != null) {
+            data.put("AT_LIST", atList);
+        }
+        if (atAll != null) {
+            data.put("AT_ALL", atAll);
+        }
+        sendMessage(map, accessToken);
+    }
+
+    /**
+     * 发送markdown消息
+     */
+    public void sendMarkdownMessage(String accessToken, String prefix, String message) {
+        sendMarkdownMessage(accessToken, prefix, message, null, null, null);
+    }
+
+    /**
+     * 发送markdown消息
+     */
+    public void sendMarkdownMessage(String accessToken, String prefix, String message, String title) {
+        sendMarkdownMessage(accessToken, prefix, message, title, null, null);
+    }
+
+    /**
+     * 发送markdown消息
+     */
+    public void sendMarkdownMessage(String accessToken, String prefix, String message, List<Integer> atList) {
+        sendMarkdownMessage(accessToken, prefix, message, null, atList, null);
+    }
+
+    /**
+     * 发送markdown消息
+     */
+    public void sendMarkdownMessage(String accessToken, String prefix, String message, @Nullable Boolean atAll) {
+        sendMarkdownMessage(accessToken, prefix, message, null, null, atAll);
+    }
+
+    /**
+     * 发送markdown消息
+     */
+    public void sendMarkdownMessage(String accessToken, String prefix, String message, @Nullable String title,
+                                    @Nullable List<Integer> atList,
+                                    @Nullable Boolean atAll) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("MSG_TYPE", "markdown");
+        Map<String, Object> data = new HashMap<>();
+        map.put("DATA", data);
+        Map<String, Object> markdown = new HashMap<>();
+        data.put("MARK_DOWN", markdown);
+        title = title == null || "".equals(title) ? prefix : title;
+        title = title.startsWith(prefix) ? title : prefix + title;
+        markdown.put("TITLE", title);
+        markdown.put("TEXT", message);
+        if (atList != null) {
+            data.put("AT_LIST", atList);
+        }
+        if (atAll != null) {
+            data.put("AT_ALL", atAll);
+        }
+        sendMessage(map, accessToken);
+    }
+
+    private void sendMessage(Map<String, Object> reqMap, String accessToken) {
+        JsonNode respon = restTemplate.postForObject(properties.getUrl(), getRequest(reqMap, accessToken),
+                JsonNode.class);
+        if (respon == null) {
+            throw new RuntimeException("调用结果为空");
+        }
+        JsonNode node = respon.get("UNI_BSS_HEAD");
+        if ("00000".equals(node.get("RESP_CODE").textValue())) {
+            node = respon.get("UNI_BSS_BODY").get("PUSH_MESSAGE_RSP");
+            if (node != null && "0000".equals(node.get("STATUS").textValue())) {
+                node = node.get("RSP");
+                if (node != null && "0000".equals(node.get("RSP_CODE").textValue())) {
+                    // 只有到这里才说明调用成功了
+                    return;
+                }
+            }
+        }
+        throw new RuntimeException(respon.toString());
+    }
+
+    private Map<String, Object> getRequest(Map<String, Object> reqMap, String accessToken) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("UNI_BSS_HEAD", getUniBssHead());
+        map.put("UNI_BSS_BODY", getUniBssBody(reqMap, accessToken));
+        return map;
+    }
+
+    private Map<String, Object> getUniBssBody(Map<String, Object> reqMap, String accessToken) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("PUSH_MESSAGE_REQ", reqMap);
+        reqMap.put("ACCESS_TOKEN", accessToken);
+        reqMap.put("BUSI_CODE", properties.getBusiCode());
+        reqMap.put("MODULE_ID", properties.getModuleId());
+        reqMap.put("SYSTEM_ID", properties.getSystemId());
+        return map;
+    }
+
+    private Map<String, Object> getUniBssHead() {
+        Map<String, Object> map = new HashMap<>();
+        Date date = new Date();
+        String timeStamp = getTimeStamp(date);
+        String transId = getTransId(date);
+        map.put("APP_ID", properties.getAppId());
+        map.put("TIMESTAMP", timeStamp);
+        map.put("TRANS_ID", transId);
+        map.put("TOKEN", getToken(timeStamp, transId));
+        return map;
+    }
+
+    private String getToken(String timeStamp, String transId) {
+        String beforeEncode = "APP_ID" + properties.getAppId() + "TIMESTAMP" + timeStamp + "TRANS_ID" + transId
+                + properties.getAppSecret();
+        return DigestUtils.md5DigestAsHex(beforeEncode.getBytes());
+    }
+
+    private String getTransId(Date date) {
+        return (new SimpleDateFormat("yyyyMMddHHmmssSSS" + getRandomNumber())).format(date);
+    }
+
+    private String getTimeStamp(Date date) {
+        return dateFormat.format(date);
+    }
+
+    private int getRandomNumber() {
+        return (int) ((Math.random() * 9D + 1D) * 100000D);
+    }
+}

+ 75 - 0
src/main/java/com/nokia/tsl_data/scheduling/entity/RunnableTask.java

@@ -0,0 +1,75 @@
+package com.nokia.tsl_data.scheduling.entity;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Objects;
+
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+import com.nokia.tsl_data.config.SpringContextHolder;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Data
+@Accessors(chain = true)
+public class RunnableTask implements Runnable {
+
+    private String taskId = "";
+    private String beanName;
+    private String methodName;
+    private String methodParameter = "";
+    private Long delay = 0L;
+
+    @Override
+    public void run() {
+        try {
+            Object bean = SpringContextHolder.getBean(beanName);
+            Method method;
+            if (StringUtils.hasLength(methodParameter)) {
+                method = bean.getClass().getMethod(methodName, String.class);
+                ReflectionUtils.makeAccessible(method);
+                method.invoke(bean, methodParameter);
+            } else {
+                method = bean.getClass().getMethod(methodName);
+                ReflectionUtils.makeAccessible(method);
+                method.invoke(bean);
+            }
+        } catch (NoSuchMethodException e) {
+            log.error("未找到方法: {}.{}()", beanName, methodName);
+            e.printStackTrace();
+        } catch (SecurityException e) {
+            log.error("{}.{}() 方法报错 SecurityException", beanName, methodName);
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            log.error("{}.{}() 方法不允许访问", beanName, methodName);
+            e.printStackTrace();
+        } catch (IllegalArgumentException e) {
+            log.error("{}.{}() 方法参数错误", beanName, methodName);
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            log.error("{}.{}() 方法执行出错:{}", beanName, methodName, e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof RunnableTask))
+            return false;
+        RunnableTask t = (RunnableTask) o;
+        return taskId.equals(t.getTaskId())
+                && beanName.equals(t.getBeanName())
+                && methodName.equals(t.getMethodName())
+                && methodParameter.equals(t.getMethodParameter())
+                && delay.equals(t.getDelay());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(taskId, beanName, methodName, methodParameter, delay);
+    }
+}

+ 149 - 0
src/main/java/com/nokia/tsl_data/scheduling/service/TaskScheduleService.java

@@ -0,0 +1,149 @@
+package com.nokia.tsl_data.scheduling.service;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+
+import javax.annotation.PreDestroy;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import com.nokia.tsl_data.scheduling.entity.RunnableTask;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 马上调度或者延时调度
+ */
+@Slf4j
+@Service
+public class TaskScheduleService {
+    /**
+     * 存储任务调度信息
+     */
+    private ConcurrentHashMap<RunnableTask, ScheduledFuture<?>> tasksSchedulingMap = new ConcurrentHashMap<>();
+
+    @Autowired
+    private ThreadPoolTaskScheduler taskScheduler;
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    /**
+     * 调度任务
+     */
+    public void schedule(RunnableTask task) {
+        // 检查任务是否合法
+        checkRunnableTask(task);
+        // 检查是否已调度过
+        if (tasksSchedulingMap.containsKey(task)) {
+            ScheduledFuture<?> future = tasksSchedulingMap.get(task);
+            if (!future.isDone()) {
+                log.error("任务 {} 已调度但未完成,不要重复调度...", task);
+                throw new RuntimeException(String.format("任务 %s 已调度但未完成,不要重复调度...", task));
+            } else {
+                log.debug("任务 {} 已完成,将重新调度...", task);
+            }
+        }
+        // 调度任务
+        Long delay = task.getDelay();
+        if (delay != null) {
+            // 延时调度
+            tasksSchedulingMap.put(task, taskScheduler.schedule(task, Instant.now().plus(Duration.ofSeconds(delay))));
+        } else {
+            // 马上执行
+            tasksSchedulingMap.put(task, taskScheduler.schedule(task, Instant.now()));
+        }
+    }
+
+    /**
+     * 删除任务
+     */
+    public void remove(RunnableTask task) {
+        if (tasksSchedulingMap.contains(task)) {
+            ScheduledFuture<?> future = tasksSchedulingMap.remove(task);
+            if (future.isDone()) {
+                log.debug("要删除的任务 {} 已完成...", task);
+            } else {
+                log.debug("要删除的任务 {} 尚未完成...", task);
+                future.cancel(true);
+            }
+        } else {
+            log.warn("尝试删除不存在的任务 {} ...", task);
+        }
+    }
+
+    /**
+     * 获取正在调度的任务
+     */
+    public List<RunnableTask> listTasksScheduling() {
+        tasksSchedulingMap.forEach((task, future) -> {
+            if (future.isDone()) {
+                log.debug(" {} 任务已完成...", task);
+                tasksSchedulingMap.remove(task);
+            }
+        });
+        return new ArrayList<>(tasksSchedulingMap.keySet());
+    }
+
+    /**
+     * 周期更新任务状态 每10分钟更新任务状态
+     */
+    @Scheduled(cron = "0 0/10 * * * *")
+    public void cronUpdate() {
+        log.debug("开始调度周期更新任务状态...");
+        tasksSchedulingMap.forEach((task, future) -> {
+            if (future.isDone()) {
+                log.debug(" {} 任务已完成...", task);
+                tasksSchedulingMap.remove(task);
+            }
+        });
+        log.debug("周期更新任务状态已完成...");
+    }
+
+    /**
+     * 在退出时结束全部任务
+     * 这里不做记录,也就是说不支持重新加载未完成的任务
+     */
+    @PreDestroy
+    public void cancelAll() {
+        tasksSchedulingMap.forEach((task, future) -> {
+            if (future.isDone()) {
+                log.debug("退出时任务 {} 已完成...", task);
+            } else {
+                log.warn("退出时任务 {} 尚未完成...", task);
+                future.cancel(true);
+            }
+        });
+    }
+
+    /**
+     * 检查任务
+     */
+    public void checkRunnableTask(RunnableTask task) {
+        try {
+            Object bean = applicationContext.getBean(task.getBeanName());
+            if (bean == null) {
+                throw new RuntimeException(String.format("%s 不存在...", task.getBeanName()));
+            }
+            if (!StringUtils.hasLength(task.getMethodParameter())) {
+                bean.getClass().getMethod(task.getMethodName());
+            } else {
+                bean.getClass().getMethod(task.getMethodName(), String.class);
+            }
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(String.format("未找到 %s.%s() ...", task.getBeanName(), task.getMethodName()));
+        } catch (SecurityException e) {
+            throw new RuntimeException(
+                    String.format("%s.%s() 出错: %s", task.getBeanName(), task.getMethodName(), e.getMessage()));
+        }
+    }
+}

+ 138 - 0
src/main/java/com/nokia/tsl_data/service/CemMobileExperienceListService.java

@@ -0,0 +1,138 @@
+package com.nokia.tsl_data.service;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.nokia.tsl_data.dao.CemMobileExperienceListMapper;
+import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
+import com.nokia.tsl_data.dao.TargetTsRatioMapper;
+import com.nokia.tsl_data.exception.LackOfDataRuntimeException;
+import com.nokia.tsl_data.util.DateFormatUtil;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 河北客户体验管理系统移网网络体验明细日信息_HE_D_CEM_MOBILE_EXPERIENCE_LIST
+ */
+@Slf4j
+@Service
+public class CemMobileExperienceListService {
+
+    @Autowired
+    private CemMobileExperienceListMapper cemMobileExperienceListMapper;
+    @Autowired
+    private SysDataDictionaryRepository sysDataDictionaryRepository;
+    @Autowired
+    private TargetTsRatioMapper targetTsRatioRepository;
+    @Autowired
+    private UserCountService userCountService;
+
+    public Map<String, List<Object>> getSheetData(String day) {
+        // 1. 初始化存储
+        Map<String, List<Object>> result = new HashMap<>();
+        // 从1开始的,当前是当月的第几天
+        int dayIndex = Integer.parseInt(day.substring(6, 8));
+        // 初始化每个地市(按固定顺序)每天的工单量
+        for (String area : sysDataDictionaryRepository.findAllCityName()) {
+            List<Object> list = new ArrayList<>();
+            // 20241105 修改 初始化增加一列
+            for (int i = 0; i <= dayIndex; i++) {
+                list.add(0);
+            }
+            result.put(area, list);
+        }
+        // 初始化全省每天工作量
+        List<Object> list0 = new ArrayList<>();
+        for (int i = 0; i <= dayIndex; i++) {
+            list0.add(0);
+        }
+        result.put("全省", list0);
+        // 读取数据库数据
+        // 按照帐期查询数据
+        List<Map<String, Object>> cityCountForStat = cemMobileExperienceListMapper.selectCityCountForStat(day);
+        // 各地市每日
+        for (Map<String, Object> map : cityCountForStat) {
+            int dayId = Integer.parseInt(map.get("day_id").toString());
+            // log.debug("==={}", map);
+            // 20241105 增加对雄安新区 名称的替换
+            String city = map.get("city").toString();
+            if ("雄安新区".equals(city)) {
+                result.get("雄安").set(dayId - 1, map.get("num"));
+            }else {
+                result.get(city).set(dayId - 1, map.get("num"));
+            }
+        }
+        List<Map<String, Object>> allforStat = cemMobileExperienceListMapper.selectAllCountForStat(day);
+        // 全省每日
+        for (Map<String, Object> map : allforStat) {
+            int dayId = Integer.parseInt(map.get("day_id").toString());
+            result.get("全省").set(dayId - 1, map.get("num"));
+        }
+        // 地市总数
+        List<Map<String, Object>> cityAllForStat = cemMobileExperienceListMapper.selectCityAllForStat(day);
+        for (Map<String, Object> map : cityAllForStat) {
+            // 20241105 从add 该为set 因为在前面已初始化
+            // 20241105 增加对雄安新区 名称的替换
+            String city = map.get("city").toString();
+            if ("雄安新区".equals(city)) {
+                result.get("雄安").set(dayIndex, map.get("num"));
+            }else {
+                result.get(city).set(dayIndex, map.get("num"));
+            }
+        }
+        // 全省总数
+        int total = cemMobileExperienceListMapper.selectCountForStat(day);
+        result.get("全省").set(dayIndex, total);
+        // 3. 读取数据库 当月管理端用户数
+        Map<String, Double> userCount = userCountService.getManagementUserCountForMonth(day.substring(0, 6));
+        // 管理端用户数写入结果
+        for (Entry<String, List<Object>> entry : result.entrySet()) {
+            entry.getValue().add(userCount.get(entry.getKey()));
+        }
+        // 4. 万投率和本月预测
+        Calendar calendar = DateFormatUtil.toCalendar(day, "yyyyMMdd");
+        for (Entry<String, List<Object>> entry : result.entrySet()) {
+            List<Object> list = entry.getValue();
+            int size = list.size();
+            // 计算万投率
+            double wtl = Double.parseDouble(list.get(size - 2).toString())
+                    / Double.parseDouble(list.get(size - 1).toString());
+            list.add(wtl);
+            // 计算预测万投率
+            double ycwtl = wtl * calendar.getActualMaximum(Calendar.DAY_OF_MONTH) / calendar.get(Calendar.DAY_OF_MONTH);
+            list.add(ycwtl);
+        }
+        // 5. 期望万投率
+        List<Map<String, Object>> targetTsRatios = targetTsRatioRepository.findByMonthId(day.substring(0, 6));
+        for (Map<String, Object> targetTsRatio : targetTsRatios) {
+            result.get(targetTsRatio.get("city_name").toString())
+                    .add(targetTsRatio.get("service_request_target_ratio"));
+        }
+        // 6. 与目标差距
+        for (Entry<String, List<Object>> entry : result.entrySet()) {
+            List<Object> list = entry.getValue();
+            int size = list.size();
+            double cj = ((double) list.get(size - 2)) - ((double) list.get(size - 1));
+            list.add(cj);
+        }
+        log.debug("==={}", result);
+        return result;
+    }
+
+    /**
+     * 按照账期检查数据
+     */
+    public void checkStatDayCount(String day) {
+        int count = cemMobileExperienceListMapper.countForDay(day);
+        if (count == 0) {
+            throw new LackOfDataRuntimeException(String.format("cemMobileExperienceList 表缺少账期 %s 数据", day));
+        }
+    }
+}

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

@@ -0,0 +1,431 @@
+package com.nokia.tsl_data.service;
+
+import com.nokia.tsl_data.dao.*;
+import com.nokia.tsl_data.properties.TslDataProperties;
+import com.nokia.tsl_data.util.CodecUtil;
+import com.nokia.tsl_data.util.TextUtil;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 从能力商店订阅的表入库
+ */
+@Slf4j
+@Service
+public class DataWarehouseService {
+
+    @Autowired
+    private TslDataDao tslDataDao;
+    @Autowired
+    private MobileComplaintMapper mobileComplaintMapper;
+    @Autowired
+    private HighQualityCountMapper highQualityCountMapper;
+    @Autowired
+    private HighQualityListDayMapper highQualityListDayMapper;
+    @Autowired
+    private ComplaintDetailsFixYwdDayMapper complaintDetailsFixYwdDayMapper;
+    @Autowired
+    private CemMobileExperienceListMapper cemMobileExperienceListMapper;
+    @Autowired
+    private ManagementDetailMapper managementDetailMapper;
+    @Autowired
+    private SysDataDictionaryRepository sysDataDictionaryRepository;
+    @Autowired
+    private TslDataProperties tslDataProperties;
+
+    /**
+     * 入库前检查数据源是否具备
+     */
+    public String checkSource(String day) {
+        StringBuffer stringBuffer = new StringBuffer();
+        // 注意,只有文件已存在时才能 checkMD5, 否则读取MD5文件时必出异常
+        // 河北_CEM移网质量投诉明细
+        File mobileComplaintDayFile = getMobileComplaintDayFile(day);
+        if (!mobileComplaintDayFile.exists()) {
+            stringBuffer.append("河北_CEM移网质量投诉明细_HE_D_MOBILE_COMP表账期 ").append(day).append(" 数据未到达\n");
+        } else if (!checkMD5(mobileComplaintDayFile)) {
+            stringBuffer.append("河北_CEM移网质量投诉明细_HE_D_MOBILE_COMP表账期 ").append(day).append(" MD5验证未通过\n");
+        }
+        // 河北_CEM高品质2日明细
+        File highQualityListDayFile = getHighQualityListDayFile(day);
+        if (!highQualityListDayFile.exists()) {
+            stringBuffer.append("河北_CEM高品质2日明细_HE_D_HIGH_QUALITY_LIST表账期 ").append(day).append(" 数据未到达\n");
+        } else if (!checkMD5(highQualityListDayFile)) {
+            stringBuffer.append("河北_CEM高品质2日明细_HE_D_HIGH_QUALITY_LIST表账期 ").append(day).append(" MD5验证未通过\n");
+        }
+        // 河北_CEM高品质2日统计
+        File highQualityCountDayFile = getHighQualityCountDayFile(day);
+        if (!highQualityCountDayFile.exists()) {
+            stringBuffer.append("河北_CEM高品质2日统计_HE_D_HIGH_QUALITY_COUNT表账期 ").append(day).append(" 数据未到达\n");
+        } else if (!checkMD5(highQualityCountDayFile)) {
+            stringBuffer.append("河北_CEM高品质2日统计_HE_D_HIGH_QUALITY_COUNT表账期 ").append(day).append(" MD5验证未通过\n");
+        }
+        // 河北客户体验管理智能定责投诉明细月累计接口日
+        File complaintDetailsFixYwdDayFile = getComplaintDetailsFixYwdDayFile(day);
+        if (!complaintDetailsFixYwdDayFile.exists()) {
+            stringBuffer.append("河北客户体验管理智能定责投诉明细月累计接口日 表账期 ").append(day).append(" 数据未到达\n");
+        } else if (!checkMD5(complaintDetailsFixYwdDayFile)) {
+            stringBuffer.append("河北客户体验管理智能定责投诉明细月累计接口日 表账期 ").append(day).append(" MD5验证未通过\n");
+        }
+        // 河北客户体验管理系统移网网络体验明细日信息
+        // 20241101 修改  20241105恢复
+        File cemMobileExperienceListFile = getCemMobileExperienceListFile(day);
+        if (!cemMobileExperienceListFile.exists()) {
+            stringBuffer.append("河北客户体验管理系统移网网络体验明细日信息 表账期 ").append(day).append(" 数据未到达\n");
+        } else if (!checkMD5(cemMobileExperienceListFile)) {
+            stringBuffer.append("河北客户体验管理系统移网网络体验明细日信息 表账期 ").append(day).append(" MD5验证未通过\n");
+        }
+        return stringBuffer.toString();
+    }
+
+    /**
+     * 入库全部文件的任务
+     */
+    public void warehouse(String day) {
+        int count;
+        if ((count = highQualityCountMapper.countForDay(day)) == 0) {
+            warehouseHighQualityCountDay(day);
+        } else {
+            log.warn("HighQualityCount 账期 {} 已存在 {} 条数据,已跳过...", day, count);
+        }
+        if ((count = highQualityListDayMapper.countForDay(day)) == 0) {
+            warehouseHighQualityListDay(day);
+        } else {
+            log.warn("HighQualityList 账期 {} 已存在 {} 条数据,已跳过...", day, count);
+        }
+        if ((count = mobileComplaintMapper.countForDay(day)) == 0) {
+            warehouseMobileComplaintDay(day);
+        } else {
+            log.warn("MobileComplaint 账期 {} 已存在 {} 条数据,已跳过...", day, count);
+        }
+        if ((count = complaintDetailsFixYwdDayMapper.countForDay(day)) == 0) {
+            warehouseComplaintDetailsFixYwdDay(day);
+        } else {
+            log.warn("complaintDetailsFixYwdDay 账期 {} 已存在 {} 条数据,已跳过...", day, count);
+        }
+        // 20241101 调整  20241105恢复
+        if ((count = cemMobileExperienceListMapper.countForDay(day)) == 0) {
+            warehouseCemMobileExperienceList(day);
+        } else {
+            log.warn("CemMobileExperienceList 账期 {} 已存在 {} 条数据,已跳过...", day, count);
+        }
+    }
+
+    /**
+     * 删除 河北_CEM移网质量投诉明细 数据
+     */
+    public int deleteMobileComplaintDay(String day) {
+        return mobileComplaintMapper.deleteMobileCompForDay(day);
+    }
+
+    /**
+     * 删除 河北_CEM高品质2日统计 数据
+     */
+    public int deleteHighQualityCountDay(String day) {
+        return highQualityCountMapper.deleteHighQualityCountForDay(day);
+    }
+
+    /**
+     * 删除 河北_CEM高质量2日明细 数据
+     */
+    public int deleteHighQualityListDay(String day) {
+        return highQualityListDayMapper.deleteHighQualityListForDay(day);
+    }
+
+    /**
+     * 删除 河北客户体验管理智能定责投诉明细月累计接口日
+     */
+    public int deleteComplaintDetailsFixYwdDay(String day) {
+        return tslDataDao.deleteComplaintDetailsFixYwdDay(day);
+    }
+
+    /**
+     * 删除 河北客户体验管理系统移网网络体验明细日信息
+     */
+    public int deleteCemMobileExperienceList(String day) {
+        return tslDataDao.deleteCemMobileExperienceList(day);
+    }
+
+    /**
+     * 入库 河北高质量2日明细数据
+     */
+    public void warehouseHighQualityListDay(String day) {
+        // 1. 检查是否已存在数据
+        int countForDay = highQualityListDayMapper.countForDay(day);
+        if (countForDay != 0) {
+            throw new RuntimeException(
+                String.format("HighQualityListDay 帐期 %s 已存在 %s 条数据", day, countForDay));
+        }
+        // 2. 入库新数据
+        warehouseHighQualityListDay(getHighQualityListDayFile(day));
+    }
+
+    /**
+     * 入库 河北_CEM移网质量投诉明细
+     */
+    public void warehouseMobileComplaintDay(String day) {
+        // 1. 检查是否已存在数据
+        int countForDay = mobileComplaintMapper.countForDay(day);
+        if (countForDay != 0) {
+            throw new RuntimeException(
+                String.format("MobileComplaintDay 帐期 %s 已存在 %s 条数据", day, countForDay));
+        }
+        // 2. 入库新数据
+        warehouseMobileComplaintDay(getMobileComplaintDayFile(day));
+    }
+
+    /**
+     * 入库 河北_CEM高品质2日统计
+     */
+    public void warehouseHighQualityCountDay(String day) {
+        // 1. 检查是否已存在数据
+        int countForDay = highQualityCountMapper.countForDay(day);
+        if (countForDay != 0) {
+            throw new RuntimeException(
+                String.format("HighQualityCountDay 帐期 %s 已存在 %s 条数据", day, countForDay));
+        }
+        // 2. 入库新数据
+        warehouseHighQualityCountDay(getHighQualityCountDayFile(day));
+    }
+
+    /**
+     * 入库 河北客户体验管理智能定责投诉明细月累计接口日 数据
+     */
+    public void warehouseComplaintDetailsFixYwdDay(String day) {
+        // 1. 检查是否已存在数据
+        int countForDay = complaintDetailsFixYwdDayMapper.countForDay(day);
+        if (countForDay != 0) {
+            throw new RuntimeException(
+                String.format("ComplaintDetailsFixYwdDay 帐期 %s 已存在 %s 条数据", day, countForDay));
+        }
+        // 2. 入库新数据
+        warehouseComplaintDetailsFixYwdDay(getComplaintDetailsFixYwdDayFile(day));
+    }
+
+    /**
+     * 入库 河北客户体验管理系统移网网络体验明细日信息
+     */
+    public void warehouseCemMobileExperienceList(String day) {
+        // 1. 检查是否已存在数据
+        int countForDay = cemMobileExperienceListMapper.countForDay(day);
+        if (countForDay != 0) {
+            throw new RuntimeException(
+                String.format("CemMobileExperienceList 帐期 %s 已存在 %s 条数据", day, countForDay));
+        }
+        // 2. 入库新数据
+        warehouseCemMobileExperienceList(getCemMobileExperienceListFile(day));
+    }
+
+    private void warehouseHighQualityListDay(File file) {
+        // 读取文件
+        try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
+            // SOH作为分割符
+            char delimiter = 1;
+            CSVParser parser = CSVFormat.DEFAULT.builder()
+                    .setRecordSeparator("\n")
+                    .setQuote(null)
+                    .setDelimiter(delimiter)
+                    .setSkipHeaderRecord(false)
+                    .build().parse(reader);
+            List<CSVRecord> records = parser.getRecords();
+            List<Object[]> list = new ArrayList<>();
+            // 查找全部需要筛选的类型
+            Set<String> allServTypeNames = sysDataDictionaryRepository.findAllServTypeNames();
+            for (CSVRecord record : records) {
+                // 按照serv_type_name进行筛选
+                if (allServTypeNames.contains(record.get(9))) {
+                    Object[] ps = new Object[tslDataProperties.getFiledNumOfHighQualityListDay().intValue()];
+                    list.add(ps);
+                    for (int i = 0; i < ps.length; i++) {
+                        ps[i] = record.get(i);
+                    }
+                }
+            }
+            tslDataDao.batchInsertHighQualityListDay(list);
+            log.debug("河北_CEM高质量2日明细 数据入库成功...");
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException("河北_CEM高质量2日明细 数据入库失败..." + e.getMessage());
+        }
+    }
+
+    private void warehouseMobileComplaintDay(File file) {
+        try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
+            // SOH作为分割符
+            char delimiter = 1;
+            CSVParser parser = CSVFormat.DEFAULT.builder()
+                    .setRecordSeparator("\n")
+                    .setQuote(null)
+                    .setDelimiter(delimiter)
+                    .setSkipHeaderRecord(false)
+                    .build().parse(reader);
+            List<CSVRecord> records = parser.getRecords();
+            List<Object[]> list = new ArrayList<>();
+            for (CSVRecord record : records) {
+                Object[] ps = new Object[tslDataProperties.getFiledNumOfMobileComplaintDay().intValue()];
+                list.add(ps);
+                for (int i = 0; i < ps.length; i++) {
+                    ps[i] = record.get(i);
+                }
+            }
+            tslDataDao.batchInsertMobileComplaintDay(list);
+            log.debug("河北_CEM移网质量投诉明细 数据入库成功...");
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException("河北_CEM移网质量投诉明细 数据入库失败..." + e.getMessage());
+        }
+    }
+
+    private void warehouseHighQualityCountDay(File file) {
+        try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
+            // SOH作为分割符
+            char delimiter = 1;
+            CSVParser parser = CSVFormat.DEFAULT.builder()
+                    .setRecordSeparator("\n")
+                    .setQuote(null)
+                    .setDelimiter(delimiter)
+                    .setSkipHeaderRecord(false)
+                    .build().parse(reader);
+            List<CSVRecord> records = parser.getRecords();
+            List<Object[]> list = new ArrayList<>();
+            for (CSVRecord record : records) {
+                Object[] ps = new Object[tslDataProperties.getFiledNumOfHighQualityCountDay().intValue()];
+                list.add(ps);
+                for (int i = 0; i < ps.length; i++) {
+                    ps[i] = record.get(i);
+                }
+            }
+            tslDataDao.batchInsertHighQualityCountDay(list);
+            log.debug("河北_CEM高品质2日统计 数据入库成功...");
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException("河北_CEM高品质2日统计数据入库失败..." + e.getMessage());
+        }
+    }
+
+    // 河北客户体验管理智能定责投诉明细月累计接口日
+    private void warehouseComplaintDetailsFixYwdDay(File file) {
+        try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
+            // SOH作为分割符
+            char delimiter = 1;
+            CSVParser parser = CSVFormat.DEFAULT.builder()
+                    .setRecordSeparator("\n")
+                    .setQuote(null)
+                    .setDelimiter(delimiter)
+                    .setSkipHeaderRecord(false)
+                    .build().parse(reader);
+            List<CSVRecord> records = parser.getRecords();
+            List<Object[]> list = new ArrayList<>();
+            for (CSVRecord record : records) {
+                Object[] ps = new Object[tslDataProperties.getFiledNumOfComplaintDetailsFixYwdDay().intValue()];
+                list.add(ps);
+                for (int i = 0; i < ps.length; i++) {
+                    ps[i] = record.get(i);
+                }
+            }
+            tslDataDao.batchInsertComplaintDetailsFixYwdDay(list);
+            log.debug("河北客户体验管理智能定责投诉明细月累计接口日 数据入库成功...");
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException("河北客户体验管理智能定责投诉明细月累计接口日..." + e.getMessage());
+        }
+    }
+
+    // 河北客户体验管理系统移网网络体验明细日信息
+    private void warehouseCemMobileExperienceList(File file) {
+        try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
+            // SOH作为分割符
+            char delimiter = 1;
+            CSVParser parser = CSVFormat.DEFAULT.builder()
+                    .setRecordSeparator("\n")
+                    .setQuote(null)
+                    .setDelimiter(delimiter)
+                    .setSkipHeaderRecord(false)
+                    .build().parse(reader);
+            List<CSVRecord> records = parser.getRecords();
+            List<Object[]> list = new ArrayList<>();
+            for (CSVRecord record : records) {
+                Object[] ps = new Object[tslDataProperties.getFiledNumOfCemMobileExperienceList().intValue()];
+                list.add(ps);
+                for (int i = 0; i < ps.length; i++) {
+                    ps[i] = record.get(i);
+                }
+            }
+            tslDataDao.batchInsertCemMobileExperienceList(list);
+            log.debug("河北客户体验管理系统移网网络体验明细日信息 数据入库成功...");
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException("河北客户体验管理系统移网网络体验明细日信息..." + e.getMessage());
+        }
+    }
+
+    private File getMobileComplaintDayFile(String day) {
+        String fileName = tslDataProperties.getPrefixOfMobileComplaint() + day + ".csv";
+        return Paths.get(tslDataProperties.getDirOfMobileComplaint(), fileName).toFile();
+    }
+
+    private File getHighQualityListDayFile(String day) {
+        String fileName = tslDataProperties.getPrefixOfHighQualityListDay() + day + ".csv";
+        return Paths.get(tslDataProperties.getDirOfHighQualityListDay(), fileName).toFile();
+    }
+
+    private File getHighQualityCountDayFile(String day) {
+        String fileName = tslDataProperties.getPrefixOfHighQualityCountDay() + day + ".csv";
+        return Paths.get(tslDataProperties.getDirOfHighQualityCountDay(), fileName).toFile();
+    }
+
+    // 河北客户体验管理智能定责投诉明细月累计接口日
+    private File getComplaintDetailsFixYwdDayFile(String day) {
+        String fileName = tslDataProperties.getPrefixOfComplaintDetailsFixYwdDay() + day + ".csv";
+        return Paths.get(tslDataProperties.getDirOfComplaintDetailsFixYwdDay(), fileName).toFile();
+    }
+
+    // 河北客户体验管理系统移网网络体验明细日信息
+    private File getCemMobileExperienceListFile(String day) {
+        String fileName = tslDataProperties.getPrefixOfCemMobileExperienceList() + day + ".csv";
+        return Paths.get(tslDataProperties.getDirOfCemMobileExperienceList(), fileName).toFile();
+    }
+
+    /**
+     * 验证文件的MD5
+     */
+    private boolean checkMD5(File file) {
+        String md5String = TextUtil.readLinesWithUTF8(file.getAbsolutePath() + ".MD5").get(0);
+        String md5OfFile = CodecUtil.MD5OfFile(file);
+        return md5String.equals(md5OfFile);
+    }
+
+    /**
+     * 更新 ManagementDetail
+     */
+    public void updateManagementDetail(String day) {
+        // 1. 检查是否已存在数据
+        int countForDay = managementDetailMapper.selectCountofStatDay(day);
+        if (countForDay != 0) {
+            throw new RuntimeException(
+                String.format("ManagementDetail 帐期 %s 已存在 %s 条数据", day, countForDay));
+        }
+        // 2. 入库新数据
+        managementDetailMapper.insertFromMobileComplaint(day);
+        managementDetailMapper.insertFromComplaintDetailsFixYwd(day);
+    }
+
+    /**
+     * 删除 ManagementDetail
+     */
+    public int deleteManagementDetail(String day) {
+        return managementDetailMapper.deleteForStatDay(day);
+    }
+}

+ 269 - 0
src/main/java/com/nokia/tsl_data/service/HighQualityCountService.java

@@ -0,0 +1,269 @@
+package com.nokia.tsl_data.service;
+
+import com.nokia.tsl_data.dao.HighQualityCountMapper;
+import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
+import com.nokia.tsl_data.properties.TslDataProperties;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 处理客户端统计数据
+ */
+@Service
+public class HighQualityCountService {
+
+    @Autowired
+    private HighQualityCountMapper highQualityCountMapper;
+    @Autowired
+    private SysDataDictionaryRepository sysDataDictionaryRepository;
+    @Autowired
+    private TslDataProperties tslDataProperties;
+
+    public List<List<Object>> getThreeRateOfCityV3(String day) {
+        List<List<Object>> result = new ArrayList<>();
+        // 各地市
+        for (String cityName : sysDataDictionaryRepository.findAllCityName()) {
+            List<Object> list = new ArrayList<>();
+            list.add(cityName);
+            result.add(list);
+        }
+        // 顺序是 满意率 解决率 响应率
+        Map<String, Double[]> map = generateThreeRateOfCityV3(day);
+        result.forEach(list -> {
+            Double[] arr = map.get(list.get(0).toString());
+            list.add(arr[0]);
+            list.add(arr[1]);
+            list.add(arr[2]);
+            // 移网综合投诉评价“三率”=响应率*0.1+解决率*0.4+满意率*0.5
+            double rate = arr[2] * 0.1 + arr[1] * 0.4 + arr[0] * 0.5;
+            list.add(rate);
+            list.add(tslDataProperties.getEvaluation());
+            list.add(tslDataProperties.getEvaluation() - rate);
+        });
+        // 逆序排序
+        result.sort((o1, o2) -> Double.compare((double) o2.get(6), (double) o1.get(6)));
+        // 全省
+        Double[] allCity = map.get("全省");
+        double rate = allCity[2] * 0.1 + allCity[1] * 0.4 + allCity[0] * 0.5;
+        result.add(new ArrayList<Object>() {
+            {
+                add("全省");
+                add(allCity[0]);
+                add(allCity[1]);
+                add(allCity[2]);
+                add(rate);
+                add(tslDataProperties.getEvaluation());
+                add(tslDataProperties.getEvaluation() - rate);
+            }
+        });
+        return result;
+    }
+
+    /**
+     * 客户端地市满意度 客户端地市解决率 客户端地市响应率
+     */
+    public Map<String, Double[]> generateThreeRateOfCityV3(String day) {
+        // 顺序是 满意率 解决率 响应率
+        Map<String, Double[]> result = new HashMap<>();
+        for (String cityName : sysDataDictionaryRepository.findAllCityName()) {
+            Double[] arr = new Double[6];
+            Arrays.fill(arr, 1.0d);
+            result.put(cityName, arr);
+        }
+        Double[] allCity = new Double[3];
+        result.put("全省", allCity);
+        double numerator = 0, denominator = 0;
+        // 客户端地市满意率
+        List<Map<String, Object>> satisfiedOfCity = highQualityCountMapper.selectSatisfiedOfCity(day);
+        for (Map<String, Object> map : satisfiedOfCity) {
+            String key = (String) map.get("city");
+            Double[] arr;
+            if (key.equals("雄安新区")) {
+                arr = result.get("雄安");
+            } else {
+                arr = result.get(key);
+            }
+            arr[0] = (double) map.get("rate");
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+        }
+        allCity[0] = numerator / denominator;
+        // 客户端地市解决率
+        numerator = 0;
+        denominator = 0;
+        List<Map<String, Object>> resolutionOfCity = highQualityCountMapper.selectResolutionOfCity(day);
+        for (Map<String, Object> map : resolutionOfCity) {
+            String key = (String) map.get("city");
+            Double[] arr;
+            if (key.equals("雄安新区")) {
+                arr = result.get("雄安");
+            } else {
+                arr = result.get(key);
+            }
+            arr[1] = (double) map.get("rate");
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+        }
+        allCity[1] = numerator / denominator;
+        // 客户端地市响应率
+        numerator = 0;
+        denominator = 0;
+        List<Map<String, Object>> responseOfCity = highQualityCountMapper.selectResponseOfCity(day);
+        for (Map<String, Object> map : responseOfCity) {
+            String key = (String) map.get("city");
+            Double[] arr;
+            if (key.equals("雄安新区")) {
+                arr = result.get("雄安");
+            } else {
+                arr = result.get(key);
+            }
+            arr[2] = (double) map.get("rate");
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+        }
+        allCity[2] = numerator / denominator;
+
+        return result;
+    }
+
+    /**
+     * 客户端地市满意度 客户端地市解决率 客户端地市响应率
+     */
+    public Map<String, List<Object>> generateThreeRateOfCity(String day) {
+        // 全部地市
+        List<String> allCityName = sysDataDictionaryRepository.findAllCityName();
+        Map<String, List<Object>> result = new HashMap<>();
+        for (String cityName : allCityName) {
+            List<Object> list = new ArrayList<>();
+            list.add(cityName);
+            result.put(cityName, list);
+        }
+        List<Object> allCity = new ArrayList<>();
+        allCity.add("全省");
+        result.put("全省", allCity);
+        double numerator = 0, denominator = 0, rate;
+        // 客户端地市满意度
+        List<Map<String, Object>> satisfiedOfCity = highQualityCountMapper.selectSatisfiedOfCity(day);
+        for (Map<String, Object> map : satisfiedOfCity) {
+            String key = (String) map.get("city");
+            List<Object> list;
+            if (key.equals("雄安新区")) {
+                list = result.get("雄安");
+            } else {
+                list = result.get(key);
+            }
+            list.add(map.get("rate"));
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+            list.add(tslDataProperties.getSatisfiedCompliance());
+            list.add((double) map.get("rate") - tslDataProperties.getSatisfiedCompliance());
+        }
+        rate = numerator / denominator;
+        allCity.add(rate);
+        allCity.add(tslDataProperties.getSatisfiedCompliance());
+        allCity.add(rate - tslDataProperties.getSatisfiedCompliance());
+        // 客户端地市解决率
+        numerator = 0;
+        denominator = 0;
+        List<Map<String, Object>> resolutionOfCity = highQualityCountMapper.selectResolutionOfCity(day);
+        for (Map<String, Object> map : resolutionOfCity) {
+            String key = (String) map.get("city");
+            List<Object> list;
+            if (key.equals("雄安新区")) {
+                list = result.get("雄安");
+            } else {
+                list = result.get(key);
+            }
+            list.add(map.get("rate"));
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+            list.add(tslDataProperties.getResolutionCompliance());
+            list.add((double) map.get("rate") - tslDataProperties.getResolutionCompliance());
+        }
+        rate = numerator / denominator;
+        allCity.add(rate);
+        allCity.add(tslDataProperties.getResolutionCompliance());
+        allCity.add(rate - tslDataProperties.getResolutionCompliance());
+        // 客户端地市响应率
+        numerator = 0;
+        denominator = 0;
+        List<Map<String, Object>> responseOfCity = highQualityCountMapper.selectResponseOfCity(day);
+        for (Map<String, Object> map : responseOfCity) {
+            String key = (String) map.get("city");
+            List<Object> list;
+            if (key.equals("雄安新区")) {
+                list = result.get("雄安");
+            } else {
+                list = result.get(key);
+            }
+            list.add(map.get("rate"));
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+            list.add(tslDataProperties.getResponseCompliance());
+            list.add((double) map.get("rate") - tslDataProperties.getResponseCompliance());
+        }
+        rate = numerator / denominator;
+        allCity.add(rate);
+        allCity.add(tslDataProperties.getResponseCompliance());
+        allCity.add(rate - tslDataProperties.getResponseCompliance());
+        return result;
+    }
+
+    /**
+     * 客户端地市和全省按天级的统计数据
+     */
+    public Map<String, int[]> getComplaintsForDay(String day) {
+        // 预处理时间
+        String monthId = day.substring(0, 6);
+        int dayIndex = Integer.parseInt(day.substring(6));
+
+        List<String> allCityName = sysDataDictionaryRepository.findAllCityName();
+        Map<String, int[]> result = new HashMap<>();
+        // 每个地市一个数组,数组保存dayIndex天数据
+        for (String areaName : allCityName) {
+            result.put(areaName, new int[dayIndex + 1]);
+        }
+        // 全省
+        result.put("全省", new int[dayIndex + 1]);
+        for (int i = 1; i <= dayIndex; i++) {
+            // 拼接一个表示天的字符串,天如果小于10要补0
+            String newDay = i >= 10 ? monthId + i : monthId + "0" + i;
+            // 这里有一个查询操作
+            List<Map<String, Object>> totalComplaintsForDay = highQualityCountMapper
+                    .selectTotalComplaintsForDay(newDay);
+            if (totalComplaintsForDay.size() == 0) {
+                throw new RuntimeException(String.format("HighQualityCount 缺少 %s 账期数据", newDay));
+            }
+            int provinceAll = 0;
+            for (Map<String, Object> map : totalComplaintsForDay) {
+                String city = map.get("businoareaname").toString();
+                // 这里雄安新区需要特殊处理一下
+                city = city.equals("雄安新区") ? "雄安" : city;
+                int[] arr = result.get(city);
+                arr[i - 1] = Integer.parseInt(map.get("total_complaints").toString());
+                provinceAll += arr[i - 1];
+                if (i == dayIndex) {
+                    arr[dayIndex] = arr[dayIndex - 1];
+                }
+            }
+            result.get("全省")[i - 1] = provinceAll;
+            if (i == dayIndex) {
+                result.get("全省")[dayIndex] = provinceAll;
+            }
+        }
+        // 以上得到的result是每天累加,需要计算一下每天的增量
+        for (int[] arr : result.values()) {
+            for (int i = dayIndex - 1; i >= 1; i--) {
+                arr[i] = arr[i] - arr[i - 1];
+            }
+        }
+        return result;
+    }
+}

+ 219 - 0
src/main/java/com/nokia/tsl_data/service/HighQualityDataService.java

@@ -0,0 +1,219 @@
+package com.nokia.tsl_data.service;
+
+import com.nokia.tsl_data.dao.HighQualityDataMapper;
+import com.nokia.tsl_data.dao.HighQualityDataRepository;
+import com.nokia.tsl_data.dao.HighQualityListDayMapper;
+import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
+import com.nokia.tsl_data.entity.HighQualityData;
+import com.nokia.tsl_data.properties.TslDataProperties;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 根据高质量明细表生产数据
+ */
+@Slf4j
+@Service
+public class HighQualityDataService {
+
+    @Autowired
+    private HighQualityDataRepository highQualityDataRepository;
+    @Autowired
+    private HighQualityDataMapper highQualityDataMapper;
+    @Autowired
+    private HighQualityListDayMapper highQualityListDayMapper;
+    @Autowired
+    private SysDataDictionaryRepository sysDataDictionaryRepository;
+    @Autowired
+    private TslDataProperties tslDataProperties;
+
+    /**
+     * 根据商定的算法生成数据
+     */
+    public void generateHighQualityData(String day) {
+        // 1. 获取数据
+        int countByDayId = highQualityDataRepository.countByDayId(day);
+        if (countByDayId != 0) {
+            throw new RuntimeException(
+                    String.format("HighQualityData 帐期 %s 已存在 %s 条数据,需要先清除才能重新计算...", day, countByDayId));
+        }
+        List<HighQualityData> highQualityData = highQualityListDayMapper.selectHighQualityDataOfDay(day);
+        // 保存到 high_quality_data 表
+        highQualityDataRepository.saveAll(highQualityData);
+        log.debug("HighQualityData {} 账期插入 {} 条数据...", day, highQualityData.size());
+        // 2. 从 WorkFlowBasicData 更新地市/区县信息
+        int countOfCityUpdated = highQualityDataMapper.updateHighQualityDataCityFromWorkFlowBasicData(day);
+        int countOfRegionUpdated = highQualityDataMapper.updateHighQualityDataRegionFromWorkFlowBasicData(day);
+        log.debug("根据 workflow 信息更新地市 {} 条, 更新区县 {} 条...", countOfCityUpdated, countOfRegionUpdated);
+        // 3. 按照HighQualityList 更新地市/区县信息
+        int countOfCityUpdated2 = highQualityDataMapper.updateHighQualityDataCityFromHighQualityListDay(day);
+        int countOfRegionUpdated2 = highQualityDataMapper.updateHighQualityDataRegionFromHighQualityListDay(day);
+        log.debug("根据 HighQualityList 信息更新地市 {} 条, 更新区县 {} 条...", countOfCityUpdated2, countOfRegionUpdated2);
+        // 4. 仍无数据的配置默认值
+        int countOfCityUpdated3 = highQualityDataMapper.updateHighQualityDataRegionFromDefault(day);
+        log.debug("根据 默认规则 更新区县 {} 条...", countOfCityUpdated3);
+    }
+
+    /**
+     * 计算区县投诉问题解决率
+     */
+    public List<List<Object>> generateCpIsOkOfRegion(String day) {
+        // 地市 区县 解决率 达标值 与达标值差距 本月累计不满意工单量
+        // 1. 获取全部的地市和区县
+        Map<String, List<Object>> result = sysDataDictionaryRepository.findAllRegion();
+        // 2. 解决率
+        List<Map<String, Object>> maps = highQualityDataMapper.selectRateOfCpIsOkOfRegion(day);
+        // 第一次遍历,将工单不为0的区县列出
+        for (Map<String, Object> map : maps) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 高品质详表存在的 区县{} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Double.parseDouble(map.get("rate").toString()));
+            }
+        }
+        // 第二次遍历,补充工单为0的区县,响应率默认100%
+        // 3. 满意度达标值 与达标值差距
+        for (List<Object> list : result.values()) {
+            if (list.size() == 2) {
+                list.add(1.0);
+            }
+            // 写入达标值
+            list.add(tslDataProperties.getResolutionCompliance());
+            // 写入与达标值的差距
+            list.add((double) list.get(2) - (double) list.get(3));
+        }
+        // 4. 本月累计不满意工单量
+        List<Map<String, Object>> maps1 = highQualityDataMapper.selectCountOfCpNotOkOfRegion(day);
+        for (Map<String, Object> map : maps1) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 累计未解决工单量 存在的区县 {} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Integer.parseInt(map.get("count").toString()));
+            }
+        }
+        for (List<Object> list : result.values()) {
+            // 对于没有不满意工单的,直接补0
+            if (list.size() == 5) {
+                list.add(0);
+            }
+        }
+        List<List<Object>> list = new ArrayList<>(result.values());
+        list.sort(Comparator.comparingDouble(o -> (double) o.get(4)));
+        return list;
+    }
+
+    /**
+     * 计算区县投诉问题满意度
+     */
+    public List<List<Object>> generateCpSatisfactionOfRegion(String day) {
+        // 地市 区县 满意度 达标值 与达标值差距 本月累计不满意工单量
+        // 1. 获取全部的地市和区县
+        Map<String, List<Object>> result = sysDataDictionaryRepository.findAllRegion();
+        // 2. 满意度
+        List<Map<String, Object>> maps = highQualityDataMapper.selectRateOfCpSatisfactionOfRegion(day);
+        // 第一次遍历,将工单不为0的区县列出
+        for (Map<String, Object> map : maps) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 高品质详表存在的 区县{} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Double.parseDouble(map.get("rate").toString()));
+            }
+        }
+        // 第二次遍历,补充工单为0的区县,响应率默认100%
+        // 3. 满意度达标值 与达标值差距
+        for (List<Object> list : result.values()) {
+            if (list.size() == 2) {
+                list.add(1.0);
+            }
+            // 写入达标值
+            list.add(tslDataProperties.getSatisfiedCompliance());
+            // 写入与达标值的差距
+            list.add((double) list.get(2) - (double) list.get(3));
+        }
+        // 4. 本月累计不满意工单量
+        List<Map<String, Object>> maps1 = highQualityDataMapper.selectCountOfCpNotSatisfactionOfRegion(day);
+        for (Map<String, Object> map : maps1) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 累计不满意工单量 存在的区县 {} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Integer.parseInt(map.get("count").toString()));
+            }
+        }
+        for (List<Object> list : result.values()) {
+            // 对于没有不满意工单的,直接补0
+            if (list.size() == 5) {
+                list.add(0);
+            }
+        }
+        List<List<Object>> list = new ArrayList<>(result.values());
+        list.sort(Comparator.comparingDouble(o -> (double) o.get(4)));
+        return list;
+    }
+
+    /**
+     * 计算区县投诉问题响应率
+     */
+    public List<List<Object>> generateCpTimelyContactOfRegion(String day) {
+        // 地市 区县 响应率 达标值 与达标值差距 本月累计未响应工单量
+        // 1. 获取全部的地市和区县
+        Map<String, List<Object>> result = sysDataDictionaryRepository.findAllRegion();
+        // 2. 响应率
+        List<Map<String, Object>> maps = highQualityDataMapper.selectRateOfCpTimelyContactOfRegion(day);
+        // 第一次遍历,将工单不为0的区县列出
+        for (Map<String, Object> map : maps) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 高品质详表存在的 区县{} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Double.parseDouble(map.get("rate").toString()));
+            }
+        }
+        // 第二次遍历,补充工单为0的区县,响应率默认100%
+        // 3. 响应率达标值 与达标值差距
+        for (List<Object> list : result.values()) {
+            if (list.size() == 2) {
+                list.add(1.0);
+            }
+            // 写入达标值和与达标值的差距
+            // 写入达标值
+            list.add(tslDataProperties.getResponseCompliance());
+            // 写入与达标值的差距
+            list.add((double) list.get(2) - (double) list.get(3));
+        }
+        // 4. 本月累计未响应工单量
+        List<Map<String, Object>> maps1 = highQualityDataMapper.selectCountOfCpNotTimelyContactOfRegion(day);
+        for (Map<String, Object> map : maps1) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 累计未响应工单量 存在的区县 {} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Integer.parseInt(map.get("count").toString()));
+            }
+        }
+        for (List<Object> list : result.values()) {
+            // 对于没有未响应工单的,直接补0
+            if (list.size() == 5) {
+                list.add(0);
+            }
+        }
+        List<List<Object>> list = new ArrayList<>(result.values());
+        list.sort(Comparator.comparingDouble(o -> (double) o.get(4)));
+        return list;
+    }
+
+    public void deleteHighQualityData(String day) {
+        highQualityDataMapper.deleteForDay(day);
+    }
+}

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

@@ -0,0 +1,272 @@
+package com.nokia.tsl_data.service;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.nokia.tsl_data.dao.ManagementDetailMapper;
+import com.nokia.tsl_data.dao.MobileComplaintMapper;
+import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
+import com.nokia.tsl_data.dao.TargetTsRatioMapper;
+import com.nokia.tsl_data.exception.LackOfDataRuntimeException;
+import com.nokia.tsl_data.util.DateFormatUtil;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 2024年适配需求
+ */
+@Slf4j
+@Service
+public class ManagementDetailService {
+
+    @Autowired
+    private ManagementDetailMapper managementDetailMapper;
+    @Autowired
+    private MobileComplaintMapper mobileComplaintMapper;
+    @Autowired
+    private SysDataDictionaryRepository sysDataDictionaryRepository;
+    @Autowired
+    private TargetTsRatioMapper targetTsRatioRepository;
+    @Autowired
+    private UserCountService userCountService;
+
+    /**
+     * 检查数据源
+     */
+    public void checkDataSource(String day) {
+        if (managementDetailMapper.selectAllForMonth(day) == 0) {
+            throw new RuntimeException("management_detail 表缺少账期 " + day + " 数据...");
+        }
+    }
+
+    /**
+     * 生成某个帐期的数据
+     */
+    public void generateForDay(String day) {
+        if (managementDetailMapper.selectCountofStatDay(day) != 0) {
+            throw new RuntimeException("managementDetail 帐期 " + day + " 已存在数据...");
+        }
+        managementDetailMapper.insertFromMobileComplaint(day);
+        managementDetailMapper.insertFromComplaintDetailsFixYwd(day);
+    }
+
+    /**
+     * 2024年适配需求 统计数据在原有基础(河北_CEM移网质量投诉明细全量数据)上增加新订阅的 河北客户体验管理智能定责投诉明细月累计接口日 表中
+     * 日定责问题分类(duty_reason_id_day) 字段值为 业务使用>>基础业务使用>>移网主被叫>>手机无法主被叫 的部分。
+     */
+    public Map<String, List<Object>> getSheet1DataV3(String day) {
+        // 1. 初始化存储
+        Map<String, List<Object>> result = new HashMap<>();
+        // dayIndex 决定每个List需要初始化多少元素(确实存在某一个某个地市数量为0的情况,这种情况数据库取到的可能没值,通过初始化可以避免缺失)
+        int dayIndex = Integer.parseInt(day.substring(6, 8));
+        // 初始化每个地市(按固定顺序)每天的工单量
+        for (String area : sysDataDictionaryRepository.findAllCityName()) {
+            List<Object> list = new ArrayList<>();
+            for (int i = 0; i < dayIndex; i++) {
+                list.add(0);
+            }
+            result.put(area, list);
+        }
+        // 初始化全省每天工作量
+        List<Object> list0 = new ArrayList<>();
+        for (int i = 0; i < dayIndex; i++) {
+            list0.add(0);
+        }
+        result.put("全省", list0);
+        // 2. 读取数据库数据(工单数量)并写入结果
+        // 各地市每日投诉量
+        List<Map<String, Object>> cityTslForMonth = managementDetailMapper.selectCityTslForMonth(day);
+        for (Map<String, Object> map : cityTslForMonth) {
+            int dayId = Integer.parseInt(map.get("day_id").toString());
+            // 注意这里用的是赋值而不是add
+            // 20240930 增加逻辑,跳过省中心
+            String comp = map.get("compl_area_local").toString();
+            if (!"省中心".equals(comp)) {
+                result.get(comp).set(dayId - 1, map.get("num"));
+            }
+        }
+        List<Map<String, Object>> allTslForMonth = managementDetailMapper.selectAllTslForMonth(day);
+        // 全省每日投诉量
+        for (Map<String, Object> map : allTslForMonth) {
+            int dayId = Integer.parseInt(map.get("day_id").toString());
+            result.get("全省").set(dayId - 1, map.get("num"));
+        }
+        // 各地市投诉总数
+        List<Map<String, Object>> cityAllForMonth = managementDetailMapper.selectCityAllForMonth(day);
+        // 各地市投诉总量
+        for (Map<String, Object> map : cityAllForMonth) {
+            // 20240930 增加逻辑,跳过省中心
+            String comp = map.get("compl_area_local").toString();
+            if (!"省中心".equals(comp)) {
+                result.get(comp).add(map.get("num"));
+            }
+        }
+        // 全省总量
+        int total = managementDetailMapper.selectAllForMonth(day);
+        result.get("全省").add(total);
+        // 3. 读取数据库 当月管理端用户数
+        Map<String, Double> userCount = userCountService.getManagementUserCountForMonth(day.substring(0, 6));
+        log.debug("============{}",userCount);
+        // 管理端用户数写入结果
+        for (Entry<String, List<Object>> entry : result.entrySet()) {
+            entry.getValue().add(userCount.get(entry.getKey()));
+        }
+        // 4. 万投率和本月预测
+        Calendar calendar = DateFormatUtil.toCalendar(day, "yyyyMMdd");
+        for (Entry<String, List<Object>> entry : result.entrySet()) {
+            List<Object> list = entry.getValue();
+            int size = list.size();
+            // 计算万投率
+            double wtl = Double.parseDouble(list.get(size - 2).toString())
+                    / Double.parseDouble(list.get(size - 1).toString());
+            list.add(wtl);
+            // 计算预测万投率
+            double ycwtl = wtl * calendar.getActualMaximum(Calendar.DAY_OF_MONTH) / calendar.get(Calendar.DAY_OF_MONTH);
+            list.add(ycwtl);
+        }
+        // 5. 期望万投率
+        List<Map<String, Object>> targetTsRatios = targetTsRatioRepository.findByMonthId(day.substring(0, 6));
+        for (Map<String, Object> targetTsRatio : targetTsRatios) {
+            result.get(targetTsRatio.get("city_name").toString()).add(targetTsRatio.get("management_target_ratio"));
+        }
+        // 6. 与目标差距
+        for (Entry<String, List<Object>> entry : result.entrySet()) {
+            List<Object> list = entry.getValue();
+            int size = list.size();
+            double cj = ((double) list.get(size - 2)) - ((double) list.get(size - 1));
+            list.add(cj);
+        }
+        return result;
+    }
+
+    /**
+     * 按照账期检查数据
+     */
+    public void checkStatDayCount(String day) {
+        int count = managementDetailMapper.selectCountofStatDay(day);
+        if (count == 0) {
+            throw new LackOfDataRuntimeException(String.format("management_detail 表缺少账期 %s 数据", day));
+        }
+    }
+
+    /**
+     * 重复投诉
+     */
+    public List<List<Object>> getRepeatTsDataV3(String day) {
+        // 从数据库
+        List<Map<String, Object>> dayData = managementDetailMapper.selectRepeatTsCountForDay(day);
+        List<Map<String, Object>> preDayData = dayData;
+        if (!day.endsWith("01")) {
+            String preDay = String.valueOf(Integer.parseInt(day) - 1);
+            preDayData = managementDetailMapper.selectRepeatTsCountForDay(preDay);
+        }
+        // 转化格式方便读取
+        Map<String, Map<String, Object>> dayMap = new HashMap<>();
+        Map<String, Map<String, Object>> preMap = new HashMap<>();
+        for (Map<String, Object> map : dayData) {
+            dayMap.put((String) map.get("compl_area_local"), map);
+        }
+        for (Map<String, Object> map : preDayData) {
+            preMap.put((String) map.get("compl_area_local"), map);
+        }
+        List<List<Object>> result = new ArrayList<>();
+        // 各地市数据
+        for (String area : sysDataDictionaryRepository.findAllCityName()) {
+            List<Object> list = new ArrayList<>(7);
+            result.add(list);
+            list.add(0, area);
+            // 重复投诉工单数
+            list.add(1, preMap.get(area).get("repeat_num"));
+            list.add(2, dayMap.get(area).get("repeat_num"));
+            // 重复投诉率
+            list.add(3, preMap.get(area).get("repeat_ratio"));
+            list.add(4, dayMap.get(area).get("repeat_ratio"));
+            // 增量
+            list.add(5, ((double) list.get(2)) - ((double) list.get(1)));
+            list.add(6, ((double) list.get(4)) - ((double) list.get(3)));
+        }
+        // 排序--逆序
+        result.sort((o1, o2) -> Double.compare((double) o2.get(6), (double) o1.get(6)));
+        // 全省数据
+        List<Object> list = new ArrayList<>(7);
+        result.add(list);
+        list.add(0, "全省");
+        // 重复投诉工单数
+        list.add(1, preMap.get("全省").get("repeat_num"));
+        list.add(2, dayMap.get("全省").get("repeat_num"));
+        // 重复投诉率
+        list.add(3, preMap.get("全省").get("repeat_ratio"));
+        list.add(4, dayMap.get("全省").get("repeat_ratio"));
+        // 增量
+        list.add(5, ((double) list.get(2)) - ((double) list.get(1)));
+        list.add(6, ((double) list.get(4)) - ((double) list.get(3)));
+        return result;
+    }
+
+    // 3G简网投诉情况 数据获取
+    // 20240409 新增
+    public Map<String, List<Object>> getSheet3GjwData(String day) {
+        // 1. 初始化存储
+        Map<String, List<Object>> result = new HashMap<>();
+        // dayIndex 决定每个List需要初始化多少元素
+        int dayIndex = Integer.parseInt(day.substring(6, 8));
+        // (确实存在某一个某个地市数量为0的情况,这种情况数据库取到的可能没值,通过初始化可以避免缺失)
+        // 初始化每个地市(按固定顺序)每天的工单量
+        // 20240602 修改 同时初始化总工单量
+        for (String area : sysDataDictionaryRepository.findAllCityName()) {
+            List<Object> list = new ArrayList<>();
+            for (int i = 0; i < dayIndex; i++) {
+                list.add(0);
+            }
+            list.add(0);
+            result.put(area, list);
+        }
+        // 初始化全省每天工作量
+        List<Object> list0 = new ArrayList<>();
+        for (int i = 0; i < dayIndex; i++) {
+            list0.add(0);
+        }
+        list0.add(0);
+        result.put("全省", list0);
+        // 2. 读取数据库数据(工单数量)并写入结果
+        // 各地市每日投诉量
+        List<Map<String, Object>> cityTslForMonth = mobileComplaintMapper.selectCity3GjwForMonth(day);
+        for (Map<String, Object> map : cityTslForMonth) {
+            int dayId = Integer.parseInt(map.get("day_id").toString());
+            // 注意这里用的是赋值而不是add
+            // 20240930 增加逻辑,跳过省中心
+            String comp = map.get("compl_area_local").toString();
+            if (!"省中心".equals(comp)) {
+                result.get(comp).set(dayId - 1, map.get("num"));
+            }
+        }
+        List<Map<String, Object>> allTslForMonth = mobileComplaintMapper.selectAll3GjwGroupByDayForMonth(day);
+        // 全省每日投诉量
+        for (Map<String, Object> map : allTslForMonth) {
+            int dayId = Integer.parseInt(map.get("day_id").toString());
+            result.get("全省").set(dayId - 1, map.get("num"));
+        }
+        // 各地市投诉总数
+        List<Map<String, Object>> cityAllForMonth = mobileComplaintMapper.selectCityAll3GjwForMonth(day);
+        // 各地市投诉总量
+        for (Map<String, Object> map : cityAllForMonth) {
+            // 20240602 修改为set
+            String comp = map.get("compl_area_local").toString();
+            if (!"省中心".equals(comp)) {
+                result.get(comp).set(dayIndex, map.get("num"));
+            }
+        }
+        // 全省总量,
+        int total = mobileComplaintMapper.selectAll3GjwForMonth(day);
+        result.get("全省").set(dayIndex, total);
+        return result;
+    }
+
+}

+ 31 - 0
src/main/java/com/nokia/tsl_data/service/MessageService.java

@@ -0,0 +1,31 @@
+package com.nokia.tsl_data.service;
+
+import org.springframework.stereotype.Service;
+
+import com.nokia.tsl_data.push_message.service.PushMessageService;
+
+@Service
+public class MessageService {
+
+    private final PushMessageService pushMessageService;
+    private final String accessToken = "b2f1424d6119affaacab614b184f043fcd2c73db2651bb86eff29992d66820bf";
+    private final String prefix = "CUC:";
+
+    public MessageService(PushMessageService pushMessageService) {
+        this.pushMessageService = pushMessageService;
+    }
+
+
+    public void error(String errorMessage) {
+        pushMessageService.sendMarkdownMessage(
+                accessToken,
+                prefix,
+                String.format("<font color=#FF0000>%s</font>", errorMessage),
+                "ERROR"
+        );
+    }
+
+    public void info(String infoMessage) {
+        pushMessageService.sendTextMessage(accessToken, prefix, infoMessage);
+    }
+}

+ 1567 - 0
src/main/java/com/nokia/tsl_data/service/ReportServiceV1.java

@@ -0,0 +1,1567 @@
+package com.nokia.tsl_data.service;
+
+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.TslDataProperties;
+import com.nokia.tsl_data.util.excel.PoiUtil;
+import com.nokia.tsl_data.util.excel.entity.CellRect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.EncryptedDocumentException;
+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.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+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.*;
+
+/**
+ * 投诉报表数据生成
+ */
+@Slf4j
+@Service
+public class ReportServiceV1 {
+
+    @Autowired
+    private TslDataService tslDataService;
+    @Autowired
+    private TslDataProperties tslDataProperties;
+    @Autowired
+    private HighQualityCountMapper highQualityCountMapper;
+    @Autowired
+    private HighQualityCountService highQualityCountService;
+    @Autowired
+    private HighQualityDataService highQualityDataService;
+    @Autowired
+    private MobileComplaintMapper mobileComplaintMapper;
+    @Autowired
+    private SysDataDictionaryRepository sysDataDictionaryRepository;
+
+    private XSSFWorkbook workbook = null;
+    private XSSFCellStyle cellStyle1 = null;
+    private XSSFCellStyle cellStyle2 = null;
+    private XSSFCellStyle cellStyle3 = null;
+
+    private static final DateFormat DAY_FORMAT = new SimpleDateFormat("yyyyMMdd");
+
+    /**
+     * 截图任务
+     */
+    public void screenShotV2(String day) {
+        String fileName = tslDataProperties.getOutputFileNamePrefix() + day + ".xlsx";
+        // 输出文件路径 增加V2
+        File file = Paths.get(tslDataProperties.getOutputPath(), "V2", day, fileName).toFile();
+        if (!file.exists()) {
+            throw new RuntimeException(String.format("无法截图,文件%s不存在", file.getAbsolutePath()));
+        }
+        try (Workbook workbook = WorkbookFactory.create(file)) {
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(new SimpleDateFormat("yyyyMMdd").parse(day));
+            int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+            BufferedImage screenShot;
+            // 截图1
+            String area = "A1:" + CellRect.getColumnName(dayOfMonth + 7) + "15";
+            screenShot = PoiUtil.screenShot(workbook.getSheet("管理端-移网质量类"), area, "微软雅黑");
+            ImageIO.write(screenShot, "png",
+                    Paths.get(tslDataProperties.getOutputPath(), day, day + "-1-投诉率.png").toFile());
+            // 截图2
+            String area2 = "A1:" + CellRect.getColumnName(dayOfMonth + 7) + "15";
+            screenShot = PoiUtil.screenShot(workbook.getSheet("客户端-战略考核"), area2, "微软雅黑");
+            ImageIO.write(screenShot, "png",
+                    Paths.get(tslDataProperties.getOutputPath(), day, day + "-2-客户端-战略考核.png").toFile());
+            // 截图3 每月1号不发送重复投诉率
+            if (!day.endsWith("01")) {
+                screenShot = PoiUtil.screenShot(workbook.getSheet("管理端-重复投诉率"), "A1:G16", "微软雅黑");
+                ImageIO.write(screenShot, "png",
+                        Paths.get(tslDataProperties.getOutputPath(), day, day + "-3-重复投诉率.png").toFile());
+            }
+            // 截图4 5
+            Sheet sheet = workbook.getSheet("投诉处理时长、超时工单概况");
+            screenShot = PoiUtil.screenShot(sheet, "A1:D15", "微软雅黑");
+            ImageIO.write(screenShot, "png",
+                    Paths.get(tslDataProperties.getOutputPath(), day, day + "-4-超时工单.png").toFile());
+            screenShot = PoiUtil.screenShot(sheet, "G1:J14", "微软雅黑");
+            ImageIO.write(screenShot, "png",
+                    Paths.get(tslDataProperties.getOutputPath(), day, day + "-5-处理时长.png").toFile());
+            // 截图6
+            sheet = workbook.getSheet("客户端地市三率");
+            screenShot = PoiUtil.screenShot(sheet, "A1:J15", "微软雅黑");
+            ImageIO.write(screenShot, "png",
+                    Paths.get(tslDataProperties.getOutputPath(), day, day + "-6-地市三率.png").toFile());
+            // 截图7 8 9 区县三率
+            sheet = workbook.getSheet("客户端区县三率");
+            screenShot = PoiUtil.screenShot(sheet, "A1:F32", "微软雅黑");
+            ImageIO.write(screenShot, "png",
+                    Paths.get(tslDataProperties.getOutputPath(), day, day + "-7-区县响应率.png").toFile());
+            screenShot = PoiUtil.screenShot(sheet, "A1:F32", "微软雅黑");
+            ImageIO.write(screenShot, "png",
+                    Paths.get(tslDataProperties.getOutputPath(), day, day + "-8-区县满意度.png").toFile());
+            screenShot = PoiUtil.screenShot(sheet, "A1:F32", "微软雅黑");
+            ImageIO.write(screenShot, "png",
+                    Paths.get(tslDataProperties.getOutputPath(), day, day + "-9-区县解决率.png").toFile());
+        } catch (EncryptedDocumentException | IOException | ParseException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    /**
+     * 生成报表,版本2,包含区县三率
+     */
+    public void generateReportV2(String day) {
+        String fileName = tslDataProperties.getOutputFileNamePrefix() + day + ".xlsx";
+        // 输出路径 增加 V2
+        File file = Paths.get(tslDataProperties.getOutputPath(), "V2", day).toFile();
+        if (!file.exists()) {
+            boolean mkdirs = file.mkdirs();
+            System.out.println(mkdirs);
+        }
+        String dayId = day.substring(0, 4) + "-" + day.substring(4, 6) + "-" + day.substring(6);
+        int qualityCountForDay = highQualityCountMapper.selectQualityCountForDay(dayId);
+        if (qualityCountForDay == 0) {
+            throw new RuntimeException("he_d_high_quality表缺少数据");
+        }
+        int compCountForDay = mobileComplaintMapper.selectCompCountForDay(day);
+        if (compCountForDay == 0) {
+            throw new RuntimeException("he_d_mobile_comp表缺少数据");
+        }
+        // 每次需要重置workbook
+        workbook = getWorkbook();
+        // 按照顺序写入各个sheet
+        // 管理端-移网质量类
+        getSheet1V1(day);
+        // 客户端-战略考核
+        getSheet1_1(day);
+        // 管理端-重复投诉率
+        getSheet2(day);
+        // 投诉处理时长、超时工单概况
+        getSheet3(day);
+        // 客户端-投诉问题解决满意度 客户端-投诉问题解决率 客户端-投诉问题响应率
+        getCityThreeRateSheet(day);
+        // 区县三率
+        getSheet7(day);
+        try (OutputStream outputStream = new FileOutputStream(Paths.get(file.getAbsolutePath(), fileName).toFile())) {
+            workbook.write(outputStream);
+            workbook.close();
+            workbook = null;
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.error("写入失败。。。" + e.getMessage());
+        }
+    }
+
+    public void generateReportV1(String day) {
+        String fileName = tslDataProperties.getOutputFileNamePrefix() + day + ".xlsx";
+        File file = Paths.get(tslDataProperties.getOutputPath(), "V1", day).toFile();
+        if (!file.exists()) {
+            boolean mkdirs = file.mkdirs();
+            System.out.println(mkdirs);
+        }
+        String dayId = day.substring(0, 4) + "-" + day.substring(4, 6) + "-" + day.substring(6);
+        int qualityCountForDay = highQualityCountMapper.selectQualityCountForDay(dayId);
+        if (qualityCountForDay == 0) {
+            throw new RuntimeException("he_d_high_quality表缺少数据");
+        }
+        int compCountForDay = mobileComplaintMapper.selectCompCountForDay(day);
+        if (compCountForDay == 0) {
+            throw new RuntimeException("he_d_mobile_comp表缺少数据");
+        }
+        workbook = getWorkbook();
+        // 管理端-移网质量类
+        getSheet1V1(day);
+        // 客户端-战略考核
+        getSheet1_1(day);
+        // 管理端-重复投诉率
+        getSheet2(day);
+        // 投诉处理时长、超时工单概况
+        getSheet3(day);
+        // 客户端-投诉问题解决满意度 客户端-投诉问题解决率 客户端-投诉问题响应率
+        getSheet4_6(day);
+        try (OutputStream outputStream = new FileOutputStream(Paths.get(file.getAbsolutePath(), fileName).toFile())) {
+            workbook.write(outputStream);
+            workbook.close();
+            workbook = null;
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.error("写入失败。。。" + e.getMessage());
+        }
+    }
+
+    /**
+     * 客户端-投诉问题解决满意度
+     * 客户端-投诉问题解决率
+     * 客户端-投诉问题响应率
+     */
+    private void getSheet4_6(String 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 = getWorkbook().createSheet("客户端-投诉问题解决满意度");
+        Sheet sheet5 = getWorkbook().createSheet("客户端-投诉问题解决率");
+        Sheet sheet6 = getWorkbook().createSheet("客户端-投诉问题响应率");
+        Row row;
+        Cell cell;
+        CellRangeAddress rangeAddress;
+        XSSFDataFormat dataFormat = getWorkbook().createDataFormat();
+        XSSFFont font = getWorkbook().createFont();
+        font.setFontName("微软雅黑");
+        font.setFontHeightInPoints((short) 10);
+        // 基本模式 微软雅黑 10号字 带全边框 水平居中
+        XSSFCellStyle baseStyle = getWorkbook().createCellStyle();
+        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);
+        // 样式1 与base相同
+        XSSFCellStyle cellStyle1 = baseStyle.copy();
+        // 样式2 百分比 2位小数
+        XSSFCellStyle cellStyle2 = baseStyle.copy();
+        cellStyle2.setDataFormat(dataFormat.getFormat("0.00%"));
+        // 样式3 自动换行 背景色FFE7E6E6 FFAEAAAA
+        XSSFCellStyle cellStyle3 = baseStyle.copy();
+        // cellStyle3.setWrapText(true);
+        XSSFColor color = new XSSFColor();
+        color.setARGBHex("FFAEAAAA");
+        cellStyle3.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        cellStyle3.setFillForegroundColor(color);
+
+        List<List<List<Object>>> sheet4_6Data = tslDataService.getSheet4_6Data(day);
+
+        // 客户端-投诉问题解决满意度 第一行
+        row = sheet4.createRow(0);
+        cell = row.createCell(0);
+        cell.setCellValue(String.format("投诉问题解决满意率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 A1 - D1
+        rangeAddress = new CellRangeAddress(0, 0, 0, 3);
+        addMergedRegion(sheet4, rangeAddress);
+        // 客户端-投诉问题解决满意度 第二行
+        row = sheet4.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue("满意率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(2);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(3);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        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(cellStyle1);
+            // 投诉问题解决满意率
+            cell = row.createCell(1);
+            cell.setCellValue(((double) list.get(1)));
+            cell.setCellStyle(cellStyle2);
+            // 达标值
+            cell = row.createCell(2);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle2);
+            // 与达标值差距
+            cell = row.createCell(3);
+            cell.setCellValue(((double) list.get(3)));
+            cell.setCellStyle(cellStyle2);
+        }
+        // 设置条件格式D3-D14
+        rangeAddress = new CellRangeAddress(2, 13, 3, 3);
+        setConditionalFormatting2(sheet4, rangeAddress);
+
+        // 设置列宽 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(cellStyle3);
+        // 合并单元格 A1 - D1
+        rangeAddress = new CellRangeAddress(0, 0, 0, 3);
+        addMergedRegion(sheet5, rangeAddress);
+        // 客户端-投诉问题解决率 第二行
+        row = sheet5.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue("解决率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(2);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(3);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        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(cellStyle1);
+            // 投诉问题解决率
+            cell = row.createCell(1);
+            cell.setCellValue(((double) list.get(1)));
+            cell.setCellStyle(cellStyle2);
+            // 达标值
+            cell = row.createCell(2);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle2);
+            // 与达标值差距
+            cell = row.createCell(3);
+            cell.setCellValue(((double) list.get(3)));
+            cell.setCellStyle(cellStyle2);
+        }
+        // 设置条件格式D3-D14
+        rangeAddress = new CellRangeAddress(2, 13, 3, 3);
+        setConditionalFormatting2(sheet5, rangeAddress);
+
+        // 设置列宽 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(cellStyle3);
+        // 合并单元格 A1 - D1
+        rangeAddress = new CellRangeAddress(0, 0, 0, 3);
+        addMergedRegion(sheet6, rangeAddress);
+        // 客户端-投诉问题响应率 第二行
+        row = sheet6.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue("响应率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(2);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(3);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        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(cellStyle1);
+            // 投诉问题解决率
+            cell = row.createCell(1);
+            cell.setCellValue(((double) list.get(1)));
+            cell.setCellStyle(cellStyle2);
+            // 达标值
+            cell = row.createCell(2);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle2);
+            // 与达标值差距
+            cell = row.createCell(3);
+            cell.setCellValue(((double) list.get(3)));
+            cell.setCellStyle(cellStyle2);
+        }
+        // 设置条件格式D3-D14
+        rangeAddress = new CellRangeAddress(2, 13, 3, 3);
+        setConditionalFormatting2(sheet6, rangeAddress);
+
+        // 设置列宽 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);
+        }
+    }
+
+    /**
+     * 客户端区县三率
+     */
+    private void getSheet7(String 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 sheet7 = getWorkbook().createSheet("客户端区县三率");
+        Row row;
+        Cell cell;
+        CellRangeAddress rangeAddress;
+
+        // 获取响应率数据
+        List<List<Object>> timelyContactRateData = highQualityDataService.generateCpTimelyContactOfRegion(day);
+        // 第一行 区县投诉问题响应率(1-11)
+        row = sheet7.createRow(0);
+        cell = row.createCell(0);
+        cell.setCellValue(String.format("区县投诉问题响应率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 A1-F1
+        rangeAddress = new CellRangeAddress(0, 0, 0, 5);
+        addMergedRegion(sheet7, rangeAddress);
+        // 第二行
+        row = sheet7.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue("区县");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(2);
+        cell.setCellValue("响应率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(3);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(4);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(5);
+        cell.setCellValue("累计未响应");
+        cell.setCellStyle(cellStyle3);
+        // 写入数据
+        int rowNum = 2;
+        for (List<Object> list : timelyContactRateData) {
+            row = sheet7.createRow(rowNum++);
+            // 地市
+            cell = row.createCell(0);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(cellStyle1);
+            // 区县
+            cell = row.createCell(1);
+            cell.setCellValue(list.get(1).toString());
+            cell.setCellStyle(cellStyle1);
+            // 响应率
+            cell = row.createCell(2);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle2);
+            // 达标值
+            cell = row.createCell(3);
+            cell.setCellValue((double) list.get(3));
+            cell.setCellStyle(cellStyle2);
+            // 与达标值差距
+            cell = row.createCell(4);
+            cell.setCellValue((double) list.get(4));
+            cell.setCellStyle(cellStyle2);
+            // 本月累计未响应工单数
+            cell = row.createCell(5);
+            cell.setCellValue((int) list.get(5));
+            cell.setCellStyle(cellStyle1);
+        }
+        // 与达标值差距设置条件格式
+        rangeAddress = new CellRangeAddress(2, --rowNum, 4, 4);
+        setConditionalFormatting2(sheet7, rangeAddress);
+        // 设置列宽
+        sheet7.setColumnWidth(0, 2848);
+        sheet7.setColumnWidth(1, 2848);
+        sheet7.setColumnWidth(2, 2848);
+        sheet7.setColumnWidth(3, 2848);
+        sheet7.setColumnWidth(4, 2848);
+        sheet7.setColumnWidth(5, 2848);
+
+        // 获取和写入满意度数据
+        List<List<Object>> satisfactionData = highQualityDataService.generateCpSatisfactionOfRegion(day);
+        // 第一行 区县投诉问题满意度(1-11)
+        row = sheet7.getRow(0);
+        cell = row.createCell(7);
+        cell.setCellValue(String.format("区县投诉问题满意度(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 H1-M1
+        rangeAddress = new CellRangeAddress(0, 0, 7, 12);
+        addMergedRegion(sheet7, rangeAddress);
+        // 第二行
+        row = sheet7.getRow(1);
+        cell = row.createCell(7);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(8);
+        cell.setCellValue("区县");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(9);
+        cell.setCellValue("满意度");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(10);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(11);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(12);
+        cell.setCellValue("累计不满意");
+        cell.setCellStyle(cellStyle3);
+        // 写入数据
+        rowNum = 2;
+        for (List<Object> list : satisfactionData) {
+            row = sheet7.getRow(rowNum++);
+            // 地市
+            cell = row.createCell(7);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(cellStyle1);
+            // 区县
+            cell = row.createCell(8);
+            cell.setCellValue(list.get(1).toString());
+            cell.setCellStyle(cellStyle1);
+            // 响应率
+            cell = row.createCell(9);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle2);
+            // 达标值
+            cell = row.createCell(10);
+            cell.setCellValue((double) list.get(3));
+            cell.setCellStyle(cellStyle2);
+            // 与达标值差距
+            cell = row.createCell(11);
+            cell.setCellValue((double) list.get(4));
+            cell.setCellStyle(cellStyle2);
+            // 本月累计未响应工单数
+            cell = row.createCell(12);
+            cell.setCellValue((int) list.get(5));
+            cell.setCellStyle(cellStyle1);
+        }
+        // 与达标值差距设置条件格式
+        rangeAddress = new CellRangeAddress(2, --rowNum, 11, 11);
+        setConditionalFormatting2(sheet7, rangeAddress);
+        // 设置列宽
+        sheet7.setColumnWidth(7, 2848);
+        sheet7.setColumnWidth(8, 2848);
+        sheet7.setColumnWidth(9, 2848);
+        sheet7.setColumnWidth(10, 2848);
+        sheet7.setColumnWidth(11, 2848);
+        sheet7.setColumnWidth(12, 2848);
+
+        // 获取和写入解决率数据 14到19列 O-T
+        List<List<Object>> cpIsOkData = highQualityDataService.generateCpIsOkOfRegion(day);
+        // 第一行 区县投诉问题解决率(1-11)
+        row = sheet7.getRow(0);
+        cell = row.createCell(14);
+        cell.setCellValue(String.format("区县投诉问题解决率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 O1-T1
+        rangeAddress = new CellRangeAddress(0, 0, 14, 19);
+        addMergedRegion(sheet7, rangeAddress);
+        // 第二行
+        row = sheet7.getRow(1);
+        cell = row.createCell(14);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(15);
+        cell.setCellValue("区县");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(16);
+        cell.setCellValue("解决率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(17);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(18);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(19);
+        cell.setCellValue("累计未解决");
+        cell.setCellStyle(cellStyle3);
+        // 写入数据
+        rowNum = 2;
+        for (List<Object> list : cpIsOkData) {
+            row = sheet7.getRow(rowNum++);
+            // 地市
+            cell = row.createCell(14);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(cellStyle1);
+            // 区县
+            cell = row.createCell(15);
+            cell.setCellValue(list.get(1).toString());
+            cell.setCellStyle(cellStyle1);
+            // 响应率
+            cell = row.createCell(16);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle2);
+            // 达标值
+            cell = row.createCell(17);
+            cell.setCellValue((double) list.get(3));
+            cell.setCellStyle(cellStyle2);
+            // 与达标值差距
+            cell = row.createCell(18);
+            cell.setCellValue((double) list.get(4));
+            cell.setCellStyle(cellStyle2);
+            // 本月累计未响应工单数
+            cell = row.createCell(19);
+            cell.setCellValue((int) list.get(5));
+            cell.setCellStyle(cellStyle1);
+        }
+        // 与达标值差距设置条件格式
+        rangeAddress = new CellRangeAddress(2, --rowNum, 18, 18);
+        setConditionalFormatting2(sheet7, rangeAddress);
+        // 设置列宽
+        sheet7.setColumnWidth(14, 2848);
+        sheet7.setColumnWidth(15, 2848);
+        sheet7.setColumnWidth(16, 2848);
+        sheet7.setColumnWidth(17, 2848);
+        sheet7.setColumnWidth(18, 2848);
+        sheet7.setColumnWidth(19, 2848);
+
+        // 设置行高
+        for (int i = 0; i < rowNum; i++) {
+            sheet7.getRow(i).setHeightInPoints(20.0F);
+        }
+    }
+
+    private void getCityThreeRateSheet(String 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 sheet = getWorkbook().createSheet("客户端地市三率");
+        Row row;
+        Cell cell;
+        CellRangeAddress rangeAddress;
+        // 获取区县三率数据
+        Map<String, List<Object>> dataMap = highQualityCountService.generateThreeRateOfCity(day);
+        // 第一行
+        row = sheet.createRow(0);
+        cell = row.createCell(0);
+        cell.setCellValue("");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue(String.format("投诉问题解决满意率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 B1 - D1
+        rangeAddress = new CellRangeAddress(0, 0, 1, 3);
+        addMergedRegion(sheet, rangeAddress);
+        cell = row.createCell(4);
+        cell.setCellValue(String.format("投诉问题解决率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 E1 - G1
+        rangeAddress = new CellRangeAddress(0, 0, 4, 6);
+        addMergedRegion(sheet, rangeAddress);
+        cell = row.createCell(7);
+        cell.setCellValue(String.format("投诉问题响应率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 H1 - J1
+        rangeAddress = new CellRangeAddress(0, 0, 7, 9);
+        addMergedRegion(sheet, rangeAddress);
+        // 第二行
+        row = sheet.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue("满意度");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(2);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(3);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(4);
+        cell.setCellValue("解决率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(5);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(6);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(7);
+        cell.setCellValue("响应率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(8);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(9);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        // 数据行
+        int rowNum = 2;
+        for (String city : sysDataDictionaryRepository.findAllCityName()) {
+            List<Object> list = dataMap.get(city);
+            row = sheet.createRow(rowNum++);
+            // 地市
+            cell = row.createCell(0);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(cellStyle1);
+            // 满意度
+            cell = row.createCell(1);
+            cell.setCellValue((double) list.get(1));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(2);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(3);
+            cell.setCellValue((double) list.get(3));
+            cell.setCellStyle(cellStyle2);
+            // 解决率
+            cell = row.createCell(4);
+            cell.setCellValue((double) list.get(4));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(5);
+            cell.setCellValue((double) list.get(5));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(6);
+            cell.setCellValue((double) list.get(6));
+            cell.setCellStyle(cellStyle2);
+            // 响应率
+            cell = row.createCell(7);
+            cell.setCellValue((double) list.get(7));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(8);
+            cell.setCellValue((double) list.get(8));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(9);
+            cell.setCellValue((double) list.get(9));
+            cell.setCellStyle(cellStyle2);
+        }
+        // 设置条件格式
+        // 设置条件格式D3-D14
+        rangeAddress = new CellRangeAddress(2, 13, 3, 3);
+        setConditionalFormatting2(sheet, rangeAddress);
+        // 设置条件格式G3-G14
+        rangeAddress = new CellRangeAddress(2, 13, 6, 6);
+        setConditionalFormatting2(sheet, rangeAddress);
+        // 设置条件格式J3-J14
+        rangeAddress = new CellRangeAddress(2, 13, 9, 9);
+        setConditionalFormatting2(sheet, rangeAddress);
+        // 写入全省数据
+        List<Object> list = dataMap.get("全省");
+        row = sheet.createRow(rowNum);
+        // 地市
+        cell = row.createCell(0);
+        cell.setCellValue(list.get(0).toString());
+        cell.setCellStyle(cellStyle1);
+        // 满意度
+        cell = row.createCell(1);
+        cell.setCellValue((double) list.get(1));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(2);
+        cell.setCellValue((double) list.get(2));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(3);
+        cell.setCellValue((double) list.get(3));
+        cell.setCellStyle(cellStyle2);
+        // 解决率
+        cell = row.createCell(4);
+        cell.setCellValue((double) list.get(4));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(5);
+        cell.setCellValue((double) list.get(5));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(6);
+        cell.setCellValue((double) list.get(6));
+        cell.setCellStyle(cellStyle2);
+        // 响应率
+        cell = row.createCell(7);
+        cell.setCellValue((double) list.get(7));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(8);
+        cell.setCellValue((double) list.get(8));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(9);
+        cell.setCellValue((double) list.get(9));
+        cell.setCellStyle(cellStyle2);
+
+        // 设置列宽
+        for (int i = 0; i < 9; i++) {
+            sheet.setColumnWidth(i, 2848);
+        }
+
+        // 设置行高 15.0 15.0...
+        for (int i = 0; i < 15; i++) {
+            sheet.getRow(i).setHeightInPoints(15.0F);
+        }
+    }
+
+    /**
+     * 投诉处理时长、超时工单概况
+     */
+    private void getSheet3(String 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);
+        // 修正,MONTH是从0开始
+        int monthOfYear = calendar.get(Calendar.MONTH) + 1;
+        int preMonthOfYear = monthOfYear > 1 ? monthOfYear - 1 : 12;
+
+        Sheet sheet = getWorkbook().createSheet("投诉处理时长、超时工单概况");
+        Row row;
+        Cell cell;
+        CellRangeAddress rangeAddress;
+        XSSFDataFormat dataFormat = getWorkbook().createDataFormat();
+        XSSFFont font = getWorkbook().createFont();
+        font.setFontName("微软雅黑");
+        font.setFontHeightInPoints((short) 10);
+        // 基本模式 微软雅黑 10号字 带全边框 水平居中
+        XSSFCellStyle baseStyle = getWorkbook().createCellStyle();
+        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);
+        // 样式1 与base相同
+        XSSFCellStyle cellStyle1 = baseStyle.copy();
+        // 样式2 百分比 2位小数
+        XSSFCellStyle cellStyle2 = baseStyle.copy();
+        cellStyle2.setDataFormat(dataFormat.getFormat("0.00%"));
+        // 样式3 自动换行 背景色FFE7E6E6 FFAEAAAA
+        XSSFCellStyle cellStyle3 = baseStyle.copy();
+        // cellStyle3.setWrapText(true);
+        XSSFColor color = new XSSFColor();
+        color.setARGBHex("FFAEAAAA");
+        cellStyle3.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        cellStyle3.setFillForegroundColor(color);
+        // 样式4 小数 保留2位
+        XSSFCellStyle cellStyle4 = baseStyle.copy();
+        cellStyle4.setDataFormat(dataFormat.getFormat("0.00"));
+
+        // 第一行
+        row = sheet.createRow(0);
+        cell = row.createCell(0);
+        cell.setCellValue(String.format("超时工单情况(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 A1 - D1
+        rangeAddress = new CellRangeAddress(0, 0, 0, 3);
+        addMergedRegion(sheet, rangeAddress);
+        cell = row.createCell(6);
+        cell.setCellValue(String.format("平均处理时长(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 G1 - J1
+        rangeAddress = new CellRangeAddress(0, 0, 6, 9);
+        addMergedRegion(sheet, rangeAddress);
+
+        // 第二行
+        row = sheet.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue("工单数");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(2);
+        cell.setCellValue("超时工单数");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(3);
+        cell.setCellValue("超时工单占比");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(6);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(7);
+        cell.setCellValue(String.format("%s月", preMonthOfYear));
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(8);
+        cell.setCellValue(String.format("%s月", monthOfYear));
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(9);
+        cell.setCellValue("涨幅");
+        cell.setCellStyle(cellStyle3);
+
+        // 写入超时工单情况
+        List<List<Object>> sheet3Data1 = tslDataService.getSheet3Data1(day);
+        int rowNum = 2;
+        for (List<Object> list : sheet3Data1) {
+            row = sheet.createRow(rowNum++);
+            // 地市
+            cell = row.createCell(0);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(cellStyle1);
+            // 工单数
+            cell = row.createCell(1);
+            cell.setCellValue((long) list.get(1));
+            cell.setCellStyle(cellStyle1);
+            // 超时工单数
+            cell = row.createCell(2);
+            cell.setCellValue((long) list.get(2));
+            cell.setCellStyle(cellStyle1);
+            // 超时工单占比
+            cell = row.createCell(3);
+            cell.setCellValue(((double) list.get(3)));
+            cell.setCellStyle(cellStyle2);
+        }
+        // 设置条件格式D3-D14
+        rangeAddress = new CellRangeAddress(2, 13, 3, 3);
+        setConditionalFormatting(sheet, rangeAddress);
+
+        // 写入平均处理时长
+        List<List<Object>> sheet3Data2 = tslDataService.getSheet3Data2(day);
+        rowNum = 2;
+        for (List<Object> list : sheet3Data2) {
+            row = sheet.getRow(rowNum++);
+            // 地市
+            cell = row.createCell(6);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(cellStyle1);
+            // 上月
+            cell = row.createCell(7);
+            cell.setCellValue((double) list.get(1));
+            cell.setCellStyle(cellStyle4);
+            // 当月
+            cell = row.createCell(8);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle4);
+            // 涨幅
+            cell = row.createCell(9);
+            cell.setCellValue((double) list.get(3));
+            cell.setCellStyle(cellStyle4);
+        }
+        // 设置条件格式J3-J14
+        rangeAddress = new CellRangeAddress(2, 13, 9, 9);
+        setConditionalFormatting(sheet, rangeAddress);
+
+        // 设置列宽 2048 2276*3 2048*3 2276*2 3612
+        for (int i = 0; i < 10; i++) {
+            sheet.setColumnWidth(i, 2848);
+        }
+
+        // 设置行高 14.25 25.5 14.25...
+        for (int i = 0; i < 15; i++) {
+            sheet.getRow(i).setHeightInPoints(15.0F);
+        }
+    }
+
+    /**
+     * 管理端-重复投诉率
+     */
+    private void getSheet2(String 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);
+        int monthOfYear = calendar.get(Calendar.MONTH) + 1;
+        // 显示当前和前一天的对比
+        int preDayOfMonth = (dayOfMonth > 1) ? dayOfMonth - 1 : dayOfMonth;
+
+        Sheet sheet = getWorkbook().createSheet("管理端-重复投诉率");
+        Row row;
+        Cell cell;
+        CellRangeAddress rangeAddress;
+        XSSFFont font;
+        XSSFDataFormat dataFormat = getWorkbook().createDataFormat();
+        // 基本模式 微软雅黑 10号字 带全边框 水平居中
+        XSSFCellStyle baseStyle = getWorkbook().createCellStyle();
+        font = getWorkbook().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);
+        // 基础模式
+        XSSFCellStyle cellStyle1 = baseStyle.copy();
+        // 带边框 百分比 2位小数
+        XSSFCellStyle cellStyle2 = baseStyle.copy();
+        cellStyle2.setDataFormat(dataFormat.getFormat("0.00%"));
+        // 微软雅黑 10号字 自动换行 背景色FFE7E6E6 FFAEAAAA
+        XSSFCellStyle cellStyle3 = baseStyle.copy();
+        // cellStyle3.setWrapText(true);
+        XSSFColor color = new XSSFColor();
+        color.setARGBHex("FFAEAAAA");
+        cellStyle3.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        cellStyle3.setFillForegroundColor(color);
+
+        // 第一行 标题栏
+        row = sheet.createRow(0);
+        cell = row.createCell(0);
+        cell.setCellValue(String.format("重复投诉率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 A1 - G1
+        rangeAddress = new CellRangeAddress(0, 0, 0, 6);
+        addMergedRegion(sheet, rangeAddress);
+
+        // 第二行
+        row = sheet.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue("工单数");
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 B2-C2
+        rangeAddress = new CellRangeAddress(1, 1, 1, 2);
+        addMergedRegion(sheet, rangeAddress);
+        cell = row.createCell(3);
+        cell.setCellValue("重复投诉率");
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 B2-C2
+        rangeAddress = new CellRangeAddress(1, 1, 3, 4);
+        addMergedRegion(sheet, rangeAddress);
+        cell = row.createCell(5);
+        cell.setCellValue("增量");
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 F2-G2
+        rangeAddress = new CellRangeAddress(1, 1, 5, 6);
+        addMergedRegion(sheet, rangeAddress);
+
+        // 第三行
+        row = sheet.createRow(2);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue(String.format("%s月截止%s日", monthOfYear, preDayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(2);
+        cell.setCellValue(String.format("%s月截止%s日", monthOfYear, dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(3);
+        cell.setCellValue(String.format("%s月截止%s日", monthOfYear, preDayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(4);
+        cell.setCellValue(String.format("%s月截止%s日", monthOfYear, dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(5);
+        cell.setCellValue("工单数");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(6);
+        cell.setCellValue("重复投诉率");
+        cell.setCellStyle(cellStyle3);
+
+        // 写入数据行
+        List<List<Object>> sheet2Data = tslDataService.getSheet2Data(day);
+        int rowNum = 3;
+        for (List<Object> list : sheet2Data) {
+            row = sheet.createRow(rowNum++);
+            cell = row.createCell(0);
+            // 地市
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(cellStyle1);
+            // 前一天重复工单数
+            cell = row.createCell(1);
+            cell.setCellValue(((double) list.get(1)));
+            cell.setCellStyle(cellStyle1);
+            // 当天重复工单数
+            cell = row.createCell(2);
+            cell.setCellValue(((double) list.get(2)));
+            cell.setCellStyle(cellStyle1);
+            // 前一天重复投诉率
+            cell = row.createCell(3);
+            cell.setCellValue(((double) list.get(3)));
+            cell.setCellStyle(cellStyle2);
+            // 当前重复投诉率
+            cell = row.createCell(4);
+            cell.setCellValue(((double) list.get(4)));
+            cell.setCellStyle(cellStyle2);
+            // 重复工单增量
+            cell = row.createCell(5);
+            cell.setCellValue(((double) list.get(5)));
+            cell.setCellStyle(cellStyle1);
+            // 重复投诉率增量
+            cell = row.createCell(6);
+            cell.setCellValue(((double) list.get(6)));
+            cell.setCellStyle(cellStyle2);
+        }
+
+        // 添加条件格式F4-F15
+        rangeAddress = new CellRangeAddress(3, 14, 5, 5);
+        setConditionalFormatting(sheet, rangeAddress);
+        // 添加条件格式G4-G15
+        rangeAddress = new CellRangeAddress(3, 14, 6, 6);
+        setConditionalFormatting(sheet, rangeAddress);
+
+        // 设置列宽2048
+        for (int i = 0; i <= 6; i++) {
+            sheet.setColumnWidth(i, 2848);
+        }
+
+        // 设置行高 15.0 ...
+        for (int i = 0; i < 16; i++) {
+            sheet.getRow(i).setHeightInPoints(15.0F);
+        }
+    }
+
+    /**
+     * 20230627新增 客户端-战略考核
+     */
+    private void getSheet1_1(String day) {
+        Sheet sheet = getWorkbook().createSheet("客户端-战略考核");
+
+        // 计算天数
+        LocalDate date = LocalDate.parse(day, DateTimeFormatter.ofPattern("yyyyMMdd"));
+        int dayOfMonth = date.getDayOfMonth();
+
+        Row row;
+        Cell cell;
+        CellRangeAddress rangeAddress;
+        XSSFFont font;
+        XSSFDataFormat dataFormat = getWorkbook().createDataFormat();
+
+        // 基本模式 微软雅黑 10号字 带全边框 水平居中
+        XSSFCellStyle baseStyle = getWorkbook().createCellStyle();
+        font = getWorkbook().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);
+        // 基础模式
+        XSSFCellStyle cellStyle1 = baseStyle.copy();
+        // 2位小数
+        XSSFCellStyle cellStyle2 = baseStyle.copy();
+        cellStyle2.setDataFormat(dataFormat.getFormat("0.00"));
+        // 微软雅黑 10号 加粗
+        XSSFCellStyle cellStyle3 = baseStyle.copy();
+        font = getWorkbook().createFont();
+        font.setFontName("微软雅黑");
+        font.setBold(true);
+        font.setFontHeightInPoints((short) 10);
+        cellStyle3.setFont(font);
+
+        // 第一行 标题栏2023年客服投诉清单各地市投诉率情况(客户端-战略考核)
+        cell = sheet.createRow(0).createCell(0);
+        cell.setCellValue(day.substring(0, 4) + "年客服投诉清单各地市投诉率情况(客户端-战略考核)");
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格
+        rangeAddress = new CellRangeAddress(0, 0, 0, dayOfMonth + 7);
+        sheet.addMergedRegion(rangeAddress);
+        // 设置合并单元格的边框
+        RegionUtil.setBorderBottom(BorderStyle.THIN, rangeAddress, sheet);
+        RegionUtil.setBorderTop(BorderStyle.THIN, rangeAddress, sheet);
+        RegionUtil.setBorderLeft(BorderStyle.THIN, rangeAddress, sheet);
+        RegionUtil.setBorderRight(BorderStyle.THIN, rangeAddress, sheet);
+
+        // 第二行 列名
+        row = sheet.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle1);
+        for (int i = 1; i <= dayOfMonth; i++) {
+            cell = row.createCell(i);
+            cell.setCellValue(i + "日");
+            cell.setCellStyle(cellStyle1);
+        }
+        cell = row.createCell(dayOfMonth + 1);
+        cell.setCellValue("投诉总量");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 2);
+        cell.setCellValue("用户数");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 3);
+        cell.setCellValue("目前万投率");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 4);
+        cell.setCellValue("本月预测");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 5);
+        cell.setCellValue("目标值");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 6);
+        cell.setCellValue("与目标差距");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 7);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle1);
+
+        // 获取数据
+        Map<String, List<Object>> seet1_1Data = tslDataService.getSheet1_1Data(day);
+
+        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(cellStyle1);
+            for (Object obj : seet1_1Data.get(area)) {
+                cell = row.createCell(cellNum++);
+                cell.setCellValue(Double.parseDouble(obj.toString()));
+                cell.setCellStyle(cellStyle1);
+            }
+            // 写入最后一列的地市
+            cell = row.createCell(cellNum);
+            cell.setCellValue(area);
+            cell.setCellStyle(cellStyle1);
+            // cellNum复位
+            cellNum = 0;
+        }
+        // 写入全省数据
+        row = sheet.createRow(rowNum);
+        // 写入A列的地市
+        cell = row.createCell(cellNum++);
+        cell.setCellValue("全省");
+        cell.setCellStyle(cellStyle1);
+        for (Object obj : seet1_1Data.get("全省")) {
+            cell = row.createCell(cellNum++);
+            cell.setCellValue(Double.parseDouble(obj.toString()));
+            cell.setCellStyle(cellStyle1);
+        }
+        // 写入最后一列的地市
+        cell = row.createCell(cellNum);
+        cell.setCellValue("全省");
+        cell.setCellStyle(cellStyle1);
+
+        // 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(cellStyle2);
+            }
+        }
+
+        // 添加条件格式B15-V15
+        rangeAddress = new CellRangeAddress(rowNum, rowNum, 1, dayOfMonth);
+        setConditionalFormatting(sheet, rangeAddress);
+        // 添加条件格式(dayOfMonth+3)(3-14)
+        rangeAddress = new CellRangeAddress(2, 13, dayOfMonth + 3, dayOfMonth + 3);
+        setConditionalFormatting(sheet, rangeAddress);
+        // 添加条件格式Z3-Z14
+        rangeAddress = new CellRangeAddress(2, 13, dayOfMonth + 4, dayOfMonth + 4);
+        setConditionalFormatting(sheet, rangeAddress);
+        // 添加条件格式AB3-AB14
+        rangeAddress = new CellRangeAddress(2, 13, dayOfMonth + 6, dayOfMonth + 6);
+        setConditionalFormatting(sheet, rangeAddress);
+
+        // 设置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);
+        }
+    }
+
+    /**
+     * 管理端-移网质量类
+     */
+    private void getSheet1V1(String day) {
+        // 获取数据
+        Map<String, List<Object>> seet1Data = tslDataService.getSheet1Data(day);
+        getSheet1(day, seet1Data, "管理端-移网质量类");
+    }
+
+    /**
+     * 写sheet1
+     */
+    private void getSheet1(String day, Map<String, List<Object>> seet1Data, String sheetName) {
+        Sheet sheet = getWorkbook().createSheet(sheetName);
+
+        // 计算天数
+        LocalDate date = LocalDate.parse(day, DateTimeFormatter.ofPattern("yyyyMMdd"));
+        int dayOfMonth = date.getDayOfMonth();
+
+        Row row;
+        Cell cell;
+
+        XSSFCellStyle cellStyle1 = getWorkbook().createCellStyle();
+        cellStyle1.setAlignment(HorizontalAlignment.CENTER);
+        cellStyle1.setVerticalAlignment(VerticalAlignment.CENTER);
+        XSSFFont font = getWorkbook().createFont();
+        font.setFontName("微软雅黑");
+        font.setBold(false);
+        font.setFontHeightInPoints((short) 10);
+        cellStyle1.setFont(font);
+        cellStyle1.setBorderBottom(BorderStyle.THIN);
+        cellStyle1.setBorderTop(BorderStyle.THIN);
+        cellStyle1.setBorderLeft(BorderStyle.THIN);
+        cellStyle1.setBorderRight(BorderStyle.THIN);
+        XSSFCellStyle cellStyle3 = getWorkbook().createCellStyle();
+        cellStyle3.setAlignment(HorizontalAlignment.CENTER);
+        cellStyle3.setVerticalAlignment(VerticalAlignment.CENTER);
+        cellStyle3.setFont(font);
+        cellStyle3.setBorderBottom(BorderStyle.THIN);
+        cellStyle3.setBorderTop(BorderStyle.THIN);
+        cellStyle3.setBorderLeft(BorderStyle.THIN);
+        cellStyle3.setBorderRight(BorderStyle.THIN);
+        XSSFDataFormat format = getWorkbook().createDataFormat();
+        cellStyle3.setDataFormat(format.getFormat("0.00"));
+        XSSFCellStyle cellStyle2 = getWorkbook().createCellStyle();
+        cellStyle2.setAlignment(HorizontalAlignment.CENTER);
+        cellStyle2.setVerticalAlignment(VerticalAlignment.CENTER);
+        font = getWorkbook().createFont();
+        font.setFontName("微软雅黑");
+        font.setBold(true);
+        font.setFontHeightInPoints((short) 10);
+        cellStyle2.setFont(font);
+        cellStyle2.setBorderBottom(BorderStyle.THIN);
+        cellStyle2.setBorderTop(BorderStyle.THIN);
+        cellStyle2.setBorderLeft(BorderStyle.THIN);
+        cellStyle2.setBorderRight(BorderStyle.THIN);
+
+        // 第一行 标题栏
+        cell = sheet.createRow(0).createCell(0);
+        cell.setCellValue(day.substring(0, 4) + "年客服投诉清单各地市投诉率情况(" + sheetName + ")");
+        cell.setCellStyle(cellStyle2);
+        // 合并单元格
+        CellRangeAddress range = new CellRangeAddress(0, 0, 0, dayOfMonth + 7);
+        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);
+
+        // 第二行 列名
+        row = sheet.createRow(1);
+        cell = row.createCell(0);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle1);
+        for (int i = 1; i <= dayOfMonth; i++) {
+            cell = row.createCell(i);
+            cell.setCellValue(i + "日");
+            cell.setCellStyle(cellStyle1);
+        }
+        cell = row.createCell(dayOfMonth + 1);
+        cell.setCellValue("投诉总量");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 2);
+        cell.setCellValue("用户数");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 3);
+        cell.setCellValue("目前万投率");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 4);
+        cell.setCellValue("本月预测");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 5);
+        cell.setCellValue("目标值");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 6);
+        cell.setCellValue("与目标差距");
+        cell.setCellStyle(cellStyle1);
+        cell = row.createCell(dayOfMonth + 7);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle1);
+
+        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(cellStyle1);
+            for (Object obj : seet1Data.get(area)) {
+                cell = row.createCell(cellNum++);
+                cell.setCellValue(Double.parseDouble(obj.toString()));
+                cell.setCellStyle(cellStyle1);
+            }
+            // 写入最后一列的地市
+            cell = row.createCell(cellNum);
+            cell.setCellValue(area);
+            cell.setCellStyle(cellStyle1);
+            // cellNum复位
+            cellNum = 0;
+        }
+        // 写入全省数据
+        row = sheet.createRow(rowNum);
+        // 写入A列的地市
+        cell = row.createCell(cellNum++);
+        cell.setCellValue("全省");
+        cell.setCellStyle(cellStyle1);
+        for (Object obj : seet1Data.get("全省")) {
+            cell = row.createCell(cellNum++);
+            cell.setCellValue(Double.parseDouble(obj.toString()));
+            cell.setCellStyle(cellStyle1);
+        }
+        // 写入最后一列的地市
+        cell = row.createCell(cellNum);
+        cell.setCellValue("全省");
+        cell.setCellStyle(cellStyle1);
+
+        // 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(cellStyle3);
+            }
+        }
+
+        // 添加条件格式B15-V15
+        CellRangeAddress rangeAddress = new CellRangeAddress(rowNum, rowNum, 1, dayOfMonth);
+        setConditionalFormatting(sheet, rangeAddress);
+        // 添加条件格式(dayOfMonth+3)(3-14)
+        rangeAddress = new CellRangeAddress(2, 13, dayOfMonth + 3, dayOfMonth + 3);
+        setConditionalFormatting(sheet, rangeAddress);
+        // 添加条件格式Z3-Z14
+        rangeAddress = new CellRangeAddress(2, 13, dayOfMonth + 4, dayOfMonth + 4);
+        setConditionalFormatting(sheet, rangeAddress);
+        // 添加条件格式AB3-AB14
+        rangeAddress = new CellRangeAddress(2, 13, dayOfMonth + 6, dayOfMonth + 6);
+        setConditionalFormatting(sheet, rangeAddress);
+
+        // 设置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);
+        }
+    }
+
+    /**
+     * 初始化表格对象以及
+     */
+    private XSSFWorkbook getWorkbook() {
+        if (workbook == null) {
+            workbook = new XSSFWorkbook();
+            // 基本模式 微软雅黑 10号字 带全边框 水平居中
+            XSSFCellStyle baseStyle = getWorkbook().createCellStyle();
+            XSSFFont font = getWorkbook().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号字 带全边框 水平居中
+            cellStyle1 = baseStyle.copy();
+            // cellStyle2 百分比 2位小数
+            cellStyle2 = baseStyle.copy();
+            XSSFDataFormat dataFormat = getWorkbook().createDataFormat();
+            cellStyle2.setDataFormat(dataFormat.getFormat("0.00%"));
+            // cellStyle3 自动换行 背景色 FFAEAAAA
+            cellStyle3 = baseStyle.copy();
+            XSSFColor color = new XSSFColor();
+            color.setARGBHex("FFAEAAAA");
+            cellStyle3.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            cellStyle3.setFillForegroundColor(color);
+        }
+        return this.workbook;
+    }
+
+    /**
+     * 添加合并单元格并添加边框
+     */
+    private 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
+     */
+    private void setConditionalFormatting(Sheet sheet, CellRangeAddress rangeAddress) {
+        SheetConditionalFormatting conditionalFormatting = sheet.getSheetConditionalFormatting();
+        ConditionalFormattingRule rule = conditionalFormatting.createConditionalFormattingColorScaleRule();
+        XSSFColor[] colors = new XSSFColor[] {
+                new XSSFColor(), new XSSFColor(), new XSSFColor()
+        };
+        colors[0].setARGBHex("FF63BE7B");
+        colors[1].setARGBHex("FFFFEB84");
+        colors[2].setARGBHex("FFF8696B");
+        rule.getColorScaleFormatting().setColors(colors);
+        CellRangeAddress[] cellRangeAddresses = new CellRangeAddress[] {
+                rangeAddress
+        };
+        conditionalFormatting.addConditionalFormatting(cellRangeAddresses, rule);
+    }
+
+    /**
+     * 条件格式 3色渐变 数字小的为红色,大的为绿色
+     * 绿 FF63BE7B 黄 FFFFEB84 红 FFF8696B
+     */
+    private void setConditionalFormatting2(Sheet sheet, CellRangeAddress rangeAddress) {
+        SheetConditionalFormatting conditionalFormatting = sheet.getSheetConditionalFormatting();
+        ConditionalFormattingRule rule = conditionalFormatting.createConditionalFormattingColorScaleRule();
+        XSSFColor[] colors = new XSSFColor[] {
+                new XSSFColor(), new XSSFColor(), new XSSFColor()
+        };
+        colors[0].setARGBHex("FFF8696B");
+        colors[1].setARGBHex("FFFFEB84");
+        colors[2].setARGBHex("FF63BE7B");
+        rule.getColorScaleFormatting().setColors(colors);
+        CellRangeAddress[] cellRangeAddresses = new CellRangeAddress[] {
+                rangeAddress
+        };
+        conditionalFormatting.addConditionalFormatting(cellRangeAddresses, rule);
+    }
+}

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä