Selaa lähdekoodia

feat: Phase 4 配分规则修正与雷达大屏优化

- LedgerSyncServiceImpl: 修正全部指标 ID(1001+ → 11~1522),移除无台账同步类型(reward_penalty/leave_special/banner_letter),添加 L3 级别路由
- ScoreRadarServiceImpl: 修复逗号分隔人名、无记录成员不显示、dept_type 改为 TEAMS、team_name 层级不匹配导致全员 80 分问题
- indicator_init.sql: 完整重写,与 score.sql ID 体系对齐(11~1522,共 128 条)
- 新增 fix_sync_indicator_ids.sql、truncate_ledger.sql 工具脚本
- pom.xml: 补充 airport-ledger 模块依赖声明

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
simonlll 1 kuukausi sitten
vanhempi
commit
b11523984f

+ 6 - 0
airport-admin/pom.xml

@@ -73,6 +73,12 @@
73 73
             <artifactId>airport-check</artifactId>
74 74
         </dependency>
75 75
 
76
+        <!-- 台账管理模块-->
77
+        <dependency>
78
+            <groupId>com.sundot.airport</groupId>
79
+            <artifactId>airport-ledger</artifactId>
80
+        </dependency>
81
+
76 82
         <dependency>
77 83
             <groupId>org.projectlombok</groupId>
78 84
             <artifactId>lombok</artifactId>

+ 108 - 171
airport-ledger/src/main/java/com/sundot/airport/ledger/service/impl/LedgerSyncServiceImpl.java

@@ -16,21 +16,23 @@ import java.util.*;
16 16
 /**
17 17
  * 台账 → score_event 同步引擎
18 18
  *
19
- * 映射规则(indicator ID 固定,与 indicator_init.sql 一致):
19
+ * 映射规则(indicator ID 与 score.sql 保持一致):
20 20
  * ─────────────────────────────────────────────────────────
21
- * supervision_problem   → dim1 / 1001(L2) / 1101(L3, -1)
22
- * realtime_interception → dim1 / 1002(L2) / 1104(L3, -1)
23
- * security_test(未通过) → dim1 / 1004(L2) / 1113(L3, -0.5)
24
- * unsafe_event          → dim1 / 1003(L2) / 1110(L3, -2)
25
- * seizure_stats         → dim1 / 1005(L2) / 1114~1116(L3, +)
26
- * reward_approval       → dim1 / 1005(L2) / 1114~1116(L3, +)
27
- * service_patrol        → dim2 / 1011(L2) / 1123(L3, -0.8)
28
- * complaint             → dim2 / 1010(L2) / 1120(L3, -2)
29
- * terminal_bonus        → dim2 / 1012(L2)  (使用台账 add_score)
30
- * banner_letter         → dim2 / 1012(L2) / 1126锦旗 | 1124感谢信
31
- * exam_score(≥98)       → dim3 / 1024(L2) / 1146(L3, +5)
32
- * reward_penalty        → dim4 / 1033减|1035加(L2) / score_change
33
- * leave_special         → dim6 / 1050(L2) / 1220(L3, 0, 仅记录)
21
+ * supervision_problem   → dim1 / 11(L2) / 111~113(L3, 按层级区分)
22
+ * realtime_interception → dim1 / 12(L2) / 121~122(L3, 按层级区分)
23
+ * security_test(未通过) → dim1 / 14(L2) / 141~143(L3, 按层级区分, 均-0.5)
24
+ * unsafe_event          → dim1 / 13(L2) / 131~135(L3, 按类别区分)
25
+ * seizure_stats         → dim1 / 15(L2) / 151~153(L3, 按类别区分)
26
+ * reward_approval       → dim1 / 15(L2) / 151~153(L3, 按类别区分)
27
+ * service_patrol        → dim2 / 22(L2) / 221~223(L3, 按层级区分)
28
+ * complaint             → dim2 / 21(L2) / 无L3(叶子节点)
29
+ * terminal_bonus        → dim2 / 23(L2) / 231(L3, 使用台账add_score)
30
+ * exam_score(≥98)       → dim3 / 35(L2) / 351理论 | 352开机员(L3)
31
+ *
32
+ * 以下类型暂无台账,不同步(配分表分析.csv 标注"暂无台账,不考虑导入规则"):
33
+ *   banner_letter  → 锦旗及感谢信(服务响应/典型服务案例)
34
+ *   reward_penalty → 部门奖惩记录(作风践行能力全部暂无台账)
35
+ *   leave_special  → 请休假记录(身心调节能力全部暂无台账)
34 36
  */
35 37
 @Service
36 38
 public class LedgerSyncServiceImpl implements ILedgerSyncService {
@@ -45,10 +47,7 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
45 47
     @Autowired private LedgerServicePatrolMapper servicePatrolMapper;
46 48
     @Autowired private LedgerComplaintMapper complaintMapper;
47 49
     @Autowired private LedgerTerminalBonusMapper terminalBonusMapper;
48
-    @Autowired private LedgerBannerLetterMapper bannerLetterMapper;
49 50
     @Autowired private LedgerExamScoreMapper examScoreMapper;
50
-    @Autowired private LedgerRewardPenaltyMapper rewardPenaltyMapper;
51
-    @Autowired private LedgerLeaveSpecialMapper leaveSpecialMapper;
52 51
 
53 52
     // ── Score Mappers ─────────────────────────────────────────
54 53
     @Autowired private ScoreEventMapper scoreEventMapper;
@@ -58,38 +57,41 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
58 57
     // ── Dimension ID cache (loaded once) ─────────────────────
59 58
     private final Map<String, Long> dimCache = new HashMap<>();
60 59
 
61
-    // ── Constants: Indicator IDs (from indicator_init.sql) ──
60
+    // ── Constants: Indicator IDs(与 score.sql 中的显式 ID 对应)
62 61
     // dim1 安全防控能力
63
-    private static final long IND_SUPERVISION_L2   = 1001L; // 员工规范化操作
64
-    private static final long IND_SUPERVISION_L3   = 1101L; // 站层级质控 -1
65
-    private static final long IND_INTERCEPT_L2     = 1002L; // 后台实时质控
66
-    private static final long IND_INTERCEPT_L3     = 1104L; // 站层级(实时拦截)-1
67
-    private static final long IND_UNSAFE_L2        = 1003L; // 不安全事件
68
-    private static final long IND_UNSAFE_L3        = 1110L; // 禁带锐器等 -2 (default)
69
-    private static final long IND_SECTEST_L2       = 1004L; // 安保测试未通过
70
-    private static final long IND_SECTEST_L3       = 1113L; // 站层级 -0.5
71
-    private static final long IND_SEIZURE_L2       = 1005L; // 典型案例查获
72
-    private static final long IND_SEIZURE_L3_A     = 1114L; // 一类查获 +1
73
-    private static final long IND_SEIZURE_L3_B     = 1115L; // 二类查获 +0.5
74
-    private static final long IND_SEIZURE_L3_C     = 1116L; // 特殊物品 +0.15
62
+    private static final long IND_SUPERVISION_L2   = 11L;  // 员工规范化操作
63
+    private static final long IND_SUPERVISION_L3_STATION  = 111L; // 站层级质控 -1.0
64
+    private static final long IND_SUPERVISION_L3_DEPT     = 112L; // 部门层级质控 -0.8
65
+    private static final long IND_SUPERVISION_L3_LEADER   = 113L; // 上级领导巡查 -1.2
66
+    private static final long IND_INTERCEPT_L2     = 12L;  // 后台实时质控拦截
67
+    private static final long IND_INTERCEPT_L3_STATION    = 121L; // 站层级 -1.0
68
+    private static final long IND_INTERCEPT_L3_DEPT       = 122L; // 部门层级 -0.8
69
+    private static final long IND_UNSAFE_L2        = 13L;  // 不安全事件
70
+    private static final long IND_UNSAFE_L3_CAT1   = 131L; // 一类 -10
71
+    private static final long IND_UNSAFE_L3_CAT2   = 132L; // 二类 -8
72
+    private static final long IND_UNSAFE_L3_CAT3   = 133L; // 三类 -5
73
+    private static final long IND_UNSAFE_L3_CAT4   = 134L; // 四类 -3
74
+    private static final long IND_UNSAFE_L3_CAT5   = 135L; // 五类 -2 (默认)
75
+    private static final long IND_SECTEST_L2       = 14L;  // 安保测试未通过
76
+    private static final long IND_SECTEST_L3_GOV   = 141L; // 局方层级 -0.5
77
+    private static final long IND_SECTEST_L3_CORP  = 142L; // 公司层级 -0.5
78
+    private static final long IND_SECTEST_L3_STA   = 143L; // 站层级 -0.5
79
+    private static final long IND_SEIZURE_L2       = 15L;  // 典型案例查获
80
+    private static final long IND_SEIZURE_L3_A     = 151L; // 一类查获 +1.0
81
+    private static final long IND_SEIZURE_L3_B     = 152L; // 二类查获 +0.5
82
+    private static final long IND_SEIZURE_L3_C     = 153L; // 特殊查获 +0.15
75 83
     // dim2 服务响应能力
76
-    private static final long IND_COMPLAINT_L2     = 1010L; // 旅客服务投诉
77
-    private static final long IND_COMPLAINT_L3     = 1120L; // 未按服务质量手册 -2
78
-    private static final long IND_SVCPATROL_L2     = 1011L; // 服务监察
79
-    private static final long IND_SVCPATROL_L3     = 1123L; // 部门层级 -0.8
80
-    private static final long IND_TYPCASE_L2       = 1012L; // 典型服务案例
81
-    private static final long IND_TYPCASE_LETTER   = 1124L; // 表扬信/感谢信 +0.7
82
-    private static final long IND_TYPCASE_BANNER   = 1126L; // 锦旗 +1
84
+    private static final long IND_COMPLAINT_L2     = 21L;  // 旅客服务投诉(叶子节点,无L3)
85
+    private static final long IND_SVCPATROL_L2     = 22L;  // 服务监察
86
+    private static final long IND_SVCPATROL_L3_TERMINAL = 221L; // 航站楼及以上 -1.2
87
+    private static final long IND_SVCPATROL_L3_STATION  = 222L; // 站层级 -1.0
88
+    private static final long IND_SVCPATROL_L3_DEPT     = 223L; // 部门层级 -0.8
89
+    private static final long IND_TYPCASE_L2       = 23L;  // 典型服务案例
90
+    private static final long IND_TYPCASE_L3       = 231L; // 航站楼加分(分值取台账add_score)
83 91
     // dim3 业务实操能力
84
-    private static final long IND_EXAM_L2          = 1024L; // 考试成绩
85
-    private static final long IND_EXAM_L3_THEORY   = 1146L; // 季度理论≥98 +5
86
-    private static final long IND_EXAM_L3_MACHINE  = 1147L; // 季度开机员≥98 +5
87
-    // dim4 作风践行能力
88
-    private static final long IND_PENALTY_L2       = 1033L; // 形象维护(减)
89
-    private static final long IND_REWARD_L2        = 1035L; // 形象维护(加)
90
-    // dim6 身心调节能力
91
-    private static final long IND_LEAVE_L2         = 1050L; // 病假(仅记录)
92
-    private static final long IND_LEAVE_L3         = 1220L; // 病假(不扣分)
92
+    private static final long IND_EXAM_L2          = 35L;  // 考试成绩
93
+    private static final long IND_EXAM_L3_THEORY   = 351L; // 季度理论≥98 +5
94
+    private static final long IND_EXAM_L3_MACHINE  = 352L; // 季度开机员≥98 +5
93 95
 
94 96
     // ── Score defaults ────────────────────────────────────────
95 97
     private static final BigDecimal NEG_ONE   = BigDecimal.valueOf(-1.00);
@@ -116,8 +118,7 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
116 118
         for (String type : Arrays.asList(
117 119
                 "supervision_problem", "realtime_interception", "security_test",
118 120
                 "unsafe_event", "seizure_stats", "reward_approval",
119
-                "service_patrol", "complaint", "terminal_bonus", "banner_letter",
120
-                "exam_score", "reward_penalty", "leave_special")) {
121
+                "service_patrol", "complaint", "terminal_bonus", "exam_score")) {
121 122
             SyncResult r = doSync(type);
122 123
             sb.append(type).append(":+").append(r.getTotalInserted())
123 124
               .append("/skip").append(r.getTotalSkipped()).append("  ");
@@ -149,10 +150,7 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
149 150
             case "service_patrol":        return syncServicePatrol();
150 151
             case "complaint":             return syncComplaint();
151 152
             case "terminal_bonus":        return syncTerminalBonus();
152
-            case "banner_letter":         return syncBannerLetter();
153 153
             case "exam_score":            return syncExamScore();
154
-            case "reward_penalty":        return syncRewardPenalty();
155
-            case "leave_special":         return syncLeaveSpecial();
156 154
             default: return new SyncResult(0, 0, "unknown type: " + type);
157 155
         }
158 156
     }
@@ -161,19 +159,24 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
161 159
     //  Per-table sync methods
162 160
     // ═════════════════════════════════════════════════════════
163 161
 
164
-    /** 1. 部门监察问题记录 → 安全防控能力/员工规范化操作 */
162
+    /** 1. 部门监察问题记录 → 安全防控能力/员工规范化操作(按问题层级区分L3) */
165 163
     private SyncResult syncSupervisionProblem() {
166 164
         List<LedgerSupervisionProblem> list = supervisionMapper.selectList(new LedgerSupervisionProblem());
167 165
         Long dimId = dimCache.get("安全防控能力");
168 166
         ScoreIndicator lv2 = getIndicator(IND_SUPERVISION_L2);
169
-        ScoreIndicator lv3 = getIndicator(IND_SUPERVISION_L3);
170 167
         int ins = 0, skip = 0;
171 168
         for (LedgerSupervisionProblem row : list) {
172 169
             String src = "supervision_problem:" + row.getId();
173 170
             if (existsBySrc(src)) { skip++; continue; }
174 171
             if (!hasName(row.getInspectedName())) { skip++; continue; }
175
-            BigDecimal sv = row.getDeductScore() != null
176
-                    ? row.getDeductScore().negate() : NEG_ONE;
172
+            // 按问题层级选取L3指标和默认分值
173
+            String level = row.getProblemType() != null ? row.getProblemType() : "";
174
+            long l3Id; BigDecimal defaultSv;
175
+            if (level.contains("上级")) { l3Id = IND_SUPERVISION_L3_LEADER; defaultSv = BigDecimal.valueOf(-1.20); }
176
+            else if (level.contains("部门")) { l3Id = IND_SUPERVISION_L3_DEPT; defaultSv = BigDecimal.valueOf(-0.80); }
177
+            else { l3Id = IND_SUPERVISION_L3_STATION; defaultSv = NEG_ONE; }
178
+            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : defaultSv;
179
+            ScoreIndicator lv3 = getIndicator(l3Id);
177 180
             ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getInspectedName(),
178 181
                     row.getTeamName(), row.getRecordDate(),
179 182
                     row.getLocation(), sv, ZERO,
@@ -184,20 +187,19 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
184 187
         return new SyncResult(ins, skip, "");
185 188
     }
186 189
 
187
-    /** 2. 实时质控拦截 → 安全防控能力/后台实时质控拦截 */
190
+    /** 2. 实时质控拦截 → 安全防控能力/后台实时质控拦截(按问题层级区分L3) */
188 191
     private SyncResult syncRealtimeInterception() {
189 192
         List<LedgerRealtimeInterception> list = interceptionMapper.selectList(new LedgerRealtimeInterception());
190 193
         Long dimId = dimCache.get("安全防控能力");
191 194
         ScoreIndicator lv2 = getIndicator(IND_INTERCEPT_L2);
192
-        ScoreIndicator lv3 = getIndicator(IND_INTERCEPT_L3);
193 195
         int ins = 0, skip = 0;
194 196
         for (LedgerRealtimeInterception row : list) {
195 197
             String src = "realtime_interception:" + row.getId();
196 198
             if (existsBySrc(src)) { skip++; continue; }
197 199
             if (!hasName(row.getInspectorName())) { skip++; continue; }
198
-            // 实时拦截是负分(被拦截到问题)
199
-            BigDecimal sv = row.getDeductScore() != null
200
-                    ? row.getDeductScore().negate() : NEG_ONE;
200
+            // 实时拦截台账无层级字段,按实际deductScore计分;L3指标默认站层级
201
+            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : NEG_ONE;
202
+            ScoreIndicator lv3 = getIndicator(IND_INTERCEPT_L3_STATION);
201 203
             ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getInspectorName(),
202 204
                     row.getTeamName(), row.getRecordDate(),
203 205
                     row.getChannelNo(), sv, ZERO,
@@ -208,18 +210,23 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
208 210
         return new SyncResult(ins, skip, "");
209 211
     }
210 212
 
211
-    /** 3. 安保测试(仅"未通过"才计分) */
213
+    /** 3. 安保测试(仅"未通过"才计分,按层级选L3,分值均为-0.5) */
212 214
     private SyncResult syncSecurityTest() {
213 215
         List<LedgerSecurityTest> list = securityTestMapper.selectList(new LedgerSecurityTest());
214 216
         Long dimId = dimCache.get("安全防控能力");
215 217
         ScoreIndicator lv2 = getIndicator(IND_SECTEST_L2);
216
-        ScoreIndicator lv3 = getIndicator(IND_SECTEST_L3);
217 218
         int ins = 0, skip = 0;
218 219
         for (LedgerSecurityTest row : list) {
219 220
             String src = "security_test:" + row.getId();
220 221
             if (existsBySrc(src)) { skip++; continue; }
221 222
             if (!"未通过".equals(row.getTestResult())) { skip++; continue; }
222 223
             if (!hasName(row.getTestedName())) { skip++; continue; }
224
+            String level = row.getTestType() != null ? row.getTestType() : "";
225
+            long l3Id;
226
+            if (level.contains("局方")) { l3Id = IND_SECTEST_L3_GOV; }
227
+            else if (level.contains("公司")) { l3Id = IND_SECTEST_L3_CORP; }
228
+            else { l3Id = IND_SECTEST_L3_STA; }
229
+            ScoreIndicator lv3 = getIndicator(l3Id);
223 230
             ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getTestedName(),
224 231
                     row.getTeamName(), row.getRecordDate(),
225 232
                     null, NEG_HALF, ZERO,
@@ -230,19 +237,25 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
230 237
         return new SyncResult(ins, skip, "");
231 238
     }
232 239
 
233
-    /** 4. 不安全事件 */
240
+    /** 4. 不安全事件(按类别选L3和默认分值) */
234 241
     private SyncResult syncUnsafeEvent() {
235 242
         List<LedgerUnsafeEvent> list = unsafeEventMapper.selectList(new LedgerUnsafeEvent());
236 243
         Long dimId = dimCache.get("安全防控能力");
237 244
         ScoreIndicator lv2 = getIndicator(IND_UNSAFE_L2);
238
-        ScoreIndicator lv3 = getIndicator(IND_UNSAFE_L3);
239 245
         int ins = 0, skip = 0;
240 246
         for (LedgerUnsafeEvent row : list) {
241 247
             String src = "unsafe_event:" + row.getId();
242 248
             if (existsBySrc(src)) { skip++; continue; }
243 249
             if (!hasName(row.getResponsibleName())) { skip++; continue; }
244
-            BigDecimal sv = row.getDeductScore() != null
245
-                    ? row.getDeductScore().negate() : NEG_TWO;
250
+            String cat = row.getEventType() != null ? row.getEventType() : "";
251
+            long l3Id; BigDecimal defaultSv;
252
+            if (cat.contains("一类"))      { l3Id = IND_UNSAFE_L3_CAT1; defaultSv = BigDecimal.valueOf(-10.00); }
253
+            else if (cat.contains("二类")) { l3Id = IND_UNSAFE_L3_CAT2; defaultSv = BigDecimal.valueOf(-8.00); }
254
+            else if (cat.contains("三类")) { l3Id = IND_UNSAFE_L3_CAT3; defaultSv = BigDecimal.valueOf(-5.00); }
255
+            else if (cat.contains("四类")) { l3Id = IND_UNSAFE_L3_CAT4; defaultSv = BigDecimal.valueOf(-3.00); }
256
+            else                           { l3Id = IND_UNSAFE_L3_CAT5; defaultSv = NEG_TWO; }
257
+            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : defaultSv;
258
+            ScoreIndicator lv3 = getIndicator(l3Id);
246 259
             ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getResponsibleName(),
247 260
                     row.getTeamName(), row.getRecordDate(),
248 261
                     null, sv, ZERO,
@@ -253,7 +266,7 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
253 266
         return new SyncResult(ins, skip, "");
254 267
     }
255 268
 
256
-    /** 5. 查获违规品统计(加分) */
269
+    /** 5. 查获违规品统计(加分,按类别选L3) */
257 270
     private SyncResult syncSeizureStats() {
258 271
         List<LedgerSeizureStats> list = seizureMapper.selectList(new LedgerSeizureStats());
259 272
         Long dimId = dimCache.get("安全防控能力");
@@ -263,13 +276,11 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
263 276
             String src = "seizure_stats:" + row.getId();
264 277
             if (existsBySrc(src)) { skip++; continue; }
265 278
             if (!hasName(row.getInspectorName())) { skip++; continue; }
266
-            // 按类别确定指标和分值
267 279
             String cat = row.getItemCategory() != null ? row.getItemCategory() : "";
268 280
             long l3Id; BigDecimal sv;
269
-            if (cat.contains("一类")) { l3Id = IND_SEIZURE_L3_A; sv = POS_ONE; }
281
+            if (cat.contains("一类"))      { l3Id = IND_SEIZURE_L3_A; sv = POS_ONE; }
270 282
             else if (cat.contains("二类")) { l3Id = IND_SEIZURE_L3_B; sv = POS_HALF; }
271
-            else { l3Id = IND_SEIZURE_L3_C; sv = POS_015; }
272
-            // 叠加分
283
+            else                           { l3Id = IND_SEIZURE_L3_C; sv = POS_015; }
273 284
             BigDecimal cascade = row.getStackedScore() != null ? row.getStackedScore() : ZERO;
274 285
             ScoreIndicator lv3 = getIndicator(l3Id);
275 286
             ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getInspectorName(),
@@ -282,7 +293,7 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
282 293
         return new SyncResult(ins, skip, "");
283 294
     }
284 295
 
285
-    /** 6. 小额奖励审批单(安全类查获加分) */
296
+    /** 6. 小额奖励审批单(安全类查获加分,按类别选L3) */
286 297
     private SyncResult syncRewardApproval() {
287 298
         List<LedgerRewardApproval> list = rewardApprovalMapper.selectList(new LedgerRewardApproval());
288 299
         Long dimId = dimCache.get("安全防控能力");
@@ -292,12 +303,11 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
292 303
             String src = "reward_approval:" + row.getId();
293 304
             if (existsBySrc(src)) { skip++; continue; }
294 305
             if (!hasName(row.getPersonName())) { skip++; continue; }
295
-            // 奖励类别
296 306
             String cat = row.getRewardType() != null ? row.getRewardType() : "";
297 307
             long l3Id; BigDecimal sv;
298
-            if ("一类".equals(cat) || cat.contains("一类")) { l3Id = IND_SEIZURE_L3_A; sv = POS_ONE; }
299
-            else if ("二类".equals(cat) || cat.contains("二类")) { l3Id = IND_SEIZURE_L3_B; sv = POS_HALF; }
300
-            else { l3Id = IND_SEIZURE_L3_C; sv = POS_015; }
308
+            if (cat.contains("一类"))      { l3Id = IND_SEIZURE_L3_A; sv = POS_ONE; }
309
+            else if (cat.contains("二类")) { l3Id = IND_SEIZURE_L3_B; sv = POS_HALF; }
310
+            else                           { l3Id = IND_SEIZURE_L3_C; sv = POS_015; }
301 311
             ScoreIndicator lv3 = getIndicator(l3Id);
302 312
             ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getPersonName(),
303 313
                     row.getTeamName(), row.getApproveDate() != null ? new java.util.Date(row.getApproveDate().getTime()) : null,
@@ -309,19 +319,23 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
309 319
         return new SyncResult(ins, skip, "");
310 320
     }
311 321
 
312
-    /** 7. 服务巡查 → 服务响应能力/服务监察 */
322
+    /** 7. 服务巡查 → 服务响应能力/服务监察(按问题层级区分L3) */
313 323
     private SyncResult syncServicePatrol() {
314 324
         List<LedgerServicePatrol> list = servicePatrolMapper.selectList(new LedgerServicePatrol());
315 325
         Long dimId = dimCache.get("服务响应能力");
316 326
         ScoreIndicator lv2 = getIndicator(IND_SVCPATROL_L2);
317
-        ScoreIndicator lv3 = getIndicator(IND_SVCPATROL_L3);
318 327
         int ins = 0, skip = 0;
319 328
         for (LedgerServicePatrol row : list) {
320 329
             String src = "service_patrol:" + row.getId();
321 330
             if (existsBySrc(src)) { skip++; continue; }
322 331
             if (!hasName(row.getInspectedName())) { skip++; continue; }
323
-            BigDecimal sv = row.getDeductScore() != null
324
-                    ? row.getDeductScore().negate() : NEG_08;
332
+            String level = row.getServiceType() != null ? row.getServiceType() : "";
333
+            long l3Id; BigDecimal defaultSv;
334
+            if (level.contains("航站楼")) { l3Id = IND_SVCPATROL_L3_TERMINAL; defaultSv = BigDecimal.valueOf(-1.20); }
335
+            else if (level.contains("站层级") || level.contains("站级")) { l3Id = IND_SVCPATROL_L3_STATION; defaultSv = NEG_ONE; }
336
+            else { l3Id = IND_SVCPATROL_L3_DEPT; defaultSv = NEG_08; }
337
+            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : defaultSv;
338
+            ScoreIndicator lv3 = getIndicator(l3Id);
325 339
             ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getInspectedName(),
326 340
                     row.getTeamName(), row.getRecordDate(),
327 341
                     row.getLocation(), sv, ZERO,
@@ -332,20 +346,18 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
332 346
         return new SyncResult(ins, skip, "");
333 347
     }
334 348
 
335
-    /** 8. 投诉情况(有责任人才计分) */
349
+    /** 8. 投诉情况(有责任人才计分;旅客服务投诉为L2叶子节点,无L3) */
336 350
     private SyncResult syncComplaint() {
337 351
         List<LedgerComplaint> list = complaintMapper.selectList(new LedgerComplaint());
338 352
         Long dimId = dimCache.get("服务响应能力");
339 353
         ScoreIndicator lv2 = getIndicator(IND_COMPLAINT_L2);
340
-        ScoreIndicator lv3 = getIndicator(IND_COMPLAINT_L3);
341 354
         int ins = 0, skip = 0;
342 355
         for (LedgerComplaint row : list) {
343 356
             String src = "complaint:" + row.getId();
344 357
             if (existsBySrc(src)) { skip++; continue; }
345 358
             if (!hasName(row.getResponsibleName())) { skip++; continue; }
346
-            BigDecimal sv = row.getDeductScore() != null
347
-                    ? row.getDeductScore().negate() : NEG_TWO;
348
-            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getResponsibleName(),
359
+            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : NEG_TWO;
360
+            ScoreEvent e = buildEvent(dimId, lv2, null, row.getResponsibleName(),
349 361
                     row.getTeamName(), row.getRecordDate(),
350 362
                     null, sv, ZERO,
351 363
                     row.getComplaintDesc(), src, row.getEvidenceFile());
@@ -355,12 +367,12 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
355 367
         return new SyncResult(ins, skip, "");
356 368
     }
357 369
 
358
-    /** 9. 航站楼加分 */
370
+    /** 9. 航站楼加分 → 服务响应能力/典型服务案例/航站楼加分(分值取台账add_score) */
359 371
     private SyncResult syncTerminalBonus() {
360 372
         List<LedgerTerminalBonus> list = terminalBonusMapper.selectList(new LedgerTerminalBonus());
361 373
         Long dimId = dimCache.get("服务响应能力");
362 374
         ScoreIndicator lv2 = getIndicator(IND_TYPCASE_L2);
363
-        ScoreIndicator lv3 = getIndicator(IND_TYPCASE_LETTER);  // 默认表扬信
375
+        ScoreIndicator lv3 = getIndicator(IND_TYPCASE_L3);
364 376
         int ins = 0, skip = 0;
365 377
         for (LedgerTerminalBonus row : list) {
366 378
             String src = "terminal_bonus:" + row.getId();
@@ -377,32 +389,7 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
377 389
         return new SyncResult(ins, skip, "");
378 390
     }
379 391
 
380
-    /** 10. 锦旗及感谢信 */
381
-    private SyncResult syncBannerLetter() {
382
-        List<LedgerBannerLetter> list = bannerLetterMapper.selectList(new LedgerBannerLetter());
383
-        Long dimId = dimCache.get("服务响应能力");
384
-        ScoreIndicator lv2 = getIndicator(IND_TYPCASE_L2);
385
-        int ins = 0, skip = 0;
386
-        for (LedgerBannerLetter row : list) {
387
-            String src = "banner_letter:" + row.getId();
388
-            if (existsBySrc(src)) { skip++; continue; }
389
-            if (!hasName(row.getPersonName())) { skip++; continue; }
390
-            boolean isBanner = "1".equals(row.getType());
391
-            long l3Id = isBanner ? IND_TYPCASE_BANNER : IND_TYPCASE_LETTER;
392
-            BigDecimal sv = row.getAddScore() != null ? row.getAddScore()
393
-                    : (isBanner ? POS_ONE : POS_07);
394
-            ScoreIndicator lv3 = getIndicator(l3Id);
395
-            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getPersonName(),
396
-                    row.getTeamName(), row.getRecordDate(),
397
-                    null, sv, ZERO,
398
-                    row.getContentDesc(), src, row.getEvidenceFile());
399
-            scoreEventMapper.insert(e);
400
-            ins++;
401
-        }
402
-        return new SyncResult(ins, skip, "");
403
-    }
404
-
405
-    /** 11. 成绩收集(≥98加5分,其余仅记录) */
392
+    /** 10. 成绩收集(≥98加5分;其余不计分,暂无台账规则) */
406 393
     private SyncResult syncExamScore() {
407 394
         List<LedgerExamScore> list = examScoreMapper.selectList(new LedgerExamScore());
408 395
         Long dimId = dimCache.get("业务实操能力");
@@ -414,21 +401,16 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
414 401
             if (!hasName(row.getPersonName())) { skip++; continue; }
415 402
             if (row.getScore() == null) { skip++; continue; }
416 403
             double score = row.getScore().doubleValue();
417
-            // ≥98 → 加分;<60 → 扣分(补考);其余仅记录
418
-            BigDecimal sv;
419
-            ScoreIndicator lv3;
420
-            if (score >= 98) {
421
-                sv = POS_FIVE;
422
-                lv3 = getIndicator(IND_EXAM_L3_THEORY);
423
-            } else if (score < 60) {
424
-                sv = BigDecimal.valueOf(-2.00);
425
-                lv3 = getIndicator(1140L); // 补考(阶梯递增)
426
-            } else {
427
-                skip++; continue; // 60~97 仅记录,不影响得分
428
-            }
404
+            // 仅≥98分加分;补考扣分规则暂无台账,不处理
405
+            if (score < 98) { skip++; continue; }
406
+            // 按科目区分L3(图像成绩→开机员能力,其余→理论)
407
+            String category = row.getExamCategory() != null ? row.getExamCategory() : "";
408
+            long l3Id = category.contains("图像") || category.contains("开机")
409
+                    ? IND_EXAM_L3_MACHINE : IND_EXAM_L3_THEORY;
410
+            ScoreIndicator lv3 = getIndicator(l3Id);
429 411
             ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getPersonName(),
430 412
                     row.getTeamName(), null,
431
-                    null, sv, ZERO,
413
+                    null, POS_FIVE, ZERO,
432 414
                     row.getExamCategory() + " " + row.getExamPeriod(), src, null);
433 415
             scoreEventMapper.insert(e);
434 416
             ins++;
@@ -436,50 +418,6 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
436 418
         return new SyncResult(ins, skip, "");
437 419
     }
438 420
 
439
-    /** 12. 部门奖惩记录 */
440
-    private SyncResult syncRewardPenalty() {
441
-        List<LedgerRewardPenalty> list = rewardPenaltyMapper.selectList(new LedgerRewardPenalty());
442
-        Long dimId = dimCache.get("作风践行能力");
443
-        int ins = 0, skip = 0;
444
-        for (LedgerRewardPenalty row : list) {
445
-            String src = "reward_penalty:" + row.getId();
446
-            if (existsBySrc(src)) { skip++; continue; }
447
-            if (!hasName(row.getPersonName())) { skip++; continue; }
448
-            boolean isPenalty = "2".equals(row.getType());
449
-            ScoreIndicator lv2 = getIndicator(isPenalty ? IND_PENALTY_L2 : IND_REWARD_L2);
450
-            BigDecimal sv = row.getScoreChange() != null ? row.getScoreChange()
451
-                    : (isPenalty ? NEG_TWO : POS_ONE);
452
-            ScoreEvent e = buildEvent(dimId, lv2, null, row.getPersonName(),
453
-                    row.getTeamName(), row.getRecordDate(),
454
-                    null, sv, ZERO,
455
-                    row.getEventDesc(), src, row.getEvidenceFile());
456
-            scoreEventMapper.insert(e);
457
-            ins++;
458
-        }
459
-        return new SyncResult(ins, skip, "");
460
-    }
461
-
462
-    /** 13. 请休假记录(仅记录,不计分) */
463
-    private SyncResult syncLeaveSpecial() {
464
-        List<LedgerLeaveSpecial> list = leaveSpecialMapper.selectList(new LedgerLeaveSpecial());
465
-        Long dimId = dimCache.get("身心调节能力");
466
-        ScoreIndicator lv2 = getIndicator(IND_LEAVE_L2);
467
-        ScoreIndicator lv3 = getIndicator(IND_LEAVE_L3);
468
-        int ins = 0, skip = 0;
469
-        for (LedgerLeaveSpecial row : list) {
470
-            String src = "leave_special:" + row.getId();
471
-            if (existsBySrc(src)) { skip++; continue; }
472
-            if (!hasName(row.getPersonName())) { skip++; continue; }
473
-            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getPersonName(),
474
-                    row.getTeamName(), null,
475
-                    null, ZERO, ZERO,
476
-                    row.getLeaveType(), src, null);
477
-            scoreEventMapper.insert(e);
478
-            ins++;
479
-        }
480
-        return new SyncResult(ins, skip, "");
481
-    }
482
-
483 421
     // ═════════════════════════════════════════════════════════
484 422
     //  Helpers
485 423
     // ═════════════════════════════════════════════════════════
@@ -514,7 +452,6 @@ public class LedgerSyncServiceImpl implements ILedgerSyncService {
514 452
                 .filter(x -> x.getValue().equals(dimId)).map(Map.Entry::getKey).findFirst().orElse("") : "");
515 453
         if (lv2 != null) {
516 454
             e.setLevel2Name(lv2.getName());
517
-            // lv3 作为末级指标 ID
518 455
         }
519 456
         if (lv3 != null) {
520 457
             e.setIndicatorId(lv3.getId());

+ 69 - 18
airport-ledger/src/main/java/com/sundot/airport/ledger/service/impl/ScoreRadarServiceImpl.java

@@ -1,6 +1,7 @@
1 1
 package com.sundot.airport.ledger.service.impl;
2 2
 
3 3
 import com.sundot.airport.common.core.domain.entity.SysDept;
4
+import com.sundot.airport.common.core.domain.entity.SysUser;
4 5
 import com.sundot.airport.ledger.domain.ScoreDimension;
5 6
 import com.sundot.airport.ledger.domain.ScoreEvent;
6 7
 import com.sundot.airport.ledger.domain.vo.MemberScoreVO;
@@ -8,6 +9,7 @@ import com.sundot.airport.ledger.mapper.ScoreDimensionMapper;
8 9
 import com.sundot.airport.ledger.mapper.ScoreEventMapper;
9 10
 import com.sundot.airport.ledger.service.IScoreRadarService;
10 11
 import com.sundot.airport.system.mapper.SysDeptMapper;
12
+import com.sundot.airport.system.mapper.SysUserMapper;
11 13
 import org.springframework.beans.factory.annotation.Autowired;
12 14
 import org.springframework.stereotype.Service;
13 15
 
@@ -28,11 +30,13 @@ public class ScoreRadarServiceImpl implements IScoreRadarService {
28 30
     @Autowired
29 31
     private SysDeptMapper sysDeptMapper;
30 32
 
33
+    @Autowired
34
+    private SysUserMapper sysUserMapper;
35
+
31 36
     @Override
32 37
     public List<String> selectTeamList() {
33
-        // 从 sys_dept 读 dept_type=MANAGER(班组)的列表,不依赖 score_event 是否有数据
34 38
         SysDept query = new SysDept();
35
-        query.setDeptType("MANAGER");
39
+        query.setDeptType("TEAMS");
36 40
         return sysDeptMapper.selectDeptList(query).stream()
37 41
                 .map(SysDept::getDeptName)
38 42
                 .filter(name -> name != null && !name.isEmpty())
@@ -48,37 +52,57 @@ public class ScoreRadarServiceImpl implements IScoreRadarService {
48 52
         List<ScoreDimension> dimensions = scoreDimensionMapper.selectList(dq);
49 53
         dimensions.sort(Comparator.comparing(d -> (d.getSortOrder() == null ? 999 : d.getSortOrder())));
50 54
 
51
-        // 2. 获取该班组所有事件记录
52
-        ScoreEvent eq = new ScoreEvent();
53
-        eq.setTeamName(teamName);
54
-        List<ScoreEvent> events = scoreEventMapper.selectList(eq);
55
+        // 2. 从 sys_dept + sys_user 获取小组完整成员列表
56
+        //    即使没有加减分记录,成员也需要展示
57
+        Set<String> rosterNames = getMemberRoster(teamName);
58
+
59
+        // 3. 加载全量事件记录,按花名册姓名过滤
60
+        //    不依赖 team_name 字段(台账导入的 team_name 是班组级别,与小组名不匹配)
61
+        //    通过姓名匹配花名册,确保只统计本小组成员的事件
62
+        //    原始数据不规范:person_name 可能为 "张三,李四,王五",需拆分后各自计分
63
+        List<ScoreEvent> events = scoreEventMapper.selectList(new ScoreEvent());
55 64
 
56
-        // 3. 按姓名分组 → 每人的维度事件分合计
65
+        // 4. 按拆分后的姓名分组汇总各维度得分(仅统计花名册成员)
57 66
         //    Map<personName, Map<dimensionId, eventScoreSum>>
58 67
         Map<String, Map<Long, BigDecimal>> personDimMap = new LinkedHashMap<>();
59 68
         for (ScoreEvent e : events) {
60
-            String person = e.getPersonName();
61
-            if (person == null || person.trim().isEmpty()) continue;
69
+            String rawName = e.getPersonName();
70
+            if (rawName == null || rawName.trim().isEmpty()) continue;
62 71
             Long dimId = e.getDimensionId();
72
+            if (dimId == null) continue;
63 73
             BigDecimal val = e.getTotalScore() != null ? e.getTotalScore() : BigDecimal.ZERO;
64
-            personDimMap
65
-                    .computeIfAbsent(person, k -> new HashMap<>())
66
-                    .merge(dimId, val, BigDecimal::add);
74
+
75
+            // 拆分逗号分隔的多人姓名,只处理在本小组花名册中的人
76
+            String[] names = rawName.split("[,,]");
77
+            for (String n : names) {
78
+                String name = n.trim();
79
+                if (name.isEmpty()) continue;
80
+                if (!rosterNames.contains(name)) continue; // 不在本组,跳过
81
+                personDimMap
82
+                        .computeIfAbsent(name, k -> new HashMap<>())
83
+                        .merge(dimId, val, BigDecimal::add);
84
+            }
67 85
         }
68 86
 
69
-        // 4. 构建 VO
87
+        // 5. 合并花名册与事件记录:花名册中的人若无事件记录,也需要出现
88
+        //    先把花名册成员加入(保证顺序:花名册在前,事件中有但花名册没有的追加到后面)
89
+        Set<String> allNames = new LinkedHashSet<>(rosterNames);
90
+        allNames.addAll(personDimMap.keySet());
91
+
92
+        // 6. 构建 VO
70 93
         List<MemberScoreVO> result = new ArrayList<>();
71
-        for (Map.Entry<String, Map<Long, BigDecimal>> entry : personDimMap.entrySet()) {
94
+        for (String name : allNames) {
95
+            Map<Long, BigDecimal> dimScoreMap = personDimMap.getOrDefault(name, Collections.emptyMap());
96
+
72 97
             MemberScoreVO vo = new MemberScoreVO();
73
-            vo.setPersonName(entry.getKey());
98
+            vo.setPersonName(name);
74 99
             vo.setTeamName(teamName);
75 100
 
76 101
             List<MemberScoreVO.DimScore> dimScores = new ArrayList<>();
77 102
             BigDecimal totalScore = BigDecimal.ZERO;
78 103
 
79 104
             for (ScoreDimension dim : dimensions) {
80
-                BigDecimal eventScore = entry.getValue()
81
-                        .getOrDefault(dim.getId(), BigDecimal.ZERO);
105
+                BigDecimal eventScore = dimScoreMap.getOrDefault(dim.getId(), BigDecimal.ZERO);
82 106
                 BigDecimal base = dim.getBaseScore() != null ? dim.getBaseScore() : BigDecimal.valueOf(80);
83 107
                 BigDecimal dimScore = base.add(eventScore);
84 108
 
@@ -105,8 +129,35 @@ public class ScoreRadarServiceImpl implements IScoreRadarService {
105 129
             result.add(vo);
106 130
         }
107 131
 
108
-        // 5. 按综合得分降序排序
132
+        // 7. 按综合得分降序排序
109 133
         result.sort((a, b) -> b.getTotalScore().compareTo(a.getTotalScore()));
110 134
         return result;
111 135
     }
136
+
137
+    /**
138
+     * 从 sys_dept + sys_user 查询班组完整花名册
139
+     * dept_type='MANAGER' 对应班组,nick_name 对应 person_name
140
+     */
141
+    private Set<String> getMemberRoster(String teamName) {
142
+        // 找班组对应的 dept_id
143
+        SysDept deptQuery = new SysDept();
144
+        deptQuery.setDeptType("TEAMS");
145
+        deptQuery.setDeptName(teamName);
146
+        List<SysDept> depts = sysDeptMapper.selectDeptList(deptQuery);
147
+        if (depts == null || depts.isEmpty()) {
148
+            return new LinkedHashSet<>();
149
+        }
150
+        Long deptId = depts.get(0).getDeptId();
151
+
152
+        // 查该班组下所有在职用户的 nick_name
153
+        List<SysUser> users = sysUserMapper.selectUsersByDeptId(deptId);
154
+        if (users == null) {
155
+            return new LinkedHashSet<>();
156
+        }
157
+        return users.stream()
158
+                .filter(u -> "0".equals(u.getStatus()) && "0".equals(u.getDelFlag()))
159
+                .map(SysUser::getNickName)
160
+                .filter(n -> n != null && !n.trim().isEmpty())
161
+                .collect(Collectors.toCollection(LinkedHashSet::new));
162
+    }
112 163
 }

+ 8 - 0
pom.xml

@@ -253,6 +253,13 @@
253 253
                 <version>${airport.version}</version>
254 254
             </dependency>
255 255
 
256
+            <!-- 台账管理模块-->
257
+            <dependency>
258
+                <groupId>com.sundot.airport</groupId>
259
+                <artifactId>airport-ledger</artifactId>
260
+                <version>${airport.version}</version>
261
+            </dependency>
262
+
256 263
             <dependency>
257 264
                 <groupId>org.projectlombok</groupId>
258 265
                 <artifactId>lombok</artifactId>
@@ -281,6 +288,7 @@
281 288
         <module>airport-exam</module>
282 289
         <module>airport-check</module>
283 290
         <module>airport-attendance</module>
291
+        <module>airport-ledger</module>
284 292
     </modules>
285 293
     <packaging>pom</packaging>
286 294
 

+ 540 - 0
sql/excel_to_sql.py

@@ -0,0 +1,540 @@
1
+#!/usr/bin/env python3
2
+# -*- coding: utf-8 -*-
3
+"""
4
+旅检三部"三三"数字管理平台 Excel → SQL 导入脚本
5
+运行: python excel_to_sql.py
6
+输出: ledger_data_import.sql
7
+"""
8
+
9
+import os, re, sys
10
+from datetime import datetime, date
11
+
12
+try:
13
+    import openpyxl
14
+except ImportError:
15
+    sys.exit("请先安装 openpyxl: pip install openpyxl")
16
+
17
+# ──────────────────────────────────────────────
18
+# 配置
19
+# ──────────────────────────────────────────────
20
+EXCEL_DIR  = r'C:\Users\linzo\Downloads'
21
+OUTPUT_SQL = r'C:\Users\linzo\IdeaProjects\chongqing-server\sql\ledger_data_import.sql'
22
+BATCH_NO   = datetime.now().strftime('BATCH_%Y%m%d_%H%M%S')
23
+CREATE_BY  = 'admin'
24
+CREATE_TIME = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
25
+
26
+# ──────────────────────────────────────────────
27
+# 工具函数
28
+# ──────────────────────────────────────────────
29
+def find_excel():
30
+    for f in os.listdir(EXCEL_DIR):
31
+        if '旅检' in f and f.endswith('.xlsx') and not f.startswith('~'):
32
+            return os.path.join(EXCEL_DIR, f)
33
+    raise FileNotFoundError('找不到 旅检*.xlsx 文件,请检查路径')
34
+
35
+def esc(v, maxlen=None):
36
+    """转义 SQL 字符串值,可选最大长度截断"""
37
+    if v is None:
38
+        return 'NULL'
39
+    s = str(v).strip()
40
+    if s == '' or s == 'None':
41
+        return 'NULL'
42
+    s = s.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n').replace('\r', '').replace('\t', ' ')
43
+    if maxlen and len(s) > maxlen:
44
+        s = s[:maxlen]
45
+    return f"'{s}'"
46
+
47
+def to_date(v):
48
+    """将各种格式的日期转成 'YYYY-MM-DD' 或 NULL"""
49
+    if v is None:
50
+        return 'NULL'
51
+    if isinstance(v, (datetime, date)):
52
+        return f"'{v.strftime('%Y-%m-%d')}'"
53
+    s = str(v).strip()
54
+    if not s or s == 'None':
55
+        return 'NULL'
56
+    # 尝试多种格式
57
+    for fmt in ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d', '%Y/%m/%d', '%m/%d/%Y'):
58
+        try:
59
+            return f"'{datetime.strptime(s.split('.')[0].strip(), fmt).strftime('%Y-%m-%d')}'"
60
+        except ValueError:
61
+            pass
62
+    # 返回原始字符串(可能有特殊格式)
63
+    return esc(s[:10])
64
+
65
+def to_decimal(v, default='NULL'):
66
+    if v is None:
67
+        return default
68
+    s = str(v).strip().replace('¥', '').replace(',', '').strip()
69
+    if not s or s == 'None':
70
+        return default
71
+    try:
72
+        return str(float(s))
73
+    except ValueError:
74
+        return default
75
+
76
+def to_int(v, default='NULL'):
77
+    if v is None:
78
+        return default
79
+    try:
80
+        return str(int(float(str(v))))
81
+    except (ValueError, TypeError):
82
+        return default
83
+
84
+def row_vals(ws, row_num):
85
+    """返回一行所有单元格值(列表)"""
86
+    row = list(ws.iter_rows(min_row=row_num, max_row=row_num, values_only=True))
87
+    return list(row[0]) if row else []
88
+
89
+def iter_data_rows(ws, header_row=2):
90
+    """从 header_row+1 开始迭代有效数据行"""
91
+    for row in ws.iter_rows(min_row=header_row + 1, values_only=True):
92
+        # 跳过全空行
93
+        if all(v is None or str(v).strip() == '' for v in row):
94
+            continue
95
+        yield list(row)
96
+
97
+def pad(lst, n):
98
+    """把列表补齐到 n 长度"""
99
+    return lst + [None] * max(0, n - len(lst))
100
+
101
+# ──────────────────────────────────────────────
102
+# 各 sheet 的映射函数
103
+# 每个函数返回 list of SQL INSERT 语句
104
+# ──────────────────────────────────────────────
105
+
106
+def sheet_supervision_problem(ws):
107
+    """部门监察问题记录表 → ledger_supervision_problem
108
+    Excel R2: 时间(0) 区域(1) 工作点(2) 岗位(3) 责任人(4) 问题描述(5)
109
+              问题类型(6) 整改措施(7) 依据(8) 问题层级(9) 班组(10)
110
+              队室质控员(11) 质控推送队室负责人(12) 附件(13) 整改情况(14)
111
+              佐证材料(15) 发送至相关人员(16) 本月内发生问题次数(17)
112
+              上月质控问题超过三次人员(18) 检查人员(19) 分管质控经理(20)
113
+              录入时间(21) 部门培训教员(22) 队室内勤(23) 队室负责人(24)
114
+    """
115
+    stmts = []
116
+    for cols in iter_data_rows(ws, header_row=2):
117
+        c = pad(cols, 25)
118
+        stmts.append(
119
+            f"INSERT INTO ledger_supervision_problem "
120
+            f"(record_date,location,channel_no,inspected_name,problem_desc,problem_type,"
121
+            f"result_handling,remark,team_name,inspector_name,evidence_file,"
122
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
123
+            f"{to_date(c[0])},{esc(c[1])},{esc(c[2])},{esc(c[4])},{esc(c[5])},{esc(c[6])},"
124
+            f"{esc(c[7])},{esc(c[8])},{esc(c[10])},{esc(c[11])},{esc(c[13])},"
125
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
126
+        )
127
+    return stmts
128
+
129
+
130
+def sheet_patrol_inspection(ws):
131
+    """队室三级质控巡查记录表 → ledger_patrol_inspection
132
+    Excel R2: 巡查日期(0) 巡查区域(1) 巡查工作点(2) 巡查时间段(3) 巡查岗位(4)
133
+              被检查人员(5) 有无问题(6) 检查情况描述(7) 类型(8) 整改措施(9)
134
+              附件(10) 整改情况(11) 佐证材料(12) 班组(13) 责任组长(14)
135
+              队室质控员(15) 队室负责人(16) 填报人(17) 队室内勤(18) 填报时间(19)
136
+    """
137
+    stmts = []
138
+    for cols in iter_data_rows(ws, header_row=2):
139
+        c = pad(cols, 20)
140
+        stmts.append(
141
+            f"INSERT INTO ledger_patrol_inspection "
142
+            f"(record_date,location,channel_no,inspected_name,patrol_type,patrol_item,"
143
+            f"problem_desc,result_handling,evidence_file,team_name,inspector_name,"
144
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
145
+            f"{to_date(c[0])},{esc(c[1])},{esc(c[2])},{esc(c[5])},{esc(c[8])},{esc(c[4])},"
146
+            f"{esc(c[7])},{esc(c[9])},{esc(c[10])},{esc(c[13])},{esc(c[14])},"
147
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
148
+        )
149
+    return stmts
150
+
151
+
152
+def _parse_datetime(v):
153
+    """返回 (date_str, time_str) tuple for SQL"""
154
+    if v is None:
155
+        return 'NULL', 'NULL'
156
+    if isinstance(v, datetime):
157
+        return f"'{v.strftime('%Y-%m-%d')}'", f"'{v.strftime('%H:%M:%S')}'"
158
+    s = str(v).strip()
159
+    if not s or s == 'None':
160
+        return 'NULL', 'NULL'
161
+    for fmt in ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d'):
162
+        try:
163
+            dt = datetime.strptime(s.split('.')[0].strip(), fmt)
164
+            return f"'{dt.strftime('%Y-%m-%d')}'", f"'{dt.strftime('%H:%M:%S')}'"
165
+        except ValueError:
166
+            pass
167
+    return esc(s[:10]), 'NULL'
168
+
169
+
170
+def sheet_realtime_interception(ws):
171
+    """部门实时质控拦截情况记录表 → ledger_realtime_interception
172
+    Excel R2: 时间(0) 区域(1) 工作点(2) 岗位(3) 责任人(4) 实时质控拦截物品(5)
173
+              个数(6) 问题类型(7) 整改措施(8) 问题层级(9) 班组(10)
174
+              附件(11) 问题类别(12) 个人复盘(13) 队室复盘(14)
175
+              队室质控员(15) 质控推送队室负责人(16) 开机年限(17)
176
+              责任人开机年龄(18) 发送至相关人员(19)
177
+    """
178
+    stmts = []
179
+    for cols in iter_data_rows(ws, header_row=2):
180
+        c = pad(cols, 20)
181
+        d, t = _parse_datetime(c[0])
182
+        stmts.append(
183
+            f"INSERT INTO ledger_realtime_interception "
184
+            f"(record_date,record_time,channel_no,inspector_name,item_name,"
185
+            f"item_quantity,item_category,handling_method,team_name,evidence_file,remark,"
186
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
187
+            f"{d},{t},{esc(c[2])},{esc(c[4])},{esc(c[5])},"
188
+            f"{to_int(c[6])},{esc(c[7])},{esc(c[8])},{esc(c[10])},{esc(c[11])},{esc(c[12])},"
189
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
190
+        )
191
+    return stmts
192
+
193
+
194
+def sheet_service_patrol(ws):
195
+    """服务巡查 → ledger_service_patrol
196
+    Excel R2: 时间(0) 区域(1) 工作点(2) 岗位(3) 责任人(4) 问题描述(5)
197
+              问题类型(6) 整改措施(7) 班组(8) 问题层级(9)
198
+              队室服务联络人(10) 服务推送队室负责人(11) 附件(12)
199
+              整改情况(13) 佐证材料(14) 发送至相关人员(15)
200
+              本月内发生问题次数(16) 检查人员(17) 分管服务经理(18) 队室内勤(19)
201
+    """
202
+    stmts = []
203
+    for cols in iter_data_rows(ws, header_row=2):
204
+        c = pad(cols, 20)
205
+        stmts.append(
206
+            f"INSERT INTO ledger_service_patrol "
207
+            f"(record_date,location,inspected_name,problem_desc,service_type,"
208
+            f"result_handling,team_name,evidence_file,inspector_name,"
209
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
210
+            f"{to_date(c[0])},{esc(c[1])},{esc(c[4])},{esc(c[5])},{esc(c[6])},"
211
+            f"{esc(c[7])},{esc(c[8])},{esc(c[12])},{esc(c[17])},"
212
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
213
+        )
214
+    return stmts
215
+
216
+
217
+def sheet_complaint(ws):
218
+    """投诉情况 → ledger_complaint
219
+    Excel R2: 时间(0) 航班号(1) 旅客姓名(2) 班组(3) 责任人(4)
220
+              投诉情况(5) 旅客诉求(6) 类别(7) 渠道来源(8)
221
+              是否有责(9) 处理进度(10) 责任队长(11) 队室内勤(12)
222
+    """
223
+    stmts = []
224
+    for cols in iter_data_rows(ws, header_row=2):
225
+        c = pad(cols, 13)
226
+        stmts.append(
227
+            f"INSERT INTO ledger_complaint "
228
+            f"(record_date,flight_no,passenger_name,team_name,responsible_name,"
229
+            f"complaint_desc,complaint_type,result_handling,remark,"
230
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
231
+            f"{to_date(c[0])},{esc(c[1])},{esc(c[2])},{esc(c[3])},{esc(c[4])},"
232
+            f"{esc(c[5])},{esc(c[7])},{esc(c[10])},{esc(c[8])},"
233
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
234
+        )
235
+    return stmts
236
+
237
+
238
+def sheet_security_test(ws):
239
+    """安保测试记录表(部门)→ ledger_security_test
240
+    Excel R2: 开展时间(0) 测试区域(1) 测试通道(2) 测试项目(3)
241
+              被测试人员(4) 被测试岗位(5) 测试物品(6) 开展项目图片或视频(7)
242
+              是否通过(8) 层级(9) 整改措施(10) 复查地点及时间(11)
243
+              整改材料(12) 班组(13) 推送队室质控员(14) 推送质控队长(15)
244
+              队室内勤(16) 点击推送至相关人员(17) 队室负责人(18)
245
+    """
246
+    stmts = []
247
+    for cols in iter_data_rows(ws, header_row=2):
248
+        c = pad(cols, 19)
249
+        stmts.append(
250
+            f"INSERT INTO ledger_security_test "
251
+            f"(record_date,location,channel_no,test_item,tested_name,test_type,"
252
+            f"evidence_file,test_result,result_handling,team_name,"
253
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
254
+            f"{to_date(c[0])},{esc(c[1])},{esc(c[2])},{esc(c[3])},{esc(c[4])},{esc(c[6])},"
255
+            f"{esc(c[7])},{esc(c[8])},{esc(c[10])},{esc(c[13])},"
256
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
257
+        )
258
+    return stmts
259
+
260
+
261
+def sheet_channel_pass_rate(ws):
262
+    """通道过检率 → ledger_channel_pass_rate
263
+    Excel R2: 组长(0) 班组(1) 队室内勤(2) 2026年1月平均过检率(3) 2026年2月...(4) 2026年3月...(5) ...
264
+    结构是 pivot:一行=一个班组,多列=多月;展开为多行
265
+    """
266
+    headers = row_vals(ws, 2)
267
+    # 找月份列(列3起)
268
+    month_cols = []
269
+    for idx, h in enumerate(headers[3:], start=3):
270
+        if h and str(h).strip():
271
+            month_cols.append((idx, str(h).strip()))
272
+
273
+    # 提取月份年份
274
+    def parse_month(label):
275
+        m = re.search(r'(\d{4})年(\d{1,2})月', label)
276
+        if m:
277
+            return f"{m.group(1)}-{int(m.group(2)):02d}-01"
278
+        return None
279
+
280
+    stmts = []
281
+    for cols in iter_data_rows(ws, header_row=2):
282
+        c = pad(cols, len(headers) + 5)
283
+        team = esc(c[1])
284
+        for col_idx, label in month_cols:
285
+            rate = to_decimal(c[col_idx] if col_idx < len(c) else None)
286
+            if rate == 'NULL':
287
+                continue
288
+            month_date = parse_month(label)
289
+            rec_date = f"'{month_date}'" if month_date else 'NULL'
290
+            stmts.append(
291
+                f"INSERT INTO ledger_channel_pass_rate "
292
+                f"(record_date,team_name,pass_rate,remark,"
293
+                f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
294
+                f"{rec_date},{team},{rate},{esc(label)},"
295
+                f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
296
+            )
297
+    return stmts
298
+
299
+
300
+def sheet_unsafe_event(ws):
301
+    """不安全事件 → ledger_unsafe_event
302
+    Excel R2: 时间(0) 事件描述(1) 类别(2) 航班号(3) 责任人(4)
303
+              涉及班组(5) 涉及物品(6) 岗位(7) 区域(8) 通道号(9)
304
+              图像(10) 队室内勤(11)
305
+    """
306
+    stmts = []
307
+    for cols in iter_data_rows(ws, header_row=2):
308
+        c = pad(cols, 12)
309
+        stmts.append(
310
+            f"INSERT INTO ledger_unsafe_event "
311
+            f"(record_date,event_desc,event_type,responsible_name,team_name,"
312
+            f"evidence_file,remark,"
313
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
314
+            f"{to_date(c[0])},{esc(c[1])},{esc(c[2])},{esc(c[4])},{esc(c[5])},"
315
+            f"{esc(c[10])},{esc(c[8])},"
316
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
317
+        )
318
+    return stmts
319
+
320
+
321
+def sheet_seizure_stats(ws):
322
+    """2026查获违规品统计 → ledger_seizure_stats
323
+    Excel R2: 查获时间(0) 信息提取(1) 信息提取2(2) 部门/队室(3) 工作区域(4)
324
+              安检员(5) 安检资格证书等级(6) 岗位(7) 航班号(8) 目的地(9)
325
+              进/出港(10) 违规主体(11) 旅客姓名(12) 旅客性别(13) 旅客民族(14)
326
+              旅客年龄(15) 违规类别(16) 违规物品种类(17) 二级分类(18) 数量(19)
327
+              ...(可能更多列)
328
+    """
329
+    stmts = []
330
+    for cols in iter_data_rows(ws, header_row=2):
331
+        c = pad(cols, 30)
332
+        stmts.append(
333
+            f"INSERT INTO ledger_seizure_stats "
334
+            f"(record_date,team_name,inspector_name,flight_no,passenger_name,"
335
+            f"item_category,item_name,item_quantity,"
336
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
337
+            f"{to_date(c[0])},{esc(c[3])},{esc(c[5])},{esc(c[8])},{esc(c[12])},"
338
+            f"{esc(c[16])},{esc(c[17])},{to_int(c[19])},"
339
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
340
+        )
341
+    return stmts
342
+
343
+
344
+def sheet_terminal_bonus(ws):
345
+    """航站楼加分 → ledger_terminal_bonus
346
+    Excel R2: 审核日期(0) 姓名(1) 班组(2) 加分分数(3) 队室内勤(4) 总加分数(5) 队室负责人(6)
347
+    """
348
+    stmts = []
349
+    for cols in iter_data_rows(ws, header_row=2):
350
+        c = pad(cols, 7)
351
+        stmts.append(
352
+            f"INSERT INTO ledger_terminal_bonus "
353
+            f"(approve_date,person_name,team_name,add_score,remark,"
354
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
355
+            f"{to_date(c[0])},{esc(c[1])},{esc(c[2])},{to_decimal(c[3],'0')},{esc(c[4])},"
356
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
357
+        )
358
+    return stmts
359
+
360
+
361
+def sheet_exam_score(ws):
362
+    """成绩收集 → ledger_exam_score
363
+    Excel R2: 类别(0) 期数(1) 考试人员(2) 理论成绩(3) 图像成绩(4) 班组(5) 分类(6) 备注(补考分数)(7) 队室教员(8)
364
+    """
365
+    stmts = []
366
+    for cols in iter_data_rows(ws, header_row=2):
367
+        c = pad(cols, 9)
368
+        stmts.append(
369
+            f"INSERT INTO ledger_exam_score "
370
+            f"(exam_category,exam_period,person_name,score,team_name,remark,"
371
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
372
+            f"{esc(c[0])},{esc(c[1])},{esc(c[2])},{to_decimal(c[3],'NULL')},{esc(c[5])},{esc(c[7])},"
373
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
374
+        )
375
+    return stmts
376
+
377
+
378
+def sheet_reward_approval(ws):
379
+    """小额奖励审批单-安检-质控 → ledger_reward_approval
380
+    特殊:R1 就是表头(没有 title row)
381
+    Excel R1: 员工编码(0) 姓名(1) 查获(事件)时间(2) 奖励类别(3) 查获物品(4)
382
+              类别(5) 奖励事由(安全)(6) 人员类别(7) 主要事由简述(8)
383
+    数据从 R2 开始
384
+    """
385
+    stmts = []
386
+    for cols in iter_data_rows(ws, header_row=1):  # header_row=1 means data from row 2
387
+        c = pad(cols, 9)
388
+        stmts.append(
389
+            f"INSERT INTO ledger_reward_approval "
390
+            f"(approve_date,person_name,reward_type,approval_status,remark,"
391
+            f"import_batch,source_type,create_by,create_time,del_flag) VALUES ("
392
+            f"{to_date(c[2])},{esc(c[1])},{esc(c[3])},'待审批',{esc(c[8])},"
393
+            f"'{BATCH_NO}','1','{CREATE_BY}','{CREATE_TIME}','0');"
394
+        )
395
+    return stmts
396
+
397
+
398
+def sheet_reward_penalty(ws):
399
+    """部门奖惩记录表 → ledger_reward_penalty
400
+    Excel R2: 姓名(0) 班组(1) 事由(2) 扣罚金额(3) 最后更新时间(4)
401
+    """
402
+    stmts = []
403
+    for cols in iter_data_rows(ws, header_row=2):
404
+        c = pad(cols, 5)
405
+        # 扣罚金额转为负数分值(仅金额,非分值,存为负数)
406
+        amt = to_decimal(c[3], 'NULL')
407
+        score_change = f"-{amt}" if amt != 'NULL' else 'NULL'
408
+        stmts.append(
409
+            f"INSERT INTO ledger_reward_penalty "
410
+            f"(record_date,person_name,team_name,type,event_desc,score_change,"
411
+            f"create_by,create_time,del_flag) VALUES ("
412
+            f"{to_date(c[4])},{esc(c[0])},{esc(c[1])},'2',{esc(c[2])},{score_change},"
413
+            f"'{CREATE_BY}','{CREATE_TIME}','0');"
414
+        )
415
+    return stmts
416
+
417
+
418
+def sheet_leave_special(ws):
419
+    """请、休假记录表(特殊)→ ledger_leave_special
420
+    Excel R2: 姓名(0) 班组(1) 时间(起)(2) 时间(止)(3) 休假类别(4) 天数/时长(5)
421
+    """
422
+    stmts = []
423
+    for cols in iter_data_rows(ws, header_row=2):
424
+        c = pad(cols, 6)
425
+        stmts.append(
426
+            f"INSERT INTO ledger_leave_special "
427
+            f"(person_name,team_name,leave_type,start_date,end_date,days,"
428
+            f"create_by,create_time,del_flag) VALUES ("
429
+            f"{esc(c[0])},{esc(c[1])},{esc(c[4])},{to_date(c[2])},{to_date(c[3])},{to_decimal(c[5],'NULL')},"
430
+            f"'{CREATE_BY}','{CREATE_TIME}','0');"
431
+        )
432
+    return stmts
433
+
434
+
435
+def sheet_banner_letter(ws):
436
+    """锦旗及感谢信 → ledger_banner_letter
437
+    Excel R2: 时间(0) 姓名(1) 获得感谢信的具体内容(2) 类别(3) 图片和附件(4) 班组(5) 队室内勤(6)
438
+    类别: 感谢信→'2', 锦旗→'1', 其他→'2'
439
+    """
440
+    stmts = []
441
+    for cols in iter_data_rows(ws, header_row=2):
442
+        c = pad(cols, 7)
443
+        typ = '1' if str(c[3] or '').strip() in ('锦旗',) else '2'
444
+        stmts.append(
445
+            f"INSERT INTO ledger_banner_letter "
446
+            f"(record_date,person_name,content_desc,type,evidence_file,team_name,remark,"
447
+            f"create_by,create_time,del_flag) VALUES ("
448
+            f"{to_date(c[0])},{esc(c[1])},{esc(c[2])},'{typ}',{esc(c[4])},{esc(c[5])},{esc(c[6])},"
449
+            f"'{CREATE_BY}','{CREATE_TIME}','0');"
450
+        )
451
+    return stmts
452
+
453
+
454
+# ──────────────────────────────────────────────
455
+# sheet name → handler mapping
456
+# ──────────────────────────────────────────────
457
+SHEET_MAP = {
458
+    '部门监察问题记录表':          ('ledger_supervision_problem',    sheet_supervision_problem),
459
+    '队室三级质控巡查记录表':       ('ledger_patrol_inspection',      sheet_patrol_inspection),
460
+    '部门实时质控拦截情况记录表':   ('ledger_realtime_interception',  sheet_realtime_interception),
461
+    '服务巡查':                    ('ledger_service_patrol',         sheet_service_patrol),
462
+    '投诉情况':                    ('ledger_complaint',              sheet_complaint),
463
+    '安保测试记录表(部门)':       ('ledger_security_test',          sheet_security_test),
464
+    '通道过检率':                  ('ledger_channel_pass_rate',      sheet_channel_pass_rate),
465
+    '不安全事件':                  ('ledger_unsafe_event',           sheet_unsafe_event),
466
+    '2026查获违规品统计':          ('ledger_seizure_stats',          sheet_seizure_stats),
467
+    '航站楼加分':                  ('ledger_terminal_bonus',         sheet_terminal_bonus),
468
+    '成绩收集':                    ('ledger_exam_score',             sheet_exam_score),
469
+    '小额奖励审批单-安检-质控':    ('ledger_reward_approval',        sheet_reward_approval),
470
+    '部门奖惩记录表':              ('ledger_reward_penalty',         sheet_reward_penalty),
471
+    '请、休假记录表(特殊)':      ('ledger_leave_special',          sheet_leave_special),
472
+    '锦旗及感谢信':               ('ledger_banner_letter',          sheet_banner_letter),
473
+}
474
+
475
+SKIP_SHEETS = {
476
+    '旅检三部人员信息表', '日常培训记录', '组长履职情况记录表',
477
+    '健康锐兵(2026_3)', '宿舍消防安全专项自查表(部门)', '培训台账问题通报',
478
+}
479
+
480
+# ──────────────────────────────────────────────
481
+# 主程序
482
+# ──────────────────────────────────────────────
483
+def main():
484
+    excel_path = find_excel()
485
+    print(f'读取文件: {excel_path}')
486
+
487
+    wb = openpyxl.load_workbook(excel_path, read_only=True, data_only=True)
488
+
489
+    lines = [
490
+        '-- =============================================',
491
+        f'-- 台账数据导入 SQL  批次: {BATCH_NO}',
492
+        f'-- 生成时间: {CREATE_TIME}',
493
+        f'-- 来源文件: {os.path.basename(excel_path)}',
494
+        '-- =============================================',
495
+        'SET NAMES utf8mb4;',
496
+        'SET FOREIGN_KEY_CHECKS=0;',
497
+        '',
498
+    ]
499
+
500
+    total = 0
501
+    for sheet_name in wb.sheetnames:
502
+        if sheet_name in SKIP_SHEETS:
503
+            print(f'  [跳过] {sheet_name}')
504
+            continue
505
+
506
+        if sheet_name not in SHEET_MAP:
507
+            print(f'  [未映射] {sheet_name}')
508
+            continue
509
+
510
+        table_name, handler = SHEET_MAP[sheet_name]
511
+        ws = wb[sheet_name]
512
+        try:
513
+            stmts = handler(ws)
514
+        except Exception as e:
515
+            print(f'  [ERROR] {sheet_name}: {e}')
516
+            import traceback; traceback.print_exc()
517
+            stmts = []
518
+
519
+        lines.append(f'-- ── {sheet_name} → {table_name} ({len(stmts)} 行) ──')
520
+        lines.extend(stmts)
521
+        lines.append('')
522
+        total += len(stmts)
523
+        print(f'  [OK] {sheet_name} → {table_name}: {len(stmts)} 条')
524
+
525
+    lines.append('SET FOREIGN_KEY_CHECKS=1;')
526
+    lines.append(f'-- 共生成 {total} 条 INSERT 语句')
527
+
528
+    wb.close()
529
+
530
+    with open(OUTPUT_SQL, 'w', encoding='utf-8') as f:
531
+        f.write('\n'.join(lines))
532
+
533
+    print(f'\n✅ 完成!共 {total} 条,已写入:\n   {OUTPUT_SQL}')
534
+    print(f'\n执行方式(在 MySQL 客户端中):')
535
+    print(f'  USE chongqing;  -- 替换为你的库名')
536
+    print(f'  SOURCE {OUTPUT_SQL.replace(chr(92), "/")};')
537
+
538
+
539
+if __name__ == '__main__':
540
+    main()

+ 24 - 0
sql/fix_sync_indicator_ids.sql

@@ -0,0 +1,24 @@
1
+-- ============================================================
2
+-- 同步数据修复脚本(2026-04-25)
3
+-- 背景:原同步服务使用 indicator_init.sql 的旧指标ID(1001+),
4
+--       与 score.sql 的新指标ID(11, 12, ...)不匹配,
5
+--       导致所有已同步 score_event 记录的 indicator_id 无效。
6
+--       同时,reward_penalty / leave_special / banner_letter
7
+--       按最新配分表属于"暂无台账,不考虑导入规则",已从同步逻辑移除。
8
+-- ============================================================
9
+
10
+-- 步骤1:清空所有台账同步写入的 score_event(source_type='2')
11
+-- 手动录入(source_type='1')不受影响
12
+DELETE FROM score_event WHERE source_type = '2';
13
+
14
+-- 步骤2:确认当前 score_indicator 使用的是 score.sql 中的新ID(11, 12...)
15
+-- 如果 MAX(id) 约 1500,说明是新版;如果约 1343,说明还是旧版,需重建指标
16
+-- SELECT MAX(id), COUNT(*) FROM score_indicator WHERE del_flag='0';
17
+
18
+-- 步骤3(如果指标仍是旧版 1001+ ID,需要重建):
19
+-- DELETE FROM score_indicator;
20
+-- 然后重新执行 score.sql 中三段 INSERT INTO score_indicator(ID 11~1522)
21
+
22
+-- 步骤4:部署新版后端代码后,在页面点击"同步台账"重新同步
23
+-- 或调用接口:POST /ledger/sync/all
24
+-- ============================================================

+ 267 - 0
sql/indicator_init.sql

@@ -0,0 +1,267 @@
1
+-- =============================================
2
+-- 评分指标初始化脚本(128条,共6个维度)
3
+-- 依赖:score.sql 已执行(score_dimension 已有6条数据,建表已完成)
4
+-- 执行前确认:SELECT id, name FROM score_dimension ORDER BY sort_order;
5
+--
6
+-- ID 规则(与 score.sql 保持一致):
7
+--   L2: 11~62   L3: 111~552   L4: 1211~1522
8
+--
9
+-- 暂无台账说明(来源:配分表分析.csv):
10
+--   以下维度下的所有指标暂时标注,不参与同步计分规则:
11
+--   · 作风践行能力(全部)
12
+--   · 群团协作能力(全部)
13
+--   · 身心调节能力(全部)
14
+--   · 安全防控/服务响应下的"风险隐患"子项
15
+--   · 业务实操除"考试成绩"外的所有子项
16
+-- =============================================
17
+
18
+-- 使用变量存储维度ID,避免与AUTO_INCREMENT值绑定
19
+SET @dim1 = (SELECT id FROM score_dimension WHERE name = '安全防控能力' LIMIT 1);
20
+SET @dim2 = (SELECT id FROM score_dimension WHERE name = '服务响应能力' LIMIT 1);
21
+SET @dim3 = (SELECT id FROM score_dimension WHERE name = '业务实操能力' LIMIT 1);
22
+SET @dim4 = (SELECT id FROM score_dimension WHERE name = '作风践行能力' LIMIT 1);
23
+SET @dim5 = (SELECT id FROM score_dimension WHERE name = '群团协作能力' LIMIT 1);
24
+SET @dim6 = (SELECT id FROM score_dimension WHERE name = '身心调节能力' LIMIT 1);
25
+
26
+-- ============================================================
27
+-- 一、安全防控能力 L2(6条)
28
+-- ============================================================
29
+INSERT INTO `score_indicator`
30
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`cascade_rule`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
31
+VALUES
32
+(11,@dim1,0,2,'员工规范化操作',  NULL,0,'返航/二次清舱-10、航班延误-8、旅客滞留-5、产生舆论-2、通报批评(站)-2、(公司)-3、(局方)-10、外部通报扭转-1',1,'0','【部门监察问题记录表】','admin',NOW()),
33
+(12,@dim1,0,2,'后台实时质控拦截',NULL,0,NULL,2,'0','【部门实时质控拦截情况记录表】','admin',NOW()),
34
+(13,@dim1,0,2,'不安全事件',      NULL,0,NULL,3,'0','【不安全事件】','admin',NOW()),
35
+(14,@dim1,0,2,'安保测试未通过',  NULL,0,NULL,4,'0','【安保测试记录表(部门)】是否通过=未通过','admin',NOW()),
36
+(15,@dim1,0,2,'典型案例查获',    NULL,0,'通报表扬(站)+1、(公司)+3、(局方)+5、讲评会点名表扬+0.5',5,'0','【小额奖励审批单-安检-质控】','admin',NOW()),
37
+(16,@dim1,0,2,'风险隐患',        NULL,0,NULL,6,'0','暂无台账,不考虑导入规则','admin',NOW());
38
+
39
+-- 安全防控能力 L3(13条)
40
+INSERT INTO `score_indicator`
41
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
42
+VALUES
43
+-- 员工规范化操作 → 11
44
+(111,@dim1,11,3,'站层级质控',  '2',-1.00,1,'0','【部门监察问题记录表】问题层级=站层级','admin',NOW()),
45
+(112,@dim1,11,3,'部门层级质控','2',-0.80,2,'0','【部门监察问题记录表】问题层级=部门层级','admin',NOW()),
46
+(113,@dim1,11,3,'上级领导巡查','2',-1.20,3,'0','【部门监察问题记录表】问题层级=上级领导巡查','admin',NOW()),
47
+-- 后台实时质控拦截 → 12
48
+(121,@dim1,12,3,'站层级',  '2',-1.00,1,'0','【部门实时质控拦截记录表】问题层级=站层级','admin',NOW()),
49
+(122,@dim1,12,3,'部门层级','2',-0.80,2,'0','【部门实时质控拦截记录表】问题层级=部门层级','admin',NOW()),
50
+-- 不安全事件 → 13
51
+(131,@dim1,13,3,'一类','2',-10.00,1,'0','未经安检旅客进隔离区;爆炸装置;枪支弹药','admin',NOW()),
52
+(132,@dim1,13,3,'二类','2', -8.00,2,'0','枪支零部件;管制器具(管制刀具/警用催泪瓦斯/警用电击器等)','admin',NOW()),
53
+(133,@dim1,13,3,'三类','2', -5.00,3,'0','易燃液/固/气体;遇水自燃物;毒害品;腐蚀品;放射性物品','admin',NOW()),
54
+(134,@dim1,13,3,'四类','2', -3.00,4,'0','漏检错检乘机证件;弹壳;烟花爆竹(D类除外)','admin',NOW()),
55
+(135,@dim1,13,3,'五类','2', -2.00,5,'0','禁带锐器/钝器;D类烟花;火种;禁限锂电池等','admin',NOW()),
56
+-- 安保测试未通过 → 14(三层均为-0.5)
57
+(141,@dim1,14,3,'局方层级','2',-0.50,1,'0','is否通过=未通过,层级=局方层级','admin',NOW()),
58
+(142,@dim1,14,3,'公司层级','2',-0.50,2,'0','是否通过=未通过,层级=公司层级','admin',NOW()),
59
+(143,@dim1,14,3,'站层级',  '2',-0.50,3,'0','是否通过=未通过,层级=站层级','admin',NOW()),
60
+-- 典型案例查获 → 15
61
+(151,@dim1,15,3,'一类',    '1', 1.00,1,'0','【小额奖励审批单-安检-质控】查获物品类别=一类','admin',NOW()),
62
+(152,@dim1,15,3,'二类',    '1', 0.50,2,'0','【小额奖励审批单-安检-质控】查获物品类别=二类','admin',NOW()),
63
+(153,@dim1,15,3,'特殊查获','1', 0.15,3,'0','后台实时质控人员拦截数','admin',NOW()),
64
+-- 风险隐患(安全)→ 16(暂无台账)
65
+(161,@dim1,16,3,'发现安全隐患及时上报并进行处置',        '1',1.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
66
+(162,@dim1,16,3,'通过自愿报告系统上报安全建议并进行采纳','1',1.00,2,'0','暂无台账;经质控部研究采纳并奖励','admin',NOW()),
67
+(163,@dim1,16,3,'在部门层级内部上报安全建议',            '1',0.50,3,'0','暂无台账;经部门研究采纳并实施','admin',NOW());
68
+
69
+-- ============================================================
70
+-- 二、服务响应能力 L2(4条)
71
+-- ============================================================
72
+INSERT INTO `score_indicator`
73
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`cascade_rule`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
74
+VALUES
75
+-- 旅客服务投诉:叶子节点(无L3,责任人不为空时计分)
76
+(21,@dim2,0,2,'旅客服务投诉','2',-2.00,'航班延误-10、旅客滞留-5、人员受伤-5、金额赔付-2、产生舆论-2、通报批评(站)-2、(公司)-3、(局方)-10、外部通报扭转-1',1,'0','【投诉情况】责任人不为空时计分;不对所属班组/队室计分','admin',NOW()),
77
+(22,@dim2,0,2,'服务监察',    NULL, 0.00,NULL,2,'0','【服务巡查】','admin',NOW()),
78
+(23,@dim2,0,2,'典型服务案例',NULL, 0.00,NULL,3,'0','【航站楼加分】','admin',NOW()),
79
+(24,@dim2,0,2,'风险隐患',    NULL, 0.00,NULL,4,'0','暂无台账,不考虑导入规则','admin',NOW());
80
+
81
+-- 服务响应能力 L3(8条)
82
+INSERT INTO `score_indicator`
83
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
84
+VALUES
85
+-- 服务监察 → 22
86
+(221,@dim2,22,3,'航站楼及以上层级','2',-1.20,1,'0','【服务巡查】问题层级=航站楼及以上','admin',NOW()),
87
+(222,@dim2,22,3,'站层级',          '2',-1.00,2,'0','【服务巡查】问题层级=站层级','admin',NOW()),
88
+(223,@dim2,22,3,'部门层级',        '2',-0.80,3,'0','【服务巡查】问题层级=部门层级','admin',NOW()),
89
+-- 典型服务案例 → 23(分值取台账 add_score 字段)
90
+(231,@dim2,23,3,'航站楼加分',     '1',0.00,1,'0','【航站楼加分】分值以台账add_score字段为准;需上报航站楼确认','admin',NOW()),
91
+(232,@dim2,23,3,'讲评会点名表扬', '1',0.50,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
92
+-- 风险隐患(服务)→ 24(暂无台账)
93
+(241,@dim2,24,3,'发现服务隐患及时上报并进行处置',          '1',1.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
94
+(242,@dim2,24,3,'通过自愿报告系统上报服务建议并进行采纳',  '1',1.00,2,'0','暂无台账;经质控部研究采纳并奖励','admin',NOW()),
95
+(243,@dim2,24,3,'在部门层级内部上报服务建议经部门采纳',    '1',0.50,3,'0','暂无台账;经部门研究采纳并实施','admin',NOW());
96
+
97
+-- ============================================================
98
+-- 三、业务实操能力 L2(6条)
99
+-- ============================================================
100
+INSERT INTO `score_indicator`
101
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`cascade_rule`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
102
+VALUES
103
+(31,@dim3,0,2,'站层级及以上考核',          NULL, 0.00,'补考阶梯递增:第1次-2,第2次-4,第3次-6,后续每次再+2',1,'0','暂无台账,不考虑导入规则','admin',NOW()),
104
+(32,@dim3,0,2,'队室及部门层级考核未通过',  '2', -1.00,NULL,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
105
+(33,@dim3,0,2,'技能保持未完成培训',        '2', -0.20,NULL,3,'0','暂无台账;每超1天扣0.2分','admin',NOW()),
106
+(34,@dim3,0,2,'岗位技能与实操能力不匹配',  NULL, 0.00,NULL,4,'0','暂无台账,不考虑导入规则','admin',NOW()),
107
+(35,@dim3,0,2,'考试成绩',                  NULL, 0.00,NULL,5,'0','【考试成绩】','admin',NOW()),
108
+(36,@dim3,0,2,'导师带徒',                  NULL, 0.00,NULL,6,'0','暂无台账,不考虑导入规则','admin',NOW());
109
+
110
+-- 业务实操能力 L3(10条)
111
+INSERT INTO `score_indicator`
112
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`cascade_rule`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
113
+VALUES
114
+-- 站层级及以上考核 → 31(暂无台账)
115
+(311,@dim3,31,3,'季度理论考试补考','2',-2.00,'阶梯递增:第1次-2,第2次-4,第3次-6,每次递增2分',1,'0','暂无台账,不考虑导入规则','admin',NOW()),
116
+-- 岗位技能与实操能力不匹配 → 34(暂无台账)
117
+(341,@dim3,34,3,'拿取中级证书满3年未放开机人员', '2',-0.50,NULL,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
118
+(342,@dim3,34,3,'返岗员工未在规定时间内放单号位','2',-0.50,NULL,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
119
+(343,@dim3,34,3,'新员工未在规定时间内放单号位',  '2',-0.50,NULL,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
120
+-- 考试成绩 → 35(有数据,同步计分)
121
+(351,@dim3,35,3,'季度理论考试',      '1',5.00,NULL,1,'0','【考试成绩】理论成绩≥98分加5分','admin',NOW()),
122
+(352,@dim3,35,3,'季度开机员能力考核','1',5.00,NULL,2,'0','【考试成绩】图像成绩≥98分加5分','admin',NOW()),
123
+-- 导师带徒 → 36(暂无台账)
124
+(361,@dim3,36,3,'初级核心岗位(验证、人身)未通过','2',-0.20,NULL,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
125
+(362,@dim3,36,3,'初级核心岗位(验证、人身)通过',  '1', 1.00,NULL,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
126
+(363,@dim3,36,3,'中级开机岗位未通过',             '2',-0.20,NULL,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
127
+(364,@dim3,36,3,'中级开机岗位通过',               '1', 5.00,NULL,4,'0','暂无台账,不考虑导入规则','admin',NOW());
128
+
129
+-- ============================================================
130
+-- 四、作风践行能力 L2(7条,全部暂无台账)
131
+-- ============================================================
132
+INSERT INTO `score_indicator`
133
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
134
+VALUES
135
+(41,@dim4,0,2,'诚信记录',    '2',-10.00,1,'0','暂无台账,不考虑导入规则;徇私舞弊/考试作弊被通报','admin',NOW()),
136
+(42,@dim4,0,2,'信息报送真实度','2',-2.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
137
+(43,@dim4,0,2,'出勤情况',    NULL, 0.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
138
+(44,@dim4,0,2,'形象维护',    NULL, 0.00,4,'0','暂无台账,不考虑导入规则','admin',NOW()),
139
+(45,@dim4,0,2,'勤务执行力',  '2', -2.00,5,'0','暂无台账;接到通道开放通知10分钟内未到达','admin',NOW()),
140
+(46,@dim4,0,2,'全勤',        '1',  5.00,6,'0','暂无台账;一季度内无迟到/早退/请假(含病假)','admin',NOW()),
141
+(47,@dim4,0,2,'志愿者服务',  '1',  0.50,7,'0','暂无台账;参与安检站/航站楼等志愿者服务','admin',NOW());
142
+
143
+-- 作风践行能力 L3(11条,全部暂无台账)
144
+INSERT INTO `score_indicator`
145
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
146
+VALUES
147
+-- 出勤情况 → 43
148
+(431,@dim4,43,3,'迟到、早退','2', -1.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
149
+(432,@dim4,43,3,'中途离岗',  '2', -5.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
150
+(433,@dim4,43,3,'旷工',      '2',-10.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
151
+-- 形象维护 → 44
152
+(441,@dim4,44,3,'私拿旅客物品','2', -2.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
153
+(442,@dim4,44,3,'占用公司财务','2', -5.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
154
+(443,@dim4,44,3,'着制服外出',  '2', -1.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
155
+(444,@dim4,44,3,'纪委监察',    '2', -2.00,4,'0','暂无台账,不考虑导入规则','admin',NOW()),
156
+(445,@dim4,44,3,'打架斗殴',    '2',-10.00,5,'0','暂无台账,不考虑导入规则','admin',NOW()),
157
+(446,@dim4,44,3,'站内审批',    '1',  2.00,6,'0','暂无台账;旅客无理取闹正确处置,经部门申报站内审批认定','admin',NOW()),
158
+(447,@dim4,44,3,'部门审批',    '1',  0.50,7,'0','暂无台账;经部门研究讨论认定','admin',NOW());
159
+
160
+-- ============================================================
161
+-- 五、群团协作能力 L2(5条,全部暂无台账)
162
+-- ============================================================
163
+INSERT INTO `score_indicator`
164
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
165
+VALUES
166
+(51,@dim5,0,2,'活动参与度',  NULL,0.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
167
+(52,@dim5,0,2,'团队荣誉',    NULL,0.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
168
+(53,@dim5,0,2,'个人荣誉',    NULL,0.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
169
+(54,@dim5,0,2,'新闻',        NULL,0.00,4,'0','暂无台账,不考虑导入规则','admin',NOW()),
170
+(55,@dim5,0,2,'自主活动筹备',NULL,0.00,5,'0','暂无台账,不考虑导入规则','admin',NOW());
171
+
172
+-- 群团协作能力 L3(14条,全部暂无台账)
173
+INSERT INTO `score_indicator`
174
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
175
+VALUES
176
+-- 活动参与度 → 51
177
+(511,@dim5,51,3,'部门层级',    '1',0.50,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
178
+(512,@dim5,51,3,'站层级',      '1',1.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
179
+(513,@dim5,51,3,'站层级及以上','1',2.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
180
+-- 团队荣誉 → 52(L4展开在下方)
181
+(521,@dim5,52,3,'部门层级',    NULL,0.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
182
+(522,@dim5,52,3,'站层级',      NULL,0.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
183
+(523,@dim5,52,3,'站层级及以上',NULL,0.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
184
+-- 个人荣誉 → 53(L4展开在下方)
185
+(531,@dim5,53,3,'部门层级',    NULL,0.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
186
+(532,@dim5,53,3,'站层级',      NULL,0.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
187
+(533,@dim5,53,3,'站层级及以上',NULL,0.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
188
+-- 新闻 → 54(L4展开在下方)
189
+(541,@dim5,54,3,'站层级(CQ安小检)',        NULL,0.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
190
+(542,@dim5,54,3,'公司层级(重庆飞/OA集团)', NULL,0.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
191
+(543,@dim5,54,3,'局方层级',                  NULL,0.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
192
+-- 自主活动筹备 → 55(L4展开在下方)
193
+(551,@dim5,55,3,'部门层级',NULL,0.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
194
+(552,@dim5,55,3,'站层级',  NULL,0.00,2,'0','暂无台账,不考虑导入规则','admin',NOW());
195
+
196
+-- 群团协作能力 L4(37条,全部暂无台账)
197
+INSERT INTO `score_indicator`
198
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
199
+VALUES
200
+-- 团队荣誉 - 部门层级 → 521
201
+(1211,@dim5,521,4,'一等奖','1', 3.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
202
+(1212,@dim5,521,4,'二等奖','1', 2.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
203
+(1213,@dim5,521,4,'三等奖','1', 1.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
204
+-- 团队荣誉 - 站层级 → 522
205
+(1221,@dim5,522,4,'一等奖','1', 5.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
206
+(1222,@dim5,522,4,'二等奖','1', 4.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
207
+(1223,@dim5,522,4,'三等奖','1', 3.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
208
+-- 团队荣誉 - 站层级及以上 → 523
209
+(1231,@dim5,523,4,'一等奖','1', 7.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
210
+(1232,@dim5,523,4,'二等奖','1', 6.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
211
+(1233,@dim5,523,4,'三等奖','1', 5.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
212
+-- 个人荣誉 - 部门层级 → 531
213
+(1311,@dim5,531,4,'一等奖','1', 4.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
214
+(1312,@dim5,531,4,'二等奖','1', 3.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
215
+(1313,@dim5,531,4,'三等奖','1', 2.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
216
+-- 个人荣誉 - 站层级 → 532
217
+(1321,@dim5,532,4,'一等奖','1', 6.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
218
+(1322,@dim5,532,4,'二等奖','1', 5.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
219
+(1323,@dim5,532,4,'三等奖','1', 4.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
220
+-- 个人荣誉 - 站层级及以上 → 533
221
+(1331,@dim5,533,4,'一等奖','1', 8.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
222
+(1332,@dim5,533,4,'二等奖','1', 7.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
223
+(1333,@dim5,533,4,'三等奖','1', 6.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
224
+-- 新闻 - 站层级(CQ安小检)→ 541
225
+(1411,@dim5,541,4,'新闻发表',      '1',1.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
226
+(1412,@dim5,541,4,'诗歌绘画微镜头','1',0.50,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
227
+(1413,@dim5,541,4,'视频脚本编辑',  '1',1.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
228
+(1414,@dim5,541,4,'视频摄影制作',  '1',1.00,4,'0','暂无台账,不考虑导入规则','admin',NOW()),
229
+(1415,@dim5,541,4,'视频员工参演',  '1',0.50,5,'0','暂无台账,不考虑导入规则','admin',NOW()),
230
+-- 新闻 - 公司层级 → 542
231
+(1421,@dim5,542,4,'新闻发表',      '1',1.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
232
+(1422,@dim5,542,4,'诗歌绘画微镜头','1',1.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
233
+(1423,@dim5,542,4,'视频脚本编辑',  '1',1.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
234
+(1424,@dim5,542,4,'视频幕后制作',  '1',1.00,4,'0','暂无台账,不考虑导入规则','admin',NOW()),
235
+(1425,@dim5,542,4,'视频员工参演',  '1',1.00,5,'0','暂无台账,不考虑导入规则','admin',NOW()),
236
+-- 新闻 - 局方层级 → 543
237
+(1431,@dim5,543,4,'新闻发表',      '1',1.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
238
+(1432,@dim5,543,4,'诗歌绘画微镜头','1',1.00,2,'0','暂无台账,不考虑导入规则','admin',NOW()),
239
+(1433,@dim5,543,4,'视频脚本编辑',  '1',1.00,3,'0','暂无台账,不考虑导入规则','admin',NOW()),
240
+(1434,@dim5,543,4,'视频幕后制作',  '1',1.00,4,'0','暂无台账,不考虑导入规则','admin',NOW()),
241
+(1435,@dim5,543,4,'视频员工参演',  '1',1.00,5,'0','暂无台账,不考虑导入规则','admin',NOW()),
242
+-- 自主活动筹备 - 部门层级 → 551
243
+(1511,@dim5,551,4,'活动策划筹备','1',3.00,1,'0','暂无台账;负责策划方向/后勤采购/设备调试/现场协调','admin',NOW()),
244
+(1512,@dim5,551,4,'活动辅助推进','1',1.00,2,'0','暂无台账;主持/摄影/幕后人员','admin',NOW()),
245
+-- 自主活动筹备 - 站层级 → 552
246
+(1521,@dim5,552,4,'活动策划筹备','1',6.00,1,'0','暂无台账,不考虑导入规则','admin',NOW()),
247
+(1522,@dim5,552,4,'活动辅助推进','1',3.00,2,'0','暂无台账,不考虑导入规则','admin',NOW());
248
+
249
+-- ============================================================
250
+-- 六、身心调节能力 L2(2条,全部暂无台账)
251
+-- ============================================================
252
+INSERT INTO `score_indicator`
253
+  (`id`,`dimension_id`,`parent_id`,`level`,`name`,`type`,`score_value`,`sort_order`,`status`,`remark`,`create_by`,`create_time`)
254
+VALUES
255
+(61,@dim6,0,2,'病假',    NULL,0.00,1,'0','暂无台账,不考虑导入规则;具体规则待定','admin',NOW()),
256
+(62,@dim6,0,2,'活动打卡',NULL,0.00,2,'0','暂无台账,不考虑导入规则;休息日运动打卡+0.5','admin',NOW());
257
+
258
+-- ============================================================
259
+-- 验证
260
+-- ============================================================
261
+-- SELECT d.name AS 维度, COUNT(i.id) AS 指标总数
262
+-- FROM score_dimension d
263
+-- LEFT JOIN score_indicator i ON i.dimension_id = d.id AND i.del_flag='0'
264
+-- GROUP BY d.id, d.name ORDER BY d.sort_order;
265
+-- 预期总数:128条
266
+--
267
+-- SELECT COUNT(*) AS 总指标数 FROM score_indicator WHERE del_flag='0';

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 4214 - 0
sql/ledger_data_import.sql


+ 5 - 0
sql/score.sql

@@ -121,12 +121,15 @@ INSERT INTO `score_dimension` (`name`, `weight`, `base_score`, `sort_order`, `st
121 121
 
122 122
 -- ═══════════════════════════════════════════
123 123
 -- 3. 指标体系(128条)
124
+--    ⚠️  推荐使用 indicator_init.sql 代替此段:
125
+--       indicator_init.sql 使用 @dim 变量动态查询维度ID,
126
+--       不依赖 AUTO_INCREMENT 顺序,更安全可靠。
127
+--
128
+--    此处 dimension_id 假设按插入顺序为 1~6:
124 129
 --      1=安全防控  2=服务响应  3=业务实操
125 130
 --      4=作风践行  5=群团协作  6=身心调节
126 131
 --    type: '1'=加分  '2'=扣分  NULL=分类节点
127 132
 --    score_value: 正=加分 负=扣分 0=分类节点/待定
128 133
 -- ═══════════════════════════════════════════
129 134
 
130 135
 -- ── Level 2(二级指标,parent_id=0,共30条)──────────────────

+ 80 - 0
sql/sys_dept.sql

@@ -0,0 +1,80 @@
1
+/*
2
+ Navicat Premium Data Transfer
3
+
4
+ Source Server         : localhost_3306
5
+ Source Server Type    : MySQL
6
+ Source Server Version : 80045
7
+ Source Host           : localhost:3306
8
+ Source Schema         : chongqing-test
9
+
10
+ Target Server Type    : MySQL
11
+ Target Server Version : 80045
12
+ File Encoding         : 65001
13
+
14
+ Date: 24/04/2026 15:37:53
15
+*/
16
+
17
+SET NAMES utf8mb4;
18
+SET FOREIGN_KEY_CHECKS = 0;
19
+
20
+-- ----------------------------
21
+-- Table structure for sys_dept
22
+-- ----------------------------
23
+DROP TABLE IF EXISTS `sys_dept`;
24
+CREATE TABLE `sys_dept`  (
25
+  `dept_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '部门id',
26
+  `parent_id` bigint(0) NULL DEFAULT 0 COMMENT '父部门id',
27
+  `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表',
28
+  `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称',
29
+  `order_num` int(0) NULL DEFAULT 0 COMMENT '显示顺序',
30
+  `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人',
31
+  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',
32
+  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
33
+  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)',
34
+  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
35
+  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
36
+  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
37
+  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者',
38
+  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
39
+  `dept_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'STATION' COMMENT '部门类型',
40
+  `dept_type_desc` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '机构站' COMMENT '部门类型名称',
41
+  PRIMARY KEY (`dept_id`) USING BTREE,
42
+  INDEX `idx_sys_dept_parent_id`(`parent_id`) USING BTREE
43
+) ENGINE = InnoDB AUTO_INCREMENT = 131 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;
44
+
45
+-- ----------------------------
46
+-- Records of sys_dept
47
+-- ----------------------------
48
+INSERT INTO `sys_dept` VALUES (100, 0, '0', '安检站', 1, '', '', '', '0', '0', 'admin', '2025-07-07 16:07:22', 'admin', '2025-11-28 16:00:40', 'STATION', '机构站');
49
+INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '旅检一部', 0, NULL, NULL, NULL, '0', '0', 'admin', '2026-04-22 12:34:06', '', NULL, 'BRIGADE', '机构站');
50
+INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '旅检二部', 1, NULL, NULL, NULL, '0', '0', 'admin', '2026-04-22 12:34:31', 'admin', '2026-04-22 12:34:39', 'BRIGADE', '机构站');
51
+INSERT INTO `sys_dept` VALUES (103, 100, '0,100', '旅检三部', 2, NULL, NULL, NULL, '0', '0', 'admin', '2026-04-22 12:34:55', '', NULL, 'BRIGADE', '机构站');
52
+INSERT INTO `sys_dept` VALUES (104, 103, '0,100,103', '安平班组', 0, '项锐', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:35:56', 'admin', '2026-04-22 12:49:15', 'MANAGER', '机构站');
53
+INSERT INTO `sys_dept` VALUES (105, 103, '0,100,103', '屹动班组', 1, '李健', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:36:57', 'admin', '2026-04-22 12:48:49', 'MANAGER', '机构站');
54
+INSERT INTO `sys_dept` VALUES (106, 103, '0,100,103', '芮盾班组', 2, '刘怡春', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:37:25', 'admin', '2026-04-22 12:37:33', 'MANAGER', '机构站');
55
+INSERT INTO `sys_dept` VALUES (107, 103, '0,100,103', '拓新班组', 3, '杨小瑜', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:38:01', '', NULL, 'MANAGER', '机构站');
56
+INSERT INTO `sys_dept` VALUES (108, 103, '0,100,103', '安行班组', 4, '赵金鹏', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:41:57', 'admin', '2026-04-22 12:54:23', 'MANAGER', '机构站');
57
+INSERT INTO `sys_dept` VALUES (109, 103, '0,100,103', '木兰班组', 5, '陈殷红', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:42:27', '', NULL, 'MANAGER', '机构站');
58
+INSERT INTO `sys_dept` VALUES (110, 104, '0,100,103,104', '陈行小组', 0, '陈行', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:43:12', '', NULL, 'TEAMS', '机构站');
59
+INSERT INTO `sys_dept` VALUES (111, 104, '0,100,103,104', '杨龙小组', 1, '杨龙', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:43:32', '', NULL, 'TEAMS', '机构站');
60
+INSERT INTO `sys_dept` VALUES (112, 104, '0,100,103,104', '王岚玉小组', 2, '王岚玉', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:49:55', '', NULL, 'TEAMS', '机构站');
61
+INSERT INTO `sys_dept` VALUES (113, 105, '0,100,103,105', '凌静小组', 0, '凌静', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:50:34', '', NULL, 'TEAMS', '机构站');
62
+INSERT INTO `sys_dept` VALUES (114, 105, '0,100,103,105', '张长圣小组', 1, '张长圣', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:50:54', 'admin', '2026-04-22 12:51:22', 'TEAMS', '机构站');
63
+INSERT INTO `sys_dept` VALUES (115, 105, '0,100,103,105', '代航小组', 2, '代航', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:51:15', '', NULL, 'TEAMS', '机构站');
64
+INSERT INTO `sys_dept` VALUES (116, 106, '0,100,103,106', '卢兴旭小组', 0, '卢兴旭', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:51:41', '', NULL, 'TEAMS', '机构站');
65
+INSERT INTO `sys_dept` VALUES (117, 106, '0,100,103,106', '张露小组', 1, '张露', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:52:06', '', NULL, 'TEAMS', '机构站');
66
+INSERT INTO `sys_dept` VALUES (118, 106, '0,100,103,106', '刘婷婷小组', 2, '刘婷婷', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:52:25', '', NULL, 'TEAMS', '机构站');
67
+INSERT INTO `sys_dept` VALUES (119, 107, '0,100,103,107', '贾剑雄小组', 0, '贾剑雄', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:52:53', '', NULL, 'TEAMS', '机构站');
68
+INSERT INTO `sys_dept` VALUES (120, 107, '0,100,103,107', '聂倩小组', 1, '聂倩', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:53:08', 'admin', '2026-04-22 12:53:38', 'TEAMS', '机构站');
69
+INSERT INTO `sys_dept` VALUES (121, 107, '0,100,103,107', '肖灿小组', 2, '肖灿', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:53:32', '', NULL, 'TEAMS', '机构站');
70
+INSERT INTO `sys_dept` VALUES (122, 108, '0,100,103,108', '周游波小组', 0, '周游波', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:54:12', 'admin', '2026-04-22 12:54:36', 'TEAMS', '机构站');
71
+INSERT INTO `sys_dept` VALUES (123, 108, '0,100,103,108', '刘欢欢小组', 1, '刘欢欢', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:54:54', '', NULL, 'TEAMS', '机构站');
72
+INSERT INTO `sys_dept` VALUES (124, 108, '0,100,103,108', '史飞小组', 2, '史飞', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:55:17', '', NULL, 'TEAMS', '机构站');
73
+INSERT INTO `sys_dept` VALUES (125, 109, '0,100,103,109', '赵俊秀小组', 0, '赵俊秀', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:55:39', '', NULL, 'TEAMS', '机构站');
74
+INSERT INTO `sys_dept` VALUES (126, 109, '0,100,103,109', '李学伟小组', 1, '李学伟', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:55:59', 'admin', '2026-04-22 15:26:01', 'TEAMS', '机构站');
75
+INSERT INTO `sys_dept` VALUES (127, 109, '0,100,103,109', '罗园园小组', 2, '罗园园', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:56:25', '', NULL, 'TEAMS', '机构站');
76
+INSERT INTO `sys_dept` VALUES (128, 109, '0,100,103,109', '尹一澄小组', 3, '尹一澄', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:56:43', '', NULL, 'TEAMS', '机构站');
77
+INSERT INTO `sys_dept` VALUES (129, 109, '0,100,103,109', '李川小组', 4, '李川', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:57:00', '', NULL, 'TEAMS', '机构站');
78
+INSERT INTO `sys_dept` VALUES (130, 109, '0,100,103,109', '罗方程雪小组', 5, '罗方程雪', NULL, NULL, '0', '0', 'admin', '2026-04-22 12:57:20', '', NULL, 'TEAMS', '机构站');
79
+
80
+SET FOREIGN_KEY_CHECKS = 1;

+ 88 - 0
sql/sys_user.sql

@@ -0,0 +1,88 @@
1
+/*
2
+ Navicat Premium Data Transfer
3
+
4
+ Source Server         : localhost_3306
5
+ Source Server Type    : MySQL
6
+ Source Server Version : 80045
7
+ Source Host           : localhost:3306
8
+ Source Schema         : chongqing-test
9
+
10
+ Target Server Type    : MySQL
11
+ Target Server Version : 80045
12
+ File Encoding         : 65001
13
+
14
+ Date: 25/04/2026 12:31:26
15
+*/
16
+
17
+SET NAMES utf8mb4;
18
+SET FOREIGN_KEY_CHECKS = 0;
19
+
20
+-- ----------------------------
21
+-- Table structure for sys_user
22
+-- ----------------------------
23
+DROP TABLE IF EXISTS `sys_user`;
24
+CREATE TABLE `sys_user`  (
25
+  `user_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
26
+  `dept_id` bigint(0) NOT NULL COMMENT '部门ID',
27
+  `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号',
28
+  `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称',
29
+  `user_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)',
30
+  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户邮箱',
31
+  `phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码',
32
+  `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
33
+  `avatar` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像地址',
34
+  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码',
35
+  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
36
+  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
37
+  `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP',
38
+  `login_date` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间',
39
+  `pwd_update_date` datetime(0) NULL DEFAULT NULL COMMENT '密码最后更新时间',
40
+  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
41
+  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
42
+  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者',
43
+  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
44
+  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
45
+  `card_number` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '身份证号',
46
+  `political_status` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '政治面貌',
47
+  `training_compliance_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '培训合规状态(0=合规;1=不合规)',
48
+  `qualification_level` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资质等级',
49
+  `administrative_status` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '行政情况(含处罚记录)',
50
+  `physical_health_status` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '身体健康情况',
51
+  `emergency_contact_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '紧急联系人姓名',
52
+  `emergency_contact_phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '紧急联系人电话',
53
+  `emergency_contact_relationship` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '紧急联系人关系',
54
+  `zodiac` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生肖',
55
+  `constellation` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '星座',
56
+  `blood_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '血型',
57
+  `character_characteristics` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '性格特征',
58
+  `working_style` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '工作风格',
59
+  `team_cooperation` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '团队配合',
60
+  `self_assessment_personality_trait` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自我评价-性格特质',
61
+  `self_assessment_capability_performance` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自我评价-能力表现',
62
+  `self_assessment_interpersonal_interaction` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自我评价-人际互动',
63
+  `self_assessment_growth_potential` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自我评价-成长潜力',
64
+  `colleague_comments_personality_trait` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '同事评价-性格特质',
65
+  `colleague_comments_capability_performance` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '同事评价-能力表现',
66
+  `colleague_comments_interpersonal_interaction` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '同事评价-人际互动',
67
+  `colleague_comments_growth_potential` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '同事评价-成长潜力',
68
+  `superior_evaluation_personality_trait` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上级评价-性格特质',
69
+  `superior_evaluation_capability_performance` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上级评价-能力表现',
70
+  `superior_evaluation_interpersonal_interaction` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上级评价-人际互动',
71
+  `superior_evaluation_growth_potential` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上级评价-成长潜力',
72
+  `subordinate_evaluation_personality_trait` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '下级评价-性格特质',
73
+  `subordinate_evaluation_capability_performance` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '下级评价-能力表现',
74
+  `subordinate_evaluation_interpersonal_interaction` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '下级评价-人际互动',
75
+  `subordinate_evaluation_growth_potential` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '下级评价-成长潜力',
76
+  `schooling` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '学历(硕士、本科、专科、技校)',
77
+  `political_review_situation` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '政审情况',
78
+  `start_working_date` date NULL DEFAULT NULL COMMENT '开始工作时间',
79
+  `security_check_start_date` date NULL DEFAULT NULL COMMENT '安检开始时间',
80
+  `security_inspection_position` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '安检工作岗位',
81
+  `work_rewards_number` int(0) NULL DEFAULT NULL COMMENT '工作奖励次数',
82
+  `work_penalties_number` int(0) NULL DEFAULT NULL COMMENT '工作处罚次数',
83
+  `security_work_position` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '曾在安检工作中担任过的最高职务(安检员、班组长)',
84
+  PRIMARY KEY (`user_id`) USING BTREE,
85
+  INDEX `idx_sys_user_dept_id`(`dept_id`) USING BTREE
86
+) ENGINE = InnoDB AUTO_INCREMENT = 320 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;
87
+
88
+SET FOREIGN_KEY_CHECKS = 1;

+ 32 - 0
sql/truncate_ledger.sql

@@ -0,0 +1,32 @@
1
+-- ============================================================
2
+-- 清理台账导入原始数据
3
+-- 场景:重新全量导入前清空所有台账表和同步数据
4
+-- 注意:只清空数据,不删除表结构
5
+-- ============================================================
6
+
7
+SET FOREIGN_KEY_CHECKS = 0;
8
+
9
+-- 台账原始数据(15张表)
10
+TRUNCATE TABLE `ledger_supervision_problem`;     -- 部门监察问题记录
11
+TRUNCATE TABLE `ledger_patrol_inspection`;        -- 队室三级质控巡查记录
12
+TRUNCATE TABLE `ledger_realtime_interception`;    -- 部门实时质控拦截记录
13
+TRUNCATE TABLE `ledger_service_patrol`;           -- 服务巡查记录
14
+TRUNCATE TABLE `ledger_complaint`;                -- 投诉情况
15
+TRUNCATE TABLE `ledger_security_test`;            -- 安保测试记录(部门)
16
+TRUNCATE TABLE `ledger_channel_pass_rate`;        -- 通道过检率
17
+TRUNCATE TABLE `ledger_unsafe_event`;             -- 不安全事件
18
+TRUNCATE TABLE `ledger_seizure_stats`;            -- 查获违规品统计
19
+TRUNCATE TABLE `ledger_terminal_bonus`;           -- 航站楼加分
20
+TRUNCATE TABLE `ledger_exam_score`;               -- 成绩收集
21
+TRUNCATE TABLE `ledger_reward_approval`;          -- 小额奖励审批单
22
+TRUNCATE TABLE `ledger_reward_penalty`;           -- 部门奖惩记录
23
+TRUNCATE TABLE `ledger_leave_special`;            -- 请休假记录(特殊)
24
+TRUNCATE TABLE `ledger_banner_letter`;            -- 锦旗及感谢信
25
+
26
+-- 台账导入日志
27
+TRUNCATE TABLE `ledger_import_log`;
28
+
29
+-- 台账同步写入的配分事项(source_type='2'),手动录入(='1')不受影响
30
+DELETE FROM `score_event` WHERE `source_type` = '2';
31
+
32
+SET FOREIGN_KEY_CHECKS = 1;