41c75b24
“wangming”
refactor: 移除所有薪酬服...
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
|
# 员工门店归属变更问题分析与解决方案
## 📋 问题描述
### 业务场景
员工每个月的归属门店可能会发生变化。当发生改变后,在以下场景中会遇到门店归属问题:
1. **工资计算场景**:
- 员工A在1月在门店A工作
- 2月调到门店B(`BASE_USER.F_Mdid` 更新为门店B)
- 计算1月工资时,如何确定该员工1月的归属门店?
2. **数据补录场景**:
- 员工A在1月在门店A产生开单/消耗数据
- 2月调到门店B
- 如果1月的某些开单/消耗数据在2月补录,这些数据应该归属到哪个门店?
### 核心矛盾
- **时间维度**:数据应该按照实际发生时间归属门店
- **当前归属**:`BASE_USER.F_Mdid` 只记录当前门店归属,无法追溯历史
- **数据完整性**:补录的历史数据需要正确的门店归属
---
## 🔍 现状分析
### 当前数据存储情况
1. **员工门店归属**:
- 存储位置:`BASE_USER.F_Mdid`
- 特点:只有一个字段,记录当前门店ID
- 问题:无法追溯历史门店归属
2. **开单/消耗数据**:
- 开单业绩表 `lq_kd_jksyj`:有 `F_StoreId` 字段(记录开单时的门店)
- 消耗表 `lq_xh_jksyj`:有 `F_StoreId` 字段(记录消耗时的门店)
- 开单主表 `lq_kd_kdjlb`:有 `djmd` 字段(单据门店)
3. **工资计算逻辑**(以健康师工资为例):
```csharp
// 当前逻辑:
// 1. 优先从业绩数据中获取门店(performanceData中的StoreId)
// 2. 如果没有,从消耗数据中获取门店
// 3. 如果还没有,使用 BASE_USER.F_Mdid
```
### 存在的问题
1. **门店归属判断不准确**:
- 如果业务数据(开单/消耗)中没有门店信息,会回退到使用 `BASE_USER.F_Mdid`
- 但 `BASE_USER.F_Mdid` 是当前门店,不是历史门店
2. **补录数据归属问题**:
- 补录历史数据时,如果没有明确的门店信息,可能被错误归属
3. **工资计算偏差**:
- 计算历史月份工资时,可能使用了错误的门店归属
- 影响门店统计、新店判断等逻辑
---
## 💡 解决方案思路
### 方案一:时间维度优先 + 门店归属快照表(推荐)
#### 核心思路
以**时间维度**为唯一标准,通过门店归属快照表记录员工每月的门店归属。
#### 实现方案
1. **创建门店归属快照表**
```
表名:lq_employee_store_assignment(员工门店归属表)
字段:
- F_Id:主键ID
- F_EmployeeId:员工ID
- F_StoreId:门店ID
- F_StoreName:门店名称(冗余字段,便于查询)
- F_Year:年份
- F_Month:月份
- F_StatisticsMonth:统计月份(YYYYMM格式)
- F_StartDate:归属开始日期(精确到天,用于处理月中调店)
- F_EndDate:归属结束日期(可为空,表示当前)
- F_CreateTime:创建时间
- F_UpdateTime:更新时间
- F_CreateUser:创建人
```
2. **门店归属维护机制**
- 在员工调店时,自动创建/更新归属记录
- 支持月中调店(一个员工一个月可能归属多个门店)
- 支持批量导入历史归属数据
3. **工资计算逻辑调整**
```
工资计算时:
1. 根据统计月份(YYYYMM)查询门店归属快照表
2. 如果一个月有多个门店,按时间范围拆分计算
3. 如果查询不到归属记录,再回退到当前逻辑
```
4. **数据补录逻辑**
```
补录数据时:
1. 根据数据实际发生时间(Yjsj)确定月份
2. 查询该月份的门店归属快照
3. 将数据的StoreId设置为归属门店
```
#### 优点
- ✅ 时间维度清晰,历史可追溯
- ✅ 支持月中调店场景
- ✅ 不影响现有数据结构
- ✅ 工资计算和数据补录都基于时间维度
#### 缺点
- ⚠️ 需要维护额外的归属表
- ⚠️ 需要迁移历史数据
---
### 方案二:业务数据中强制记录门店信息
#### 核心思路
在开单/消耗数据录入时,强制要求记录门店信息,工资计算完全依赖业务数据中的门店。
#### 实现方案
1. **数据录入规则**
- 开单/消耗数据必须包含门店信息(StoreId)
- 补录数据时,根据数据发生时间,查询当时的门店归属
- 如果无法确定,要求用户手动选择门店
2. **工资计算逻辑**
```
工资计算时:
1. 只从业务数据(开单/消耗)中获取门店信息
2. 如果业务数据中没有门店信息,报错或跳过
3. 不再使用 BASE_USER.F_Mdid 作为回退
```
#### 优点
- ✅ 数据来源单一,逻辑清晰
- ✅ 不需要额外的表结构
#### 缺点
- ❌ 如果业务数据中没有门店信息,无法计算工资
- ❌ 补录历史数据时,需要手动确定门店归属
- ❌ 数据完整性要求高
---
### 方案三:BASE_USER表增加门店归属历史字段
#### 核心思路
在 `BASE_USER` 表中增加字段,记录门店归属历史(JSON格式或关联表)。
#### 实现方案
1. **字段设计**
```
方案A:JSON字段
F_StoreAssignmentHistory:JSON格式存储历史归属
{
"202501": "store_id_1",
"202502": "store_id_2",
...
}
方案B:关联表
创建 BASE_USER_STORE_HISTORY 表
记录员工每个月的门店归属
```
2. **维护机制**
- 员工调店时,更新历史记录
- 支持批量导入历史数据
#### 优点
- ✅ 数据集中管理
- ✅ 查询方便
#### 缺点
- ⚠️ 修改核心用户表,影响面大
- ⚠️ JSON字段查询性能较差
- ⚠️ 关联表方案与方案一类似
---
### 方案四:工资统计表记录门店归属
#### 核心思路
在工资统计表中记录门店归属,计算工资时使用该记录,但首次计算需要确定归属。
#### 实现方案
1. **工资统计表已有字段**
- `F_StoreId`:门店ID
- `F_StoreName`:门店名称
- 这些字段已经记录了工资计算时的门店归属
2. **逻辑调整**
```
工资计算时:
1. 查询历史工资统计记录
2. 如果存在记录,使用记录中的门店归属
3. 如果不存在,使用当前逻辑确定门店归属
```
#### 优点
- ✅ 不需要额外的表结构
- ✅ 工资记录本身就是历史快照
#### 缺点
- ❌ 首次计算工资时,门店归属判断仍有问题
- ❌ 数据补录时,无法确定门店归属
- ❌ 不能解决数据补录的问题
---
## 🎯 推荐方案
### 推荐:方案一(门店归属快照表)
#### 理由
1. **时间维度清晰**:以时间维度为唯一标准,符合业务逻辑
2. **完整解决**:同时解决工资计算和数据补录的问题
3. **扩展性好**:支持月中调店、批量导入等场景
4. **影响面小**:新增表结构,不影响现有逻辑
#### 实施步骤建议
1. **第一阶段:数据准备**
- 创建门店归属快照表
- 整理历史员工门店归属数据
- 批量导入历史归属数据
2. **第二阶段:功能开发**
- 开发门店归属维护功能(调店时自动创建记录)
- 调整工资计算逻辑(优先使用快照表)
- 调整数据补录逻辑(根据时间查询归属)
3. **第三阶段:数据迁移**
- 迁移历史数据
- 验证数据准确性
- 逐步切换到新逻辑
4. **第四阶段:优化**
- 性能优化
- 用户体验优化
- 监控和告警
---
## 📊 方案对比
| 方案 | 实施难度 | 数据完整性 | 可追溯性 | 扩展性 | 推荐度 |
|------|---------|-----------|---------|--------|--------|
| 方案一:快照表 | 中等 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 方案二:业务数据强制 | 低 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| 方案三:BASE_USER扩展 | 高 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 方案四:工资表记录 | 低 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
---
## 🔧 技术实现要点
### 1. 门店归属快照表设计
```sql
CREATE TABLE lq_employee_store_assignment (
F_Id VARCHAR(50) PRIMARY KEY,
F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID',
F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID',
F_StoreName VARCHAR(200) COMMENT '门店名称',
F_Year INT NOT NULL COMMENT '年份',
F_Month INT NOT NULL COMMENT '月份',
F_StatisticsMonth VARCHAR(6) NOT NULL COMMENT '统计月份YYYYMM',
F_StartDate DATE COMMENT '归属开始日期',
F_EndDate DATE COMMENT '归属结束日期',
F_CreateTime DATETIME,
F_UpdateTime DATETIME,
F_CreateUser VARCHAR(50),
INDEX idx_employee_month (F_EmployeeId, F_StatisticsMonth),
INDEX idx_store_month (F_StoreId, F_StatisticsMonth)
) COMMENT '员工门店归属表';
```
### 2. 工资计算逻辑调整要点
```csharp
// 伪代码示例
public async Task CalculateSalary(int year, int month)
{
var monthStr = $"{year}{month:D2}";
// 1. 查询员工门店归属(优先使用快照表)
var storeAssignments = await _db.Queryable<EmployeeStoreAssignmentEntity>()
.Where(x => x.StatisticsMonth == monthStr)
.ToListAsync();
// 2. 如果没有快照记录,使用当前逻辑(回退方案)
if (!storeAssignments.Any())
{
// 使用现有逻辑:从业务数据或BASE_USER获取
}
// 3. 按门店归属计算工资
foreach (var assignment in storeAssignments)
{
// 查询该员工在该门店的业绩数据
// 计算工资
}
}
```
### 3. 数据补录逻辑调整要点
```csharp
// 伪代码示例
public async Task ImportBillingData(DateTime billingDate, string employeeId)
{
var monthStr = $"{billingDate.Year}{billingDate.Month:D2}";
// 1. 查询该月份的门店归属
var assignment = await _db.Queryable<EmployeeStoreAssignmentEntity>()
.Where(x => x.EmployeeId == employeeId && x.StatisticsMonth == monthStr)
.Where(x => billingDate >= x.StartDate && (x.EndDate == null || billingDate <= x.EndDate))
.FirstAsync();
// 2. 设置数据的门店ID
billingRecord.StoreId = assignment?.StoreId ?? GetStoreIdByCurrentLogic();
}
```
---
## ⚠️ 注意事项
1. **数据一致性**
- 门店归属快照表的数据必须准确
- 需要定期检查和校验
2. **性能考虑**
- 门店归属查询需要建立合适的索引
- 工资计算时批量查询,避免N+1问题
3. **回退方案**
- 如果快照表中没有记录,需要有回退逻辑
- 回退逻辑可以使用现有逻辑(业务数据或BASE_USER)
4. **月中调店处理**
- 如果一个员工一个月内调店,需要创建多条记录
- 工资计算时,需要按时间范围拆分计算
5. **历史数据迁移**
- 需要整理历史员工门店归属数据
- 可能需要手动确认部分数据
---
## 📝 总结
员工门店归属变更问题的核心是**时间维度与当前归属的矛盾**。
**推荐使用方案一(门店归属快照表)**,理由:
- 以时间维度为唯一标准,符合业务逻辑
- 完整解决工资计算和数据补录的问题
- 扩展性好,支持复杂场景
- 实施难度适中,影响面可控
实施时需要重点关注:
- 数据准确性(门店归属快照表)
- 性能优化(索引、批量查询)
- 回退方案(数据缺失时的处理)
- 历史数据迁移(数据整理和导入)
---
**文档版本:** v1.0
**创建日期:** 2026-01-09
**文档性质:** 问题分析与方案设计(仅思考,不修改代码)
|