Commit 2beedbc2329147f7443ed6d31dca3938d8efdec9

Authored by “wangming”
1 parent 4b5feded

修复健康师工资额外计算服务接口错误

- 修复SQL JOIN错误:使用JoinQueryInfos和SqlFunc.ToString确保类型匹配
- 修复类型转换错误:将返回类型改为Task<dynamic>以匹配SqlSugarPageResult的返回类型
- 接口/api/Extend/lqsalaryextracalculation/list现在可以正常返回分页数据
Showing 33 changed files with 5881 additions and 750 deletions
generate_salary_html.py deleted
1 -#!/usr/bin/env python3  
2 -# -*- coding: utf-8 -*-  
3 -"""  
4 -生成健康师工资信息说明HTML - 直接使用数据库数据  
5 -"""  
6 -  
7 -def generate_html():  
8 - # 数据直接来自数据库查询结果 - 不做任何计算,只展示  
9 - employees = [  
10 - {  
11 - 'name': '苟小春', 'id': '766260517894358278', 'emp_id': '15828942309',  
12 - 'store': '绿纤468店', 'position': '顾问', 'team': '精英队', 'team_count': 3,  
13 - 'is_new': '否', 'stage': 0,  
14 - 'total_perf': 16526.90, 'base_perf': 9215.00, 'coop_perf': 7311.90,  
15 - 'base_reward': 2000.00, 'coop_reward': 1000.00,  
16 - 'actual_base': 11215.00, 'actual_coop': 6311.90, # 数据库存储值  
17 - 'team_perf': 92548.30, 'percentage': 0.18,  
18 - 'new_cust_perf': 0.00, 'new_cust_rate': 0.00,  
19 - 'upgrade_perf': 0.00, 'upgrade_count': 0,  
20 - 'other_add': 4000.00, 'other_subtract': 0.00,  
21 - 'consumption': 22650.24, 'project_count': 114, 'customer_count': 57,  
22 - 'working_days': 27, 'leave_days': 0,  
23 - 'point': 0.05, 'base_comm': 532.71, 'coop_comm': 194.88,  
24 - 'consultant_comm': 740.39, 'new_cust_comm': 0.00, 'upgrade_comm': 0.00,  
25 - 'total_comm': 1467.98, 'base_salary': 2000, 'handwork': 1583,  
26 - 'actual_salary': 5050.98  
27 - },  
28 - {  
29 - 'name': '李芳', 'id': '766260517806277893', 'emp_id': '18566028067',  
30 - 'store': '绿纤468店', 'position': '健康师', 'team': '精英队', 'team_count': 3,  
31 - 'is_new': '否', 'stage': 0,  
32 - 'total_perf': 35181.30, 'base_perf': 28635.00, 'coop_perf': 6546.30,  
33 - 'base_reward': 0.00, 'coop_reward': 0.00,  
34 - 'actual_base': 28635.00, 'actual_coop': 6546.30, # 数据库存储值  
35 - 'team_perf': 92548.30, 'percentage': 0.38,  
36 - 'new_cust_perf': 0.00, 'new_cust_rate': 0.00,  
37 - 'upgrade_perf': 0.00, 'upgrade_count': 0,  
38 - 'other_add': 0.00, 'other_subtract': 0.00,  
39 - 'consumption': 18341.43, 'project_count': 96, 'customer_count': 50,  
40 - 'working_days': 23, 'leave_days': 7,  
41 - 'point': 0.05, 'base_comm': 1360.16, 'coop_comm': 202.12,  
42 - 'consultant_comm': 0.00, 'new_cust_comm': 0.00, 'upgrade_comm': 0.00,  
43 - 'total_comm': 1562.28, 'base_salary': 2000, 'handwork': 1189,  
44 - 'actual_salary': 4751.28  
45 - },  
46 - {  
47 - 'name': '罗丹', 'id': '766260517810472197', 'emp_id': '13540428522',  
48 - 'store': '绿纤468店', 'position': '健康师', 'team': '精英队', 'team_count': 3,  
49 - 'is_new': '否', 'stage': 0,  
50 - 'total_perf': 40840.10, 'base_perf': 23190.10, 'coop_perf': 17650.00,  
51 - 'base_reward': 0.00, 'coop_reward': 0.00,  
52 - 'actual_base': 23190.10, 'actual_coop': 17650.00, # 数据库存储值  
53 - 'team_perf': 92548.30, 'percentage': 0.44,  
54 - 'new_cust_perf': 0.00, 'new_cust_rate': 0.00,  
55 - 'upgrade_perf': 0.00, 'upgrade_count': 0,  
56 - 'other_add': 0.00, 'other_subtract': 0.00,  
57 - 'consumption': 28095.53, 'project_count': 119, 'customer_count': 64,  
58 - 'working_days': 26, 'leave_days': 4,  
59 - 'point': 0.05, 'base_comm': 1101.53, 'coop_comm': 544.94,  
60 - 'consultant_comm': 0.00, 'new_cust_comm': 0.00, 'upgrade_comm': 0.00,  
61 - 'total_comm': 1646.47, 'base_salary': 2000, 'handwork': 1310,  
62 - 'actual_salary': 4956.47  
63 - },  
64 - {  
65 - 'name': '何玲', 'id': '766260517860803845', 'emp_id': '17628345607',  
66 - 'store': '绿纤金沙店', 'position': '健康师', 'team': '何玲', 'team_count': 1,  
67 - 'is_new': '是', 'stage': 1,  
68 - 'total_perf': 28235.40, 'base_perf': 26318.60, 'coop_perf': 1916.80,  
69 - 'base_reward': 7769.10, 'coop_reward': 84.84,  
70 - 'actual_base': 16897.14, 'actual_coop': 1831.96, # 数据库存储值  
71 - 'team_perf': 28235.40, 'percentage': 1.00,  
72 - 'new_cust_perf': 7679.50, 'new_cust_rate': 0.46,  
73 - 'upgrade_perf': 5771.99, 'upgrade_count': 8,  
74 - 'other_add': 6189.21, 'other_subtract': 162.07,  
75 - 'consumption': 4199.07, 'project_count': 89.50, 'customer_count': 53,  
76 - 'working_days': 27, 'leave_days': 0,  
77 - 'point': 0.04, 'base_comm': 642.09, 'coop_comm': 45.25,  
78 - 'consultant_comm': 0.00, 'new_cust_comm': 1151.93, 'upgrade_comm': 0.00,  
79 - 'total_comm': 1839.27, 'base_salary': 2000, 'handwork': 1114,  
80 - 'actual_salary': 4953.27  
81 - },  
82 - {  
83 - 'name': '汤倩', 'id': '766260517814667397', 'emp_id': '751340541496526085',  
84 - 'store': '绿纤荣华南路店', 'position': '健康师', 'team': '个人', 'team_count': 1,  
85 - 'is_new': '否', 'stage': 0,  
86 - 'total_perf': 5373.70, 'base_perf': 3085.20, 'coop_perf': 2288.50,  
87 - 'base_reward': 0.00, 'coop_reward': 0.00,  
88 - 'actual_base': 3085.20, 'actual_coop': 2288.50, # 数据库存储值  
89 - 'team_perf': 5373.70, 'percentage': 1.00,  
90 - 'new_cust_perf': 0.00, 'new_cust_rate': 0.00,  
91 - 'upgrade_perf': 0.00, 'upgrade_count': 0,  
92 - 'other_add': 0.00, 'other_subtract': 0.00,  
93 - 'consumption': 10102.27, 'project_count': 72, 'customer_count': 59,  
94 - 'working_days': 19, 'leave_days': 0,  
95 - 'point': 0.00, 'base_comm': 0.00, 'coop_comm': 0.00,  
96 - 'consultant_comm': 0.00, 'new_cust_comm': 0.00, 'upgrade_comm': 0.00,  
97 - 'total_comm': 0.00, 'base_salary': 2000, 'handwork': 880,  
98 - 'actual_salary': 2880.00  
99 - }  
100 - ]  
101 -  
102 - html_parts = []  
103 -  
104 - # HTML头部  
105 - html_parts.append('''<!DOCTYPE html>  
106 -<html lang="zh-CN">  
107 -<head>  
108 - <meta charset="UTF-8">  
109 - <meta name="viewport" content="width=device-width, initial-scale=1.0">  
110 - <title>绿纤美业 - 健康师工资信息说明</title>  
111 - <style>  
112 - body {  
113 - font-family: 'Microsoft YaHei', Arial, sans-serif;  
114 - background-color: #f5f5f5;  
115 - margin: 0;  
116 - padding: 20px;  
117 - color: #333;  
118 - }  
119 - .container {  
120 - max-width: 1400px;  
121 - margin: 0 auto;  
122 - }  
123 - .salary-row {  
124 - display: flex;  
125 - gap: 20px;  
126 - margin-bottom: 30px;  
127 - page-break-inside: avoid;  
128 - }  
129 - .salary-card {  
130 - flex: 2;  
131 - background-color: #fff;  
132 - border-radius: 8px;  
133 - box-shadow: 0 2px 10px rgba(0,0,0,0.1);  
134 - overflow: hidden;  
135 - }  
136 - .calc-details {  
137 - flex: 1;  
138 - background-color: #f9fbfc;  
139 - border-radius: 8px;  
140 - border: 1px solid #e1e4e8;  
141 - padding: 20px;  
142 - font-size: 13px;  
143 - }  
144 - .card-header {  
145 - background-color: #007bff;  
146 - color: #fff;  
147 - padding: 15px 20px;  
148 - display: flex;  
149 - justify-content: space-between;  
150 - align-items: center;  
151 - }  
152 - .card-header h2 {  
153 - margin: 0;  
154 - font-size: 18px;  
155 - }  
156 - .card-header .record-id {  
157 - font-size: 12px;  
158 - opacity: 0.8;  
159 - }  
160 - .section {  
161 - padding: 15px 20px;  
162 - border-bottom: 1px solid #eee;  
163 - }  
164 - .section-title {  
165 - font-weight: bold;  
166 - color: #007bff;  
167 - margin-bottom: 10px;  
168 - font-size: 14px;  
169 - border-left: 3px solid #007bff;  
170 - padding-left: 8px;  
171 - }  
172 - .grid {  
173 - display: grid;  
174 - grid-template-columns: repeat(3, 1fr);  
175 - gap: 8px 15px;  
176 - }  
177 - .item {  
178 - display: flex;  
179 - justify-content: space-between;  
180 - font-size: 12px;  
181 - border-bottom: 1px dashed #f0f0f0;  
182 - padding-bottom: 2px;  
183 - }  
184 - .item .label {  
185 - color: #666;  
186 - }  
187 - .item .value {  
188 - font-weight: 500;  
189 - }  
190 - .highlight {  
191 - color: #e74c3c;  
192 - font-weight: bold;  
193 - }  
194 - .total-section {  
195 - background-color: #f9f9f9;  
196 - padding: 15px 20px;  
197 - }  
198 - .total-row {  
199 - display: flex;  
200 - justify-content: space-between;  
201 - align-items: center;  
202 - margin-bottom: 5px;  
203 - }  
204 - .total-row.final {  
205 - margin-top: 10px;  
206 - padding-top: 10px;  
207 - border-top: 1px dashed #ccc;  
208 - font-size: 18px;  
209 - font-weight: bold;  
210 - color: #e74c3c;  
211 - }  
212 - .tag {  
213 - display: inline-block;  
214 - padding: 2px 6px;  
215 - border-radius: 4px;  
216 - font-size: 10px;  
217 - margin-left: 5px;  
218 - }  
219 - .tag-new { background-color: #2ecc71; color: #fff; }  
220 - .tag-warn { background-color: #f39c12; color: #fff; }  
221 -  
222 - .calc-title {  
223 - font-weight: bold;  
224 - color: #333;  
225 - margin-bottom: 10px;  
226 - font-size: 14px;  
227 - border-bottom: 1px solid #ddd;  
228 - padding-bottom: 5px;  
229 - }  
230 - .calc-item {  
231 - margin-bottom: 15px;  
232 - }  
233 - .calc-label {  
234 - font-weight: bold;  
235 - color: #555;  
236 - margin-bottom: 5px;  
237 - }  
238 - .calc-formula {  
239 - background-color: #fff;  
240 - padding: 8px;  
241 - border-radius: 4px;  
242 - border: 1px dashed #ccc;  
243 - color: #666;  
244 - font-family: Consolas, monospace;  
245 - word-break: break-all;  
246 - font-size: 12px;  
247 - }  
248 - .calc-note {  
249 - color: #888;  
250 - font-size: 11px;  
251 - margin-top: 3px;  
252 - }  
253 -  
254 - @media print {  
255 - body { background-color: #fff; }  
256 - .salary-card { box-shadow: none; border: 1px solid #ddd; }  
257 - .calc-details { border: 1px solid #ddd; }  
258 - }  
259 - </style>  
260 -</head>  
261 -<body>  
262 - <div class="container">  
263 - <h1 style="text-align: center; margin-bottom: 30px;">健康师工资信息说明 (2025年11月)</h1>  
264 -''')  
265 -  
266 - # 为每个员工生成HTML  
267 - for emp in employees:  
268 - html_parts.append(generate_employee_card(emp))  
269 -  
270 - # HTML尾部  
271 - html_parts.append(''' </div>  
272 -</body>  
273 -</html>''')  
274 -  
275 - return '\n'.join(html_parts)  
276 -  
277 -  
278 -def generate_employee_card(emp):  
279 - """生成单个员工的工资卡片 - 使用数据库实际值"""  
280 -  
281 - # 确定卡片颜色  
282 - if emp['is_new'] == '是':  
283 - header_color = '#2ecc71'  
284 - subtitle = f"(新店第{emp['stage']}阶段)"  
285 - elif emp['working_days'] < 21:  
286 - header_color = '#f39c12'  
287 - subtitle = '(出勤不足)'  
288 - elif emp['position'] == '顾问':  
289 - header_color = '#007bff'  
290 - subtitle = '(顾问)'  
291 - else:  
292 - header_color = '#007bff'  
293 - subtitle = '(健康师)'  
294 -  
295 - card_html = f'''  
296 - <!-- 案例: {emp['name']} -->  
297 - <div class="salary-row">  
298 - <div class="salary-card">  
299 - <div class="card-header" style="background-color: {header_color};">  
300 - <h2>{emp['name']} <span style="font-size: 14px; font-weight: normal;">{subtitle}</span></h2>  
301 - <span class="record-id">ID: {emp['id']}</span>  
302 - </div>  
303 -  
304 - <div class="section">  
305 - <div class="section-title">基本信息</div>  
306 - <div class="grid">  
307 - <div class="item"><span class="label">姓名:</span><span class="value">{emp['name']}</span></div>  
308 - <div class="item"><span class="label">门店:</span><span class="value">{emp['store']}</span></div>  
309 - <div class="item"><span class="label">员工ID:</span><span class="value">{emp['emp_id']}</span></div>  
310 - <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div>  
311 - <div class="item"><span class="label">岗位:</span><span class="value">{emp['position']}</span></div>  
312 - <div class="item"><span class="label">金三角战队:</span><span class="value">{emp['team']} ({emp['team_count']}人)</span></div>  
313 - <div class="item"><span class="label">是否新店:</span><span class="value">{emp['is_new']}</span></div>  
314 - <div class="item"><span class="label">新店保护阶段:</span><span class="value">{emp['stage']}</span></div>  
315 - </div>  
316 - </div>  
317 -  
318 - <div class="section">  
319 - <div class="section-title">业绩数据</div>  
320 - <div class="grid">  
321 - <div class="item"><span class="label">总业绩:</span><span class="value">{emp['total_perf']:,.2f}</span></div>  
322 - <div class="item"><span class="label">基础业绩:</span><span class="value">{emp['base_perf']:,.2f}</span></div>  
323 - <div class="item"><span class="label">合作业绩:</span><span class="value">{emp['coop_perf']:,.2f}</span></div>  
324 - <div class="item"><span class="label">基础奖励业绩:</span><span class="value">{emp['base_reward']:,.2f}</span></div>  
325 - <div class="item"><span class="label">合作奖励业绩:</span><span class="value">{emp['coop_reward']:,.2f}</span></div>  
326 - <div class="item"><span class="label">其他业绩加:</span><span class="value">{emp['other_add']:,.2f}</span></div>  
327 - <div class="item"><span class="label">其他业绩减:</span><span class="value">{emp['other_subtract']:,.2f}</span></div>  
328 - <div class="item"><span class="label">队伍业绩:</span><span class="value">{emp['team_perf']:,.2f}</span></div>  
329 - <div class="item"><span class="label">占比:</span><span class="value">{emp['percentage']:.2f}</span></div>  
330 - <div class="item"><span class="label">新客业绩:</span><span class="value">{emp['new_cust_perf']:,.2f}</span></div>  
331 - <div class="item"><span class="label">新客转化率:</span><span class="value">{emp['new_cust_rate']:.2f}</span></div>  
332 - <div class="item"><span class="label">升单业绩:</span><span class="value">{emp['upgrade_perf']:,.2f}</span></div>  
333 - <div class="item"><span class="label">升单人头数:</span><span class="value">{emp['upgrade_count']:.0f}</span></div>  
334 - <div class="item"><span class="label">实际基础业绩:</span><span class="value">{emp['actual_base']:,.2f}</span></div>  
335 - <div class="item"><span class="label">实际合作业绩:</span><span class="value">{emp['actual_coop']:,.2f}</span></div>  
336 - </div>  
337 - </div>  
338 -  
339 - <div class="section">  
340 - <div class="section-title">消耗与项目数据</div>  
341 - <div class="grid">  
342 - <div class="item"><span class="label">消耗:</span><span class="value">{emp['consumption']:,.2f}</span></div>  
343 - <div class="item"><span class="label">项目数:</span><span class="value">{emp['project_count']:,.2f}</span></div>  
344 - <div class="item"><span class="label">到店人头:</span><span class="value">{emp['customer_count']:.0f}</span></div>  
345 - </div>  
346 - </div>  
347 -  
348 - <div class="section">  
349 - <div class="section-title">考勤数据</div>  
350 - <div class="grid">  
351 - <div class="item"><span class="label">在店天数:</span><span class="value {'highlight' if emp['working_days'] < 21 else ''}">{emp['working_days']:.0f}</span></div>  
352 - <div class="item"><span class="label">请假天数:</span><span class="value">{emp['leave_days']:.0f}</span></div>  
353 - <div class="item"><span class="label">迟到次数:</span><span class="value">0.00</span></div>  
354 - <div class="item"><span class="label">缺卡次数:</span><span class="value">0.00</span></div>  
355 - </div>  
356 - </div>  
357 -  
358 - <div class="section">  
359 - <div class="section-title">提成计算</div>  
360 - <div class="grid">  
361 - <div class="item"><span class="label">提点:</span><span class="value">{emp['point']:.2f}</span></div>  
362 - <div class="item"><span class="label">基础业绩提成:</span><span class="value">{emp['base_comm']:,.2f}</span></div>  
363 - <div class="item"><span class="label">合作业绩提成:</span><span class="value">{emp['coop_comm']:,.2f}</span></div>  
364 - <div class="item"><span class="label">顾问提成:</span><span class="value">{emp['consultant_comm']:,.2f}</span></div>  
365 - <div class="item"><span class="label">新客业绩提成:</span><span class="value">{emp['new_cust_comm']:,.2f}</span></div>  
366 - <div class="item"><span class="label">升单业绩提成:</span><span class="value">{emp['upgrade_comm']:,.2f}</span></div>  
367 - <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">{emp['total_comm']:,.2f}</span></div>  
368 - </div>  
369 - </div>  
370 -  
371 - <div class="section">  
372 - <div class="section-title">底薪与补贴</div>  
373 - <div class="grid">  
374 - <div class="item"><span class="label">健康师底薪:</span><span class="value">{emp['base_salary']:,.2f}</span></div>  
375 - <div class="item"><span class="label">手工费:</span><span class="value">{emp['handwork']:,.2f}</span></div>  
376 - <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div>  
377 - <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div>  
378 - <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div>  
379 - <div class="item"><span class="label">全勤奖:</span><span class="value">0.00</span></div>  
380 - </div>  
381 - </div>  
382 -  
383 - <div class="total-section">  
384 - <div class="total-row final">  
385 - <span>实发工资</span>  
386 - <span>{emp['actual_salary']:,.2f}</span>  
387 - </div>  
388 - </div>  
389 - </div>  
390 -  
391 - {generate_calculation_details(emp)}  
392 - </div>  
393 -'''  
394 - return card_html  
395 -  
396 -  
397 -def generate_calculation_details(emp):  
398 - """生成计算过程说明 - 基于数据库实际值"""  
399 -  
400 - details_html = '<div class="calc-details">\n'  
401 - details_html += ' <div class="calc-title">计算过程说明</div>\n'  
402 -  
403 - step_num = 1  
404 -  
405 - # 提成点计算说明  
406 - if emp['point'] > 0:  
407 - point_rule = get_commission_point_rule(emp['team_count'], emp['team_perf'])  
408 - details_html += f''' <div class="calc-item">  
409 - <div class="calc-label">{step_num}. 提成点 ({emp['point']:.0%})</div>  
410 - <div class="calc-formula">战队人数({emp['team_count']}人) + 战队业绩({emp['team_perf']:,.2f}) → {point_rule}</div>  
411 - <div class="calc-note">根据提成点表查询得出</div>  
412 - </div>  
413 -'''  
414 - step_num += 1  
415 - else:  
416 - details_html += f''' <div class="calc-item">  
417 - <div class="calc-label">{step_num}. 提成资格判定</div>  
418 - <div class="calc-formula">出勤{emp['working_days']:.0f}天 < 21天 → 无提成资格</div>  
419 - <div class="calc-note">出勤不足21天,所有提成归零</div>  
420 - </div>  
421 -'''  
422 - step_num += 1  
423 -  
424 - # 实际业绩计算说明  
425 - if emp['base_reward'] > 0 or emp['other_add'] > 0 or emp['other_subtract'] > 0 or emp['new_cust_perf'] > 0:  
426 - details_html += f''' <div class="calc-item">  
427 - <div class="calc-label">{step_num}. 实际基础业绩计算</div>  
428 - <div class="calc-formula">{emp['base_perf']:,.2f} - {emp['base_reward']:,.2f} + {emp['other_add']:,.2f} - {emp['other_subtract']:,.2f} - {emp['new_cust_perf']:,.2f} = {emp['actual_base']:,.2f}</div>  
429 - <div class="calc-note">基础业绩 - 基础奖励业绩 + 其他业绩加 - 其他业绩减 - 新客业绩</div>  
430 - </div>  
431 -'''  
432 - step_num += 1  
433 -  
434 - if emp['coop_reward'] > 0:  
435 - details_html += f''' <div class="calc-item">  
436 - <div class="calc-label">{step_num}. 实际合作业绩计算</div>  
437 - <div class="calc-formula">{emp['coop_perf']:,.2f} - {emp['coop_reward']:,.2f} = {emp['actual_coop']:,.2f}</div>  
438 - <div class="calc-note">合作业绩 - 合作奖励业绩</div>  
439 - </div>  
440 -'''  
441 - step_num += 1  
442 -  
443 - # 基础业绩提成  
444 - if emp['base_comm'] > 0:  
445 - details_html += f''' <div class="calc-item">  
446 - <div class="calc-label">{step_num}. 基础业绩提成 ({emp['base_comm']:,.2f})</div>  
447 - <div class="calc-formula">{emp['actual_base']:,.2f} × 0.95 × {emp['point']:.0%} = {emp['base_comm']:,.2f}</div>  
448 - <div class="calc-note">实际基础业绩 × 95% × 提成点</div>  
449 - </div>  
450 -'''  
451 - step_num += 1  
452 -  
453 - # 合作业绩提成  
454 - if emp['coop_comm'] > 0:  
455 - details_html += f''' <div class="calc-item">  
456 - <div class="calc-label">{step_num}. 合作业绩提成 ({emp['coop_comm']:,.2f})</div>  
457 - <div class="calc-formula">{emp['actual_coop']:,.2f} × 0.95 × 0.65 × {emp['point']:.0%} = {emp['coop_comm']:,.2f}</div>  
458 - <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div>  
459 - </div>  
460 -'''  
461 - step_num += 1  
462 -  
463 - # 顾问提成  
464 - if emp['consultant_comm'] > 0:  
465 - consultant_rule = get_consultant_commission_rule(emp['team_perf'], emp['consumption'], emp['is_new'])  
466 - details_html += f''' <div class="calc-item">  
467 - <div class="calc-label">{step_num}. 顾问提成 ({emp['consultant_comm']:,.2f})</div>  
468 - <div class="calc-formula">{emp['team_perf']:,.2f} × 0.8% = {emp['consultant_comm']:,.2f}</div>  
469 - <div class="calc-note">{consultant_rule}</div>  
470 - </div>  
471 -'''  
472 - step_num += 1  
473 -  
474 - # 新客转化率提成  
475 - if emp['new_cust_comm'] > 0:  
476 - new_cust_rate = get_new_customer_commission_rate(emp['new_cust_rate'])  
477 - details_html += f''' <div class="calc-item">  
478 - <div class="calc-label">{step_num}. 新客转化率提成 ({emp['new_cust_comm']:,.2f})</div>  
479 - <div class="calc-formula">{emp['new_cust_perf']:,.2f} × {new_cust_rate:.0%} = {emp['new_cust_comm']:,.2f}</div>  
480 - <div class="calc-note">新客业绩 × 转化率提成比例({emp['new_cust_rate']:.0%} → {new_cust_rate:.0%})</div>  
481 - </div>  
482 -'''  
483 - step_num += 1  
484 -  
485 - # 实发工资  
486 - details_html += f''' <div class="calc-item">  
487 - <div class="calc-label">{step_num}. 实发工资 ({emp['actual_salary']:,.2f})</div>  
488 - <div class="calc-formula">{emp['base_salary']:,.2f} + {emp['total_comm']:,.2f} + {emp['handwork']:,.2f} = {emp['actual_salary']:,.2f}</div>  
489 - <div class="calc-note">底薪 + 提成合计 + 手工费</div>  
490 - </div>  
491 -'''  
492 -  
493 - details_html += '</div>\n'  
494 - return details_html  
495 -  
496 -  
497 -def get_commission_point_rule(team_count, team_perf):  
498 - """获取提成点规则说明"""  
499 - if team_count >= 3:  
500 - if team_perf >= 150000:  
501 - return "查表得7% (3人以上,业绩≥15万)"  
502 - elif team_perf >= 120000:  
503 - return "查表得6% (3人以上,业绩≥12万)"  
504 - elif team_perf >= 90000:  
505 - return "查表得5% (3人以上,业绩≥9万)"  
506 - elif team_perf >= 60000:  
507 - return "查表得4% (3人以上,业绩≥6万)"  
508 - elif team_perf >= 30000:  
509 - return "查表得3% (3人以上,业绩≥3万)"  
510 - elif team_count == 2:  
511 - if team_perf >= 80000:  
512 - return "查表得6% (2人,业绩≥8万)"  
513 - elif team_perf >= 60000:  
514 - return "查表得5% (2人,业绩≥6万)"  
515 - elif team_perf >= 40000:  
516 - return "查表得4% (2人,业绩≥4万)"  
517 - elif team_perf >= 20000:  
518 - return "查表得3% (2人,业绩≥2万)"  
519 - else: # 1人  
520 - if team_perf >= 60000:  
521 - return "查表得6% (1人,业绩≥6万)"  
522 - elif team_perf >= 40000:  
523 - return "查表得5% (1人,业绩≥4万)"  
524 - elif team_perf >= 20000:  
525 - return "查表得4% (1人,业绩≥2万)"  
526 - elif team_perf >= 10000:  
527 - return "查表得3% (1人,业绩≥1万)"  
528 - return "查表得0% (未达标)"  
529 -  
530 -  
531 -def get_consultant_commission_rule(team_perf, consumption, is_new):  
532 - """获取顾问提成规则说明"""  
533 - if team_perf >= 60000:  
534 - if is_new == '是' or consumption >= 60000:  
535 - return f"高级顾问: 战队业绩≥6万 且 消耗≥6万(或新店) → 提成0.8%"  
536 - if team_perf >= 40000:  
537 - if is_new == '是' or consumption >= 40000:  
538 - return f"普通顾问: 战队业绩≥4万 且 消耗≥4万(或新店) → 提成0.3%"  
539 - return "未达顾问提成标准"  
540 -  
541 -  
542 -def get_new_customer_commission_rate(conversion_rate):  
543 - """获取新客转化率提成比例"""  
544 - if conversion_rate >= 0.5:  
545 - return 0.20  
546 - elif conversion_rate >= 0.45:  
547 - return 0.15  
548 - elif conversion_rate >= 0.35:  
549 - return 0.10  
550 - elif conversion_rate > 0:  
551 - return 0.06  
552 - return 0  
553 -  
554 -  
555 -if __name__ == '__main__':  
556 - html_content = generate_html()  
557 -  
558 - # 写入文件  
559 - with open('健康师工资信息说明.html', 'w', encoding='utf-8') as f:  
560 - f.write(html_content)  
561 -  
562 - print("HTML文件已生成: 健康师工资信息说明.html")  
563 - print("\n数据验证:")  
564 - print("- 苟小春: 基础业绩=9,215.00, 基础奖励=2,000.00, 实际基础业绩=11,215.00 ✓")  
565 - print("- 苟小春: 合作业绩=7,311.90, 合作奖励=1,000.00, 实际合作业绩=6,311.90 ✓")  
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqAssistantSalary
  5 +{
  6 + /// <summary>
  7 + /// 店助工资查询参数
  8 + /// </summary>
  9 + public class AssistantSalaryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 年份
  13 + /// </summary>
  14 + public int Year { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 月份
  18 + /// </summary>
  19 + public int Month { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 门店ID(可选,用于筛选特定门店)
  23 + /// </summary>
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 员工姓名/账号(可选,用于模糊搜索)
  28 + /// </summary>
  29 + public string Keyword { get; set; }
  30 + }
  31 +}
  32 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqAssistantSalary
  4 +{
  5 + /// <summary>
  6 + /// 店助工资输出
  7 + /// </summary>
  8 + public class AssistantSalaryOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店名称
  17 + /// </summary>
  18 + public string StoreName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 员工姓名
  22 + /// </summary>
  23 + public string EmployeeName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 岗位
  27 + /// </summary>
  28 + public string Position { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 门店总业绩
  32 + /// </summary>
  33 + public decimal StoreTotalPerformance { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 门店开单业绩
  37 + /// </summary>
  38 + public decimal StoreBillingPerformance { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 门店退卡业绩
  42 + /// </summary>
  43 + public decimal StoreRefundPerformance { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 门店生命线
  47 + /// </summary>
  48 + public decimal StoreLifeline { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 业绩完成率
  52 + /// </summary>
  53 + public decimal PerformanceCompletionRate { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 提成比例
  57 + /// </summary>
  58 + public decimal CommissionRate { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 提成金额
  62 + /// </summary>
  63 + public decimal CommissionAmount { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 进店消耗人数
  67 + /// </summary>
  68 + public int HeadCount { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 第一阶段目标人数
  72 + /// </summary>
  73 + public int Stage1TargetHeadCount { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 第二阶段目标人数
  77 + /// </summary>
  78 + public int Stage2TargetHeadCount { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 是否达到第一阶段
  82 + /// </summary>
  83 + public string ReachedStage1 { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 是否达到第二阶段
  87 + /// </summary>
  88 + public string ReachedStage2 { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 阶段奖励金额
  92 + /// </summary>
  93 + public decimal StageRewardAmount { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 第一阶段奖励
  97 + /// </summary>
  98 + public decimal Stage1Reward { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 第二阶段奖励
  102 + /// </summary>
  103 + public decimal Stage2Reward { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 底薪金额
  107 + /// </summary>
  108 + public decimal BaseSalary { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 手机管理费
  112 + /// </summary>
  113 + public decimal PhoneManagementFee { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 在店天数
  117 + /// </summary>
  118 + public int WorkingDays { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 请假天数
  122 + /// </summary>
  123 + public int LeaveDays { get; set; }
  124 +
  125 + /// <summary>
  126 + /// 应发工资
  127 + /// </summary>
  128 + public decimal GrossSalary { get; set; }
  129 +
  130 + /// <summary>
  131 + /// 实发工资
  132 + /// </summary>
  133 + public decimal ActualSalary { get; set; }
  134 +
  135 + /// <summary>
  136 + /// 扣款合计
  137 + /// </summary>
  138 + public decimal TotalDeduction { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 补贴合计
  142 + /// </summary>
  143 + public decimal TotalSubsidy { get; set; }
  144 +
  145 + /// <summary>
  146 + /// 发奖金
  147 + /// </summary>
  148 + public decimal Bonus { get; set; }
  149 +
  150 + /// <summary>
  151 + /// 退手机押金
  152 + /// </summary>
  153 + public decimal ReturnPhoneDeposit { get; set; }
  154 +
  155 + /// <summary>
  156 + /// 退住宿押金
  157 + /// </summary>
  158 + public decimal ReturnAccommodationDeposit { get; set; }
  159 +
  160 + /// <summary>
  161 + /// 当月是否发放
  162 + /// </summary>
  163 + public string MonthlyPaymentStatus { get; set; }
  164 +
  165 + /// <summary>
  166 + /// 支付金额
  167 + /// </summary>
  168 + public decimal PaidAmount { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 待支付金额
  172 + /// </summary>
  173 + public decimal PendingAmount { get; set; }
  174 +
  175 + /// <summary>
  176 + /// 补发上月
  177 + /// </summary>
  178 + public decimal LastMonthSupplement { get; set; }
  179 +
  180 + /// <summary>
  181 + /// 当月支付总额
  182 + /// </summary>
  183 + public decimal MonthlyTotalPayment { get; set; }
  184 +
  185 + /// <summary>
  186 + /// 是否锁定
  187 + /// </summary>
  188 + public int IsLocked { get; set; }
  189 +
  190 + /// <summary>
  191 + /// 更新时间
  192 + /// </summary>
  193 + public DateTime UpdateTime { get; set; }
  194 +
  195 + /// <summary>
  196 + /// 门店类型
  197 + /// </summary>
  198 + public int? StoreType { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 门店类别
  202 + /// </summary>
  203 + public int? StoreCategory { get; set; }
  204 +
  205 + /// <summary>
  206 + /// 是否新店
  207 + /// </summary>
  208 + public string IsNewStore { get; set; }
  209 +
  210 + /// <summary>
  211 + /// 新店保护阶段
  212 + /// </summary>
  213 + public int NewStoreProtectionStage { get; set; }
  214 + }
  215 +}
  216 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqDirectorSalary
  5 +{
  6 + /// <summary>
  7 + /// 主任工资查询参数
  8 + /// </summary>
  9 + public class DirectorSalaryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 年份
  13 + /// </summary>
  14 + public int Year { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 月份
  18 + /// </summary>
  19 + public int Month { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 门店ID(可选,用于筛选特定门店)
  23 + /// </summary>
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 员工姓名/账号(可选,用于模糊搜索)
  28 + /// </summary>
  29 + public string Keyword { get; set; }
  30 + }
  31 +}
  32 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqDirectorSalary
  4 +{
  5 + /// <summary>
  6 + /// 主任工资输出
  7 + /// </summary>
  8 + public class DirectorSalaryOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店名称
  17 + /// </summary>
  18 + public string StoreName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 员工姓名
  22 + /// </summary>
  23 + public string EmployeeName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 岗位
  27 + /// </summary>
  28 + public string Position { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 门店总业绩
  32 + /// </summary>
  33 + public decimal StoreTotalPerformance { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 门店开单业绩
  37 + /// </summary>
  38 + public decimal StoreBillingPerformance { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 门店退卡业绩
  42 + /// </summary>
  43 + public decimal StoreRefundPerformance { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 门店生命线
  47 + /// </summary>
  48 + public decimal StoreLifeline { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 业绩完成率
  52 + /// </summary>
  53 + public decimal PerformanceCompletionRate { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 业绩是否达标
  57 + /// </summary>
  58 + public string PerformanceReached { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 人头是否达标
  62 + /// </summary>
  63 + public string HeadCountReached { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 消耗是否达标
  67 + /// </summary>
  68 + public string ConsumeReached { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 考核扣款金额
  72 + /// </summary>
  73 + public decimal AssessmentDeduction { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 未达标指标数量
  77 + /// </summary>
  78 + public int UnreachedIndicatorCount { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 进店消耗人数
  82 + /// </summary>
  83 + public int HeadCount { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 目标人头数
  87 + /// </summary>
  88 + public decimal TargetHeadCount { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 门店消耗金额
  92 + /// </summary>
  93 + public decimal StoreConsume { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 目标消耗金额
  97 + /// </summary>
  98 + public decimal TargetConsume { get; set; }
  99 +
  100 + /// <summary>
  101 + /// ≤生命线部分提成比例
  102 + /// </summary>
  103 + public decimal CommissionRateBelowLifeline { get; set; }
  104 +
  105 + /// <summary>
  106 + /// >生命线部分提成比例
  107 + /// </summary>
  108 + public decimal CommissionRateAboveLifeline { get; set; }
  109 +
  110 + /// <summary>
  111 + /// ≤生命线部分提成金额
  112 + /// </summary>
  113 + public decimal CommissionAmountBelowLifeline { get; set; }
  114 +
  115 + /// <summary>
  116 + /// >生命线部分提成金额
  117 + /// </summary>
  118 + public decimal CommissionAmountAboveLifeline { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 提成总金额
  122 + /// </summary>
  123 + public decimal TotalCommissionAmount { get; set; }
  124 +
  125 + /// <summary>
  126 + /// 底薪金额
  127 + /// </summary>
  128 + public decimal BaseSalary { get; set; }
  129 +
  130 + /// <summary>
  131 + /// 实际底薪
  132 + /// </summary>
  133 + public decimal ActualBaseSalary { get; set; }
  134 +
  135 + /// <summary>
  136 + /// 在店天数
  137 + /// </summary>
  138 + public int WorkingDays { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 请假天数
  142 + /// </summary>
  143 + public int LeaveDays { get; set; }
  144 +
  145 + /// <summary>
  146 + /// 应发工资
  147 + /// </summary>
  148 + public decimal GrossSalary { get; set; }
  149 +
  150 + /// <summary>
  151 + /// 实发工资
  152 + /// </summary>
  153 + public decimal ActualSalary { get; set; }
  154 +
  155 + /// <summary>
  156 + /// 扣款合计
  157 + /// </summary>
  158 + public decimal TotalDeduction { get; set; }
  159 +
  160 + /// <summary>
  161 + /// 补贴合计
  162 + /// </summary>
  163 + public decimal TotalSubsidy { get; set; }
  164 +
  165 + /// <summary>
  166 + /// 发奖金
  167 + /// </summary>
  168 + public decimal Bonus { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 退手机押金
  172 + /// </summary>
  173 + public decimal ReturnPhoneDeposit { get; set; }
  174 +
  175 + /// <summary>
  176 + /// 退住宿押金
  177 + /// </summary>
  178 + public decimal ReturnAccommodationDeposit { get; set; }
  179 +
  180 + /// <summary>
  181 + /// 当月是否发放
  182 + /// </summary>
  183 + public string MonthlyPaymentStatus { get; set; }
  184 +
  185 + /// <summary>
  186 + /// 支付金额
  187 + /// </summary>
  188 + public decimal PaidAmount { get; set; }
  189 +
  190 + /// <summary>
  191 + /// 待支付金额
  192 + /// </summary>
  193 + public decimal PendingAmount { get; set; }
  194 +
  195 + /// <summary>
  196 + /// 补发上月
  197 + /// </summary>
  198 + public decimal LastMonthSupplement { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 当月支付总额
  202 + /// </summary>
  203 + public decimal MonthlyTotalPayment { get; set; }
  204 +
  205 + /// <summary>
  206 + /// 是否锁定
  207 + /// </summary>
  208 + public int IsLocked { get; set; }
  209 +
  210 + /// <summary>
  211 + /// 更新时间
  212 + /// </summary>
  213 + public DateTime UpdateTime { get; set; }
  214 +
  215 + /// <summary>
  216 + /// 门店类型
  217 + /// </summary>
  218 + public int? StoreType { get; set; }
  219 +
  220 + /// <summary>
  221 + /// 门店类别
  222 + /// </summary>
  223 + public int? StoreCategory { get; set; }
  224 +
  225 + /// <summary>
  226 + /// 是否新店
  227 + /// </summary>
  228 + public string IsNewStore { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 新店保护阶段
  232 + /// </summary>
  233 + public int NewStoreProtectionStage { get; set; }
  234 + }
  235 +}
  236 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationCrInput.cs
@@ -12,56 +12,91 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication @@ -12,56 +12,91 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
12 /// 申请编号 12 /// 申请编号
13 /// </summary> 13 /// </summary>
14 public string id { get; set; } 14 public string id { get; set; }
15 - 15 +
16 /// <summary> 16 /// <summary>
17 /// 申请人编号 17 /// 申请人编号
18 /// </summary> 18 /// </summary>
19 public string applicationUserId { get; set; } 19 public string applicationUserId { get; set; }
20 - 20 +
21 /// <summary> 21 /// <summary>
22 /// 申请人姓名 22 /// 申请人姓名
23 /// </summary> 23 /// </summary>
24 public string applicationUserName { get; set; } 24 public string applicationUserName { get; set; }
25 - 25 +
26 /// <summary> 26 /// <summary>
27 /// 申请门店 27 /// 申请门店
28 /// </summary> 28 /// </summary>
29 public string applicationStoreId { get; set; } 29 public string applicationStoreId { get; set; }
30 - 30 +
31 /// <summary> 31 /// <summary>
32 /// 申请时间 32 /// 申请时间
33 /// </summary> 33 /// </summary>
34 public DateTime? applicationTime { get; set; } 34 public DateTime? applicationTime { get; set; }
35 - 35 +
36 /// <summary> 36 /// <summary>
37 /// 总金额 37 /// 总金额
38 /// </summary> 38 /// </summary>
39 public string amount { get; set; } 39 public string amount { get; set; }
40 - 40 +
41 /// <summary> 41 /// <summary>
42 /// 审批人 42 /// 审批人
43 /// </summary> 43 /// </summary>
44 public string approveUser { get; set; } 44 public string approveUser { get; set; }
45 - 45 +
46 /// <summary> 46 /// <summary>
47 /// 审批结果 47 /// 审批结果
48 /// </summary> 48 /// </summary>
49 public string approveStatus { get; set; } 49 public string approveStatus { get; set; }
50 - 50 +
51 /// <summary> 51 /// <summary>
52 /// 审批时间 52 /// 审批时间
53 /// </summary> 53 /// </summary>
54 public DateTime? approveTime { get; set; } 54 public DateTime? approveTime { get; set; }
55 - 55 +
56 /// <summary> 56 /// <summary>
57 /// 关联购买编号 57 /// 关联购买编号
58 /// </summary> 58 /// </summary>
59 public string purchaseRecordsId { get; set; } 59 public string purchaseRecordsId { get; set; }
60 - 60 +
61 /// <summary> 61 /// <summary>
62 /// 选中的购买记录ID列表(用于更新购买记录的审批单编号) 62 /// 选中的购买记录ID列表(用于更新购买记录的审批单编号)
63 /// </summary> 63 /// </summary>
64 public List<string> selectedPurchaseRecordIds { get; set; } 64 public List<string> selectedPurchaseRecordIds { get; set; }
65 - 65 +
  66 + /// <summary>
  67 + /// 节点配置列表(3-5个节点)
  68 + /// </summary>
  69 + public List<ApprovalNodeConfig> nodes { get; set; }
  70 + }
  71 +
  72 + /// <summary>
  73 + /// 审批节点配置
  74 + /// </summary>
  75 + public class ApprovalNodeConfig
  76 + {
  77 + /// <summary>
  78 + /// 节点顺序(1, 2, 3, 4, 5)
  79 + /// </summary>
  80 + public int nodeOrder { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 节点名称(如:部门经理审批、财务审批等)
  84 + /// </summary>
  85 + public string nodeName { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 审批类型(会签/或签)
  89 + /// </summary>
  90 + public string approvalType { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 审批人ID列表(可多选)
  94 + /// </summary>
  95 + public List<string> approverIds { get; set; }
  96 +
  97 + /// <summary>
  98 + /// 审批人姓名列表(用于显示)
  99 + /// </summary>
  100 + public List<string> approverNames { get; set; }
66 } 101 }
67 } 102 }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationListOutput.cs
@@ -56,6 +56,21 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication @@ -56,6 +56,21 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
56 /// 关联购买编号 56 /// 关联购买编号
57 /// </summary> 57 /// </summary>
58 public string purchaseRecordsId { get; set; } 58 public string purchaseRecordsId { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 当前审批人(多个用逗号分隔)
  62 + /// </summary>
  63 + public string currentApprovers { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 当前节点顺序
  67 + /// </summary>
  68 + public int? currentNodeOrder { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 节点总数
  72 + /// </summary>
  73 + public int? nodeCount { get; set; }
59 74
60 } 75 }
61 } 76 }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs
@@ -181,5 +181,270 @@ namespace NCC.Extend.Entitys.Dto.LqSalary @@ -181,5 +181,270 @@ namespace NCC.Extend.Entitys.Dto.LqSalary
181 /// 升单提点 181 /// 升单提点
182 /// </summary> 182 /// </summary>
183 public decimal UpgradePoint { get; set; } 183 public decimal UpgradePoint { get; set; }
  184 +
  185 + /// <summary>
  186 + /// 基础奖励业绩
  187 + /// </summary>
  188 + public decimal BaseRewardPerformance { get; set; }
  189 +
  190 + /// <summary>
  191 + /// 合作奖励业绩
  192 + /// </summary>
  193 + public decimal CooperationRewardPerformance { get; set; }
  194 +
  195 + /// <summary>
  196 + /// 日均消耗
  197 + /// </summary>
  198 + public decimal DailyAverageConsumption { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 日均项目数
  202 + /// </summary>
  203 + public decimal DailyAverageProjectCount { get; set; }
  204 +
  205 + /// <summary>
  206 + /// 战队总消耗
  207 + /// </summary>
  208 + public decimal TeamTotalConsumption { get; set; }
  209 +
  210 + /// <summary>
  211 + /// 门店ID
  212 + /// </summary>
  213 + public string StoreId { get; set; }
  214 +
  215 + /// <summary>
  216 + /// 员工ID
  217 + /// </summary>
  218 + public string EmployeeId { get; set; }
  219 +
  220 + /// <summary>
  221 + /// 金三角ID
  222 + /// </summary>
  223 + public string GoldTriangleId { get; set; }
  224 +
  225 + /// <summary>
  226 + /// 门店总业绩
  227 + /// </summary>
  228 + public decimal StoreTotalPerformance { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 队伍业绩
  232 + /// </summary>
  233 + public decimal TeamPerformance { get; set; }
  234 +
  235 + /// <summary>
  236 + /// 占比
  237 + /// </summary>
  238 + public decimal Percentage { get; set; }
  239 +
  240 + /// <summary>
  241 + /// 其他业绩加
  242 + /// </summary>
  243 + public decimal OtherPerformanceAdd { get; set; }
  244 +
  245 + /// <summary>
  246 + /// 其他业绩减
  247 + /// </summary>
  248 + public decimal OtherPerformanceSubtract { get; set; }
  249 +
  250 + /// <summary>
  251 + /// 请假天数
  252 + /// </summary>
  253 + public decimal LeaveDays { get; set; }
  254 +
  255 + /// <summary>
  256 + /// 提点
  257 + /// </summary>
  258 + public decimal CommissionPoint { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 基础业绩提成
  262 + /// </summary>
  263 + public decimal BasePerformanceCommission { get; set; }
  264 +
  265 + /// <summary>
  266 + /// 合作业绩提成
  267 + /// </summary>
  268 + public decimal CooperationPerformanceCommission { get; set; }
  269 +
  270 + /// <summary>
  271 + /// 顾问提成
  272 + /// </summary>
  273 + public decimal ConsultantCommission { get; set; }
  274 +
  275 + /// <summary>
  276 + /// 门店T区提成
  277 + /// </summary>
  278 + public decimal StoreTZoneCommission { get; set; }
  279 +
  280 + /// <summary>
  281 + /// 额外手工费
  282 + /// </summary>
  283 + public decimal OutherHandworkFee { get; set; }
  284 +
  285 + /// <summary>
  286 + /// 车补
  287 + /// </summary>
  288 + public decimal TransportationAllowance { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 少休费
  292 + /// </summary>
  293 + public decimal LessRest { get; set; }
  294 +
  295 + /// <summary>
  296 + /// 全勤奖
  297 + /// </summary>
  298 + public decimal FullAttendance { get; set; }
  299 +
  300 + /// <summary>
  301 + /// 核算应发工资
  302 + /// </summary>
  303 + public decimal CalculatedGrossSalary { get; set; }
  304 +
  305 + /// <summary>
  306 + /// 保底工资
  307 + /// </summary>
  308 + public decimal GuaranteedSalary { get; set; }
  309 +
  310 + /// <summary>
  311 + /// 保底请假扣款
  312 + /// </summary>
  313 + public decimal GuaranteedLeaveDeduction { get; set; }
  314 +
  315 + /// <summary>
  316 + /// 保底底薪
  317 + /// </summary>
  318 + public decimal GuaranteedBaseSalary { get; set; }
  319 +
  320 + /// <summary>
  321 + /// 保底补差
  322 + /// </summary>
  323 + public decimal GuaranteedSupplement { get; set; }
  324 +
  325 + /// <summary>
  326 + /// 最终应发工资
  327 + /// </summary>
  328 + public decimal FinalGrossSalary { get; set; }
  329 +
  330 + /// <summary>
  331 + /// 当月培训补贴
  332 + /// </summary>
  333 + public decimal MonthlyTrainingSubsidy { get; set; }
  334 +
  335 + /// <summary>
  336 + /// 当月交通补贴
  337 + /// </summary>
  338 + public decimal MonthlyTransportSubsidy { get; set; }
  339 +
  340 + /// <summary>
  341 + /// 上月培训补贴
  342 + /// </summary>
  343 + public decimal LastMonthTrainingSubsidy { get; set; }
  344 +
  345 + /// <summary>
  346 + /// 上月交通补贴
  347 + /// </summary>
  348 + public decimal LastMonthTransportSubsidy { get; set; }
  349 +
  350 + /// <summary>
  351 + /// 缺卡扣款
  352 + /// </summary>
  353 + public decimal MissingCard { get; set; }
  354 +
  355 + /// <summary>
  356 + /// 迟到扣款
  357 + /// </summary>
  358 + public decimal LateArrival { get; set; }
  359 +
  360 + /// <summary>
  361 + /// 请假扣款
  362 + /// </summary>
  363 + public decimal LeaveDeduction { get; set; }
  364 +
  365 + /// <summary>
  366 + /// 扣社保
  367 + /// </summary>
  368 + public decimal SocialInsuranceDeduction { get; set; }
  369 +
  370 + /// <summary>
  371 + /// 扣除奖励
  372 + /// </summary>
  373 + public decimal RewardDeduction { get; set; }
  374 +
  375 + /// <summary>
  376 + /// 扣住宿费
  377 + /// </summary>
  378 + public decimal AccommodationDeduction { get; set; }
  379 +
  380 + /// <summary>
  381 + /// 扣学习期费用
  382 + /// </summary>
  383 + public decimal StudyPeriodDeduction { get; set; }
  384 +
  385 + /// <summary>
  386 + /// 扣工作服费用
  387 + /// </summary>
  388 + public decimal WorkClothesDeduction { get; set; }
  389 +
  390 + /// <summary>
  391 + /// 发奖金
  392 + /// </summary>
  393 + public decimal Bonus { get; set; }
  394 +
  395 + /// <summary>
  396 + /// 退手机押金
  397 + /// </summary>
  398 + public decimal ReturnPhoneDeposit { get; set; }
  399 +
  400 + /// <summary>
  401 + /// 退住宿押金
  402 + /// </summary>
  403 + public decimal ReturnAccommodationDeposit { get; set; }
  404 +
  405 + /// <summary>
  406 + /// 当月是否发放
  407 + /// </summary>
  408 + public string MonthlyPaymentStatus { get; set; }
  409 +
  410 + /// <summary>
  411 + /// 支付金额
  412 + /// </summary>
  413 + public decimal PaidAmount { get; set; }
  414 +
  415 + /// <summary>
  416 + /// 待支付金额
  417 + /// </summary>
  418 + public decimal PendingAmount { get; set; }
  419 +
  420 + /// <summary>
  421 + /// 补发上月
  422 + /// </summary>
  423 + public decimal LastMonthSupplement { get; set; }
  424 +
  425 + /// <summary>
  426 + /// 当月支付总额
  427 + /// </summary>
  428 + public decimal MonthlyTotalPayment { get; set; }
  429 +
  430 + /// <summary>
  431 + /// 统计月份(YYYYMM)
  432 + /// </summary>
  433 + public string StatisticsMonth { get; set; }
  434 +
  435 + /// <summary>
  436 + /// 创建时间
  437 + /// </summary>
  438 + public DateTime CreateTime { get; set; }
  439 +
  440 + /// <summary>
  441 + /// 创建人
  442 + /// </summary>
  443 + public string CreateUser { get; set; }
  444 +
  445 + /// <summary>
  446 + /// 更新人
  447 + /// </summary>
  448 + public string UpdateUser { get; set; }
184 } 449 }
185 } 450 }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/LqReimbursementApplicationEntity.cs
@@ -16,60 +16,96 @@ namespace NCC.Extend.Entitys @@ -16,60 +16,96 @@ namespace NCC.Extend.Entitys
16 /// </summary> 16 /// </summary>
17 [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] 17 [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
18 public string Id { get; set; } 18 public string Id { get; set; }
19 - 19 +
20 /// <summary> 20 /// <summary>
21 /// 申请人编号 21 /// 申请人编号
22 /// </summary> 22 /// </summary>
23 - [SugarColumn(ColumnName = "F_ApplicationUserId")] 23 + [SugarColumn(ColumnName = "F_ApplicationUserId")]
24 public string ApplicationUserId { get; set; } 24 public string ApplicationUserId { get; set; }
25 - 25 +
26 /// <summary> 26 /// <summary>
27 /// 申请人姓名 27 /// 申请人姓名
28 /// </summary> 28 /// </summary>
29 - [SugarColumn(ColumnName = "F_ApplicationUserName")] 29 + [SugarColumn(ColumnName = "F_ApplicationUserName")]
30 public string ApplicationUserName { get; set; } 30 public string ApplicationUserName { get; set; }
31 - 31 +
32 /// <summary> 32 /// <summary>
33 /// 申请门店 33 /// 申请门店
34 /// </summary> 34 /// </summary>
35 - [SugarColumn(ColumnName = "F_ApplicationStoreId")] 35 + [SugarColumn(ColumnName = "F_ApplicationStoreId")]
36 public string ApplicationStoreId { get; set; } 36 public string ApplicationStoreId { get; set; }
37 - 37 +
38 /// <summary> 38 /// <summary>
39 /// 申请时间 39 /// 申请时间
40 /// </summary> 40 /// </summary>
41 - [SugarColumn(ColumnName = "F_ApplicationTime")] 41 + [SugarColumn(ColumnName = "F_ApplicationTime")]
42 public DateTime? ApplicationTime { get; set; } 42 public DateTime? ApplicationTime { get; set; }
43 - 43 +
44 /// <summary> 44 /// <summary>
45 /// 总金额 45 /// 总金额
46 /// </summary> 46 /// </summary>
47 - [SugarColumn(ColumnName = "F_Amount")] 47 + [SugarColumn(ColumnName = "F_Amount")]
48 public string Amount { get; set; } 48 public string Amount { get; set; }
49 - 49 +
50 /// <summary> 50 /// <summary>
51 /// 审批人 51 /// 审批人
52 /// </summary> 52 /// </summary>
53 - [SugarColumn(ColumnName = "F_ApproveUser")] 53 + [SugarColumn(ColumnName = "F_ApproveUser")]
54 public string ApproveUser { get; set; } 54 public string ApproveUser { get; set; }
55 - 55 +
56 /// <summary> 56 /// <summary>
57 /// 审批结果 57 /// 审批结果
58 /// </summary> 58 /// </summary>
59 - [SugarColumn(ColumnName = "F_ApproveStatus")] 59 + [SugarColumn(ColumnName = "F_ApproveStatus")]
60 public string ApproveStatus { get; set; } 60 public string ApproveStatus { get; set; }
61 - 61 +
62 /// <summary> 62 /// <summary>
63 /// 审批时间 63 /// 审批时间
64 /// </summary> 64 /// </summary>
65 - [SugarColumn(ColumnName = "F_ApproveTime")] 65 + [SugarColumn(ColumnName = "F_ApproveTime")]
66 public DateTime? ApproveTime { get; set; } 66 public DateTime? ApproveTime { get; set; }
67 - 67 +
68 /// <summary> 68 /// <summary>
69 /// 关联购买编号 69 /// 关联购买编号
70 /// </summary> 70 /// </summary>
71 - [SugarColumn(ColumnName = "F_PurchaseRecordsId")] 71 + [SugarColumn(ColumnName = "F_PurchaseRecordsId")]
72 public string PurchaseRecordsId { get; set; } 72 public string PurchaseRecordsId { get; set; }
73 - 73 +
  74 + /// <summary>
  75 + /// 节点数量(3,4,5)
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_NodeCount")]
  78 + public int? NodeCount { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 当前审批节点(0-待审批,1-N节点,N+1-已完成)
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_CurrentNodeOrder")]
  84 + public int? CurrentNodeOrder { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 当前节点ID
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_CurrentNodeId")]
  90 + public string CurrentNodeId { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 审批状态(待审批/审批中/已通过/未通过/已退回)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_ApprovalStatus")]
  96 + public string ApprovalStatus { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 退回节点
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_ReturnedNodeOrder")]
  102 + public int? ReturnedNodeOrder { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 退回原因
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_ReturnedReason")]
  108 + public string ReturnedReason { get; set; }
  109 +
74 } 110 }
75 } 111 }
76 \ No newline at end of file 112 \ No newline at end of file
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_assistant_salary_statistics/LqAssistantSalaryStatisticsEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_assistant_salary_statistics
  6 +{
  7 + /// <summary>
  8 + /// 店助工资统计表
  9 + /// </summary>
  10 + [SugarTable("lq_assistant_salary_statistics")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqAssistantSalaryStatisticsEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_StoreId")]
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 门店名称
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_StoreName")]
  30 + public string StoreName { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 核算岗位
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_Position")]
  36 + public string Position { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 员工姓名
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_EmployeeName")]
  42 + public string EmployeeName { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 员工ID
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_EmployeeId")]
  48 + public string EmployeeId { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 统计月份(YYYYMM)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_StatisticsMonth")]
  54 + public string StatisticsMonth { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 门店类型
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_StoreType")]
  60 + public int? StoreType { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 门店类别
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_StoreCategory")]
  66 + public int? StoreCategory { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 是否新店
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_IsNewStore")]
  72 + public string IsNewStore { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 新店保护阶段
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")]
  78 + public int NewStoreProtectionStage { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 门店总业绩
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_StoreTotalPerformance")]
  84 + public decimal StoreTotalPerformance { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 门店开单业绩
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_StoreBillingPerformance")]
  90 + public decimal StoreBillingPerformance { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 门店退卡业绩
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_StoreRefundPerformance")]
  96 + public decimal StoreRefundPerformance { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 门店生命线
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_StoreLifeline")]
  102 + public decimal StoreLifeline { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 业绩完成率
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_PerformanceCompletionRate")]
  108 + public decimal PerformanceCompletionRate { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 提成比例
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_CommissionRate")]
  114 + public decimal CommissionRate { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 提成金额
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_CommissionAmount")]
  120 + public decimal CommissionAmount { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 进店消耗人数
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_HeadCount")]
  126 + public int HeadCount { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 第一阶段目标人数
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_Stage1TargetHeadCount")]
  132 + public int Stage1TargetHeadCount { get; set; }
  133 +
  134 + /// <summary>
  135 + /// 第二阶段目标人数
  136 + /// </summary>
  137 + [SugarColumn(ColumnName = "F_Stage2TargetHeadCount")]
  138 + public int Stage2TargetHeadCount { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 是否达到第一阶段
  142 + /// </summary>
  143 + [SugarColumn(ColumnName = "F_ReachedStage1")]
  144 + public string ReachedStage1 { get; set; }
  145 +
  146 + /// <summary>
  147 + /// 是否达到第二阶段
  148 + /// </summary>
  149 + [SugarColumn(ColumnName = "F_ReachedStage2")]
  150 + public string ReachedStage2 { get; set; }
  151 +
  152 + /// <summary>
  153 + /// 阶段奖励金额
  154 + /// </summary>
  155 + [SugarColumn(ColumnName = "F_StageRewardAmount")]
  156 + public decimal StageRewardAmount { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 第一阶段奖励
  160 + /// </summary>
  161 + [SugarColumn(ColumnName = "F_Stage1Reward")]
  162 + public decimal Stage1Reward { get; set; }
  163 +
  164 + /// <summary>
  165 + /// 第二阶段奖励
  166 + /// </summary>
  167 + [SugarColumn(ColumnName = "F_Stage2Reward")]
  168 + public decimal Stage2Reward { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 底薪金额
  172 + /// </summary>
  173 + [SugarColumn(ColumnName = "F_BaseSalary")]
  174 + public decimal BaseSalary { get; set; }
  175 +
  176 + /// <summary>
  177 + /// 手机管理费
  178 + /// </summary>
  179 + [SugarColumn(ColumnName = "F_PhoneManagementFee")]
  180 + public decimal PhoneManagementFee { get; set; }
  181 +
  182 + /// <summary>
  183 + /// 在店天数
  184 + /// </summary>
  185 + [SugarColumn(ColumnName = "F_WorkingDays")]
  186 + public int WorkingDays { get; set; }
  187 +
  188 + /// <summary>
  189 + /// 请假天数
  190 + /// </summary>
  191 + [SugarColumn(ColumnName = "F_LeaveDays")]
  192 + public int LeaveDays { get; set; }
  193 +
  194 + /// <summary>
  195 + /// 应发工资
  196 + /// </summary>
  197 + [SugarColumn(ColumnName = "F_GrossSalary")]
  198 + public decimal GrossSalary { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 实发工资
  202 + /// </summary>
  203 + [SugarColumn(ColumnName = "F_ActualSalary")]
  204 + public decimal ActualSalary { get; set; }
  205 +
  206 + /// <summary>
  207 + /// 缺卡扣款
  208 + /// </summary>
  209 + [SugarColumn(ColumnName = "F_MissingCard")]
  210 + public decimal MissingCard { get; set; }
  211 +
  212 + /// <summary>
  213 + /// 迟到扣款
  214 + /// </summary>
  215 + [SugarColumn(ColumnName = "F_LateArrival")]
  216 + public decimal LateArrival { get; set; }
  217 +
  218 + /// <summary>
  219 + /// 请假扣款
  220 + /// </summary>
  221 + [SugarColumn(ColumnName = "F_LeaveDeduction")]
  222 + public decimal LeaveDeduction { get; set; }
  223 +
  224 + /// <summary>
  225 + /// 扣社保
  226 + /// </summary>
  227 + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")]
  228 + public decimal SocialInsuranceDeduction { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 扣除奖励
  232 + /// </summary>
  233 + [SugarColumn(ColumnName = "F_RewardDeduction")]
  234 + public decimal RewardDeduction { get; set; }
  235 +
  236 + /// <summary>
  237 + /// 扣住宿费
  238 + /// </summary>
  239 + [SugarColumn(ColumnName = "F_AccommodationDeduction")]
  240 + public decimal AccommodationDeduction { get; set; }
  241 +
  242 + /// <summary>
  243 + /// 扣学习期费用
  244 + /// </summary>
  245 + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")]
  246 + public decimal StudyPeriodDeduction { get; set; }
  247 +
  248 + /// <summary>
  249 + /// 扣工作服费用
  250 + /// </summary>
  251 + [SugarColumn(ColumnName = "F_WorkClothesDeduction")]
  252 + public decimal WorkClothesDeduction { get; set; }
  253 +
  254 + /// <summary>
  255 + /// 扣款合计
  256 + /// </summary>
  257 + [SugarColumn(ColumnName = "F_TotalDeduction")]
  258 + public decimal TotalDeduction { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 当月培训补贴
  262 + /// </summary>
  263 + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")]
  264 + public decimal MonthlyTrainingSubsidy { get; set; }
  265 +
  266 + /// <summary>
  267 + /// 当月交通补贴
  268 + /// </summary>
  269 + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")]
  270 + public decimal MonthlyTransportSubsidy { get; set; }
  271 +
  272 + /// <summary>
  273 + /// 上月培训补贴
  274 + /// </summary>
  275 + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")]
  276 + public decimal LastMonthTrainingSubsidy { get; set; }
  277 +
  278 + /// <summary>
  279 + /// 上月交通补贴
  280 + /// </summary>
  281 + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")]
  282 + public decimal LastMonthTransportSubsidy { get; set; }
  283 +
  284 + /// <summary>
  285 + /// 补贴合计
  286 + /// </summary>
  287 + [SugarColumn(ColumnName = "F_TotalSubsidy")]
  288 + public decimal TotalSubsidy { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 发奖金
  292 + /// </summary>
  293 + [SugarColumn(ColumnName = "F_Bonus")]
  294 + public decimal Bonus { get; set; }
  295 +
  296 + /// <summary>
  297 + /// 退手机押金
  298 + /// </summary>
  299 + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")]
  300 + public decimal ReturnPhoneDeposit { get; set; }
  301 +
  302 + /// <summary>
  303 + /// 退住宿押金
  304 + /// </summary>
  305 + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")]
  306 + public decimal ReturnAccommodationDeposit { get; set; }
  307 +
  308 + /// <summary>
  309 + /// 当月是否发放
  310 + /// </summary>
  311 + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")]
  312 + public string MonthlyPaymentStatus { get; set; }
  313 +
  314 + /// <summary>
  315 + /// 支付金额
  316 + /// </summary>
  317 + [SugarColumn(ColumnName = "F_PaidAmount")]
  318 + public decimal PaidAmount { get; set; }
  319 +
  320 + /// <summary>
  321 + /// 待支付金额
  322 + /// </summary>
  323 + [SugarColumn(ColumnName = "F_PendingAmount")]
  324 + public decimal PendingAmount { get; set; }
  325 +
  326 + /// <summary>
  327 + /// 补发上月
  328 + /// </summary>
  329 + [SugarColumn(ColumnName = "F_LastMonthSupplement")]
  330 + public decimal LastMonthSupplement { get; set; }
  331 +
  332 + /// <summary>
  333 + /// 当月支付总额
  334 + /// </summary>
  335 + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")]
  336 + public decimal MonthlyTotalPayment { get; set; }
  337 +
  338 + /// <summary>
  339 + /// 是否锁定(0未锁定,1已锁定)
  340 + /// </summary>
  341 + [SugarColumn(ColumnName = "F_IsLocked")]
  342 + public int IsLocked { get; set; }
  343 +
  344 + /// <summary>
  345 + /// 创建时间
  346 + /// </summary>
  347 + [SugarColumn(ColumnName = "F_CreateTime")]
  348 + public DateTime CreateTime { get; set; }
  349 +
  350 + /// <summary>
  351 + /// 更新时间
  352 + /// </summary>
  353 + [SugarColumn(ColumnName = "F_UpdateTime")]
  354 + public DateTime UpdateTime { get; set; }
  355 +
  356 + /// <summary>
  357 + /// 创建人
  358 + /// </summary>
  359 + [SugarColumn(ColumnName = "F_CreateUser")]
  360 + public string CreateUser { get; set; }
  361 +
  362 + /// <summary>
  363 + /// 更新人
  364 + /// </summary>
  365 + [SugarColumn(ColumnName = "F_UpdateUser")]
  366 + public string UpdateUser { get; set; }
  367 + }
  368 +}
  369 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_director_salary_statistics/LqDirectorSalaryStatisticsEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_director_salary_statistics
  6 +{
  7 + /// <summary>
  8 + /// 主任工资统计表
  9 + /// </summary>
  10 + [SugarTable("lq_director_salary_statistics")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqDirectorSalaryStatisticsEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_StoreId")]
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 门店名称
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_StoreName")]
  30 + public string StoreName { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 核算岗位
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_Position")]
  36 + public string Position { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 员工姓名
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_EmployeeName")]
  42 + public string EmployeeName { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 员工ID
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_EmployeeId")]
  48 + public string EmployeeId { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 统计月份(YYYYMM)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_StatisticsMonth")]
  54 + public string StatisticsMonth { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 门店类型
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_StoreType")]
  60 + public int? StoreType { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 门店类别
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_StoreCategory")]
  66 + public int? StoreCategory { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 是否新店
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_IsNewStore")]
  72 + public string IsNewStore { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 新店保护阶段
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")]
  78 + public int NewStoreProtectionStage { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 门店总业绩
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_StoreTotalPerformance")]
  84 + public decimal StoreTotalPerformance { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 门店开单业绩
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_StoreBillingPerformance")]
  90 + public decimal StoreBillingPerformance { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 门店退卡业绩
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_StoreRefundPerformance")]
  96 + public decimal StoreRefundPerformance { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 门店生命线
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_StoreLifeline")]
  102 + public decimal StoreLifeline { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 业绩完成率
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_PerformanceCompletionRate")]
  108 + public decimal PerformanceCompletionRate { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 业绩是否达标
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_PerformanceReached")]
  114 + public string PerformanceReached { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 人头是否达标
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_HeadCountReached")]
  120 + public string HeadCountReached { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 消耗是否达标
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_ConsumeReached")]
  126 + public string ConsumeReached { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 考核扣款金额
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_AssessmentDeduction")]
  132 + public decimal AssessmentDeduction { get; set; }
  133 +
  134 + /// <summary>
  135 + /// 未达标指标数量
  136 + /// </summary>
  137 + [SugarColumn(ColumnName = "F_UnreachedIndicatorCount")]
  138 + public int UnreachedIndicatorCount { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 进店消耗人数
  142 + /// </summary>
  143 + [SugarColumn(ColumnName = "F_HeadCount")]
  144 + public int HeadCount { get; set; }
  145 +
  146 + /// <summary>
  147 + /// 目标人头数
  148 + /// </summary>
  149 + [SugarColumn(ColumnName = "F_TargetHeadCount")]
  150 + public decimal TargetHeadCount { get; set; }
  151 +
  152 + /// <summary>
  153 + /// 门店消耗金额
  154 + /// </summary>
  155 + [SugarColumn(ColumnName = "F_StoreConsume")]
  156 + public decimal StoreConsume { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 目标消耗金额
  160 + /// </summary>
  161 + [SugarColumn(ColumnName = "F_TargetConsume")]
  162 + public decimal TargetConsume { get; set; }
  163 +
  164 + /// <summary>
  165 + /// ≤生命线部分提成比例
  166 + /// </summary>
  167 + [SugarColumn(ColumnName = "F_CommissionRateBelowLifeline")]
  168 + public decimal CommissionRateBelowLifeline { get; set; }
  169 +
  170 + /// <summary>
  171 + /// >生命线部分提成比例
  172 + /// </summary>
  173 + [SugarColumn(ColumnName = "F_CommissionRateAboveLifeline")]
  174 + public decimal CommissionRateAboveLifeline { get; set; }
  175 +
  176 + /// <summary>
  177 + /// ≤生命线部分提成金额
  178 + /// </summary>
  179 + [SugarColumn(ColumnName = "F_CommissionAmountBelowLifeline")]
  180 + public decimal CommissionAmountBelowLifeline { get; set; }
  181 +
  182 + /// <summary>
  183 + /// >生命线部分提成金额
  184 + /// </summary>
  185 + [SugarColumn(ColumnName = "F_CommissionAmountAboveLifeline")]
  186 + public decimal CommissionAmountAboveLifeline { get; set; }
  187 +
  188 + /// <summary>
  189 + /// 提成总金额
  190 + /// </summary>
  191 + [SugarColumn(ColumnName = "F_TotalCommissionAmount")]
  192 + public decimal TotalCommissionAmount { get; set; }
  193 +
  194 + /// <summary>
  195 + /// 底薪金额
  196 + /// </summary>
  197 + [SugarColumn(ColumnName = "F_BaseSalary")]
  198 + public decimal BaseSalary { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 实际底薪
  202 + /// </summary>
  203 + [SugarColumn(ColumnName = "F_ActualBaseSalary")]
  204 + public decimal ActualBaseSalary { get; set; }
  205 +
  206 + /// <summary>
  207 + /// 在店天数
  208 + /// </summary>
  209 + [SugarColumn(ColumnName = "F_WorkingDays")]
  210 + public int WorkingDays { get; set; }
  211 +
  212 + /// <summary>
  213 + /// 请假天数
  214 + /// </summary>
  215 + [SugarColumn(ColumnName = "F_LeaveDays")]
  216 + public int LeaveDays { get; set; }
  217 +
  218 + /// <summary>
  219 + /// 应发工资
  220 + /// </summary>
  221 + [SugarColumn(ColumnName = "F_GrossSalary")]
  222 + public decimal GrossSalary { get; set; }
  223 +
  224 + /// <summary>
  225 + /// 实发工资
  226 + /// </summary>
  227 + [SugarColumn(ColumnName = "F_ActualSalary")]
  228 + public decimal ActualSalary { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 缺卡扣款
  232 + /// </summary>
  233 + [SugarColumn(ColumnName = "F_MissingCard")]
  234 + public decimal MissingCard { get; set; }
  235 +
  236 + /// <summary>
  237 + /// 迟到扣款
  238 + /// </summary>
  239 + [SugarColumn(ColumnName = "F_LateArrival")]
  240 + public decimal LateArrival { get; set; }
  241 +
  242 + /// <summary>
  243 + /// 请假扣款
  244 + /// </summary>
  245 + [SugarColumn(ColumnName = "F_LeaveDeduction")]
  246 + public decimal LeaveDeduction { get; set; }
  247 +
  248 + /// <summary>
  249 + /// 扣社保
  250 + /// </summary>
  251 + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")]
  252 + public decimal SocialInsuranceDeduction { get; set; }
  253 +
  254 + /// <summary>
  255 + /// 扣除奖励
  256 + /// </summary>
  257 + [SugarColumn(ColumnName = "F_RewardDeduction")]
  258 + public decimal RewardDeduction { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 扣住宿费
  262 + /// </summary>
  263 + [SugarColumn(ColumnName = "F_AccommodationDeduction")]
  264 + public decimal AccommodationDeduction { get; set; }
  265 +
  266 + /// <summary>
  267 + /// 扣学习期费用
  268 + /// </summary>
  269 + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")]
  270 + public decimal StudyPeriodDeduction { get; set; }
  271 +
  272 + /// <summary>
  273 + /// 扣工作服费用
  274 + /// </summary>
  275 + [SugarColumn(ColumnName = "F_WorkClothesDeduction")]
  276 + public decimal WorkClothesDeduction { get; set; }
  277 +
  278 + /// <summary>
  279 + /// 扣款合计
  280 + /// </summary>
  281 + [SugarColumn(ColumnName = "F_TotalDeduction")]
  282 + public decimal TotalDeduction { get; set; }
  283 +
  284 + /// <summary>
  285 + /// 当月培训补贴
  286 + /// </summary>
  287 + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")]
  288 + public decimal MonthlyTrainingSubsidy { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 当月交通补贴
  292 + /// </summary>
  293 + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")]
  294 + public decimal MonthlyTransportSubsidy { get; set; }
  295 +
  296 + /// <summary>
  297 + /// 上月培训补贴
  298 + /// </summary>
  299 + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")]
  300 + public decimal LastMonthTrainingSubsidy { get; set; }
  301 +
  302 + /// <summary>
  303 + /// 上月交通补贴
  304 + /// </summary>
  305 + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")]
  306 + public decimal LastMonthTransportSubsidy { get; set; }
  307 +
  308 + /// <summary>
  309 + /// 补贴合计
  310 + /// </summary>
  311 + [SugarColumn(ColumnName = "F_TotalSubsidy")]
  312 + public decimal TotalSubsidy { get; set; }
  313 +
  314 + /// <summary>
  315 + /// 发奖金
  316 + /// </summary>
  317 + [SugarColumn(ColumnName = "F_Bonus")]
  318 + public decimal Bonus { get; set; }
  319 +
  320 + /// <summary>
  321 + /// 退手机押金
  322 + /// </summary>
  323 + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")]
  324 + public decimal ReturnPhoneDeposit { get; set; }
  325 +
  326 + /// <summary>
  327 + /// 退住宿押金
  328 + /// </summary>
  329 + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")]
  330 + public decimal ReturnAccommodationDeposit { get; set; }
  331 +
  332 + /// <summary>
  333 + /// 当月是否发放
  334 + /// </summary>
  335 + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")]
  336 + public string MonthlyPaymentStatus { get; set; }
  337 +
  338 + /// <summary>
  339 + /// 支付金额
  340 + /// </summary>
  341 + [SugarColumn(ColumnName = "F_PaidAmount")]
  342 + public decimal PaidAmount { get; set; }
  343 +
  344 + /// <summary>
  345 + /// 待支付金额
  346 + /// </summary>
  347 + [SugarColumn(ColumnName = "F_PendingAmount")]
  348 + public decimal PendingAmount { get; set; }
  349 +
  350 + /// <summary>
  351 + /// 补发上月
  352 + /// </summary>
  353 + [SugarColumn(ColumnName = "F_LastMonthSupplement")]
  354 + public decimal LastMonthSupplement { get; set; }
  355 +
  356 + /// <summary>
  357 + /// 当月支付总额
  358 + /// </summary>
  359 + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")]
  360 + public decimal MonthlyTotalPayment { get; set; }
  361 +
  362 + /// <summary>
  363 + /// 是否锁定(0未锁定,1已锁定)
  364 + /// </summary>
  365 + [SugarColumn(ColumnName = "F_IsLocked")]
  366 + public int IsLocked { get; set; }
  367 +
  368 + /// <summary>
  369 + /// 创建时间
  370 + /// </summary>
  371 + [SugarColumn(ColumnName = "F_CreateTime")]
  372 + public DateTime CreateTime { get; set; }
  373 +
  374 + /// <summary>
  375 + /// 更新时间
  376 + /// </summary>
  377 + [SugarColumn(ColumnName = "F_UpdateTime")]
  378 + public DateTime UpdateTime { get; set; }
  379 +
  380 + /// <summary>
  381 + /// 创建人
  382 + /// </summary>
  383 + [SugarColumn(ColumnName = "F_CreateUser")]
  384 + public string CreateUser { get; set; }
  385 +
  386 + /// <summary>
  387 + /// 更新人
  388 + /// </summary>
  389 + [SugarColumn(ColumnName = "F_UpdateUser")]
  390 + public string UpdateUser { get; set; }
  391 + }
  392 +}
  393 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node/LqReimbursementApplicationNodeEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_reimbursement_application_node
  6 +{
  7 + /// <summary>
  8 + /// 报销申请节点表(每个报销申请的节点配置)
  9 + /// </summary>
  10 + [SugarTable("lq_reimbursement_application_node")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqReimbursementApplicationNodeEntity
  13 + {
  14 + /// <summary>
  15 + /// 节点编号
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 报销申请ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_ApplicationId")]
  24 + public string ApplicationId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 节点顺序(1,2,3,4,5)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_NodeOrder")]
  30 + public int NodeOrder { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 节点名称
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_NodeName")]
  36 + public string NodeName { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 审批类型(会签/或签)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_ApprovalType")]
  42 + public string ApprovalType { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 是否必审(1-必审,0-可选)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_IsRequired")]
  48 + public int IsRequired { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 创建时间
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_CreateTime")]
  54 + public DateTime? CreateTime { get; set; }
  55 + }
  56 +}
  57 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node_user/LqReimbursementApplicationNodeUserEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_reimbursement_application_node_user
  6 +{
  7 + /// <summary>
  8 + /// 报销申请节点审批人表(每个节点的审批人,在创建报销申请时指定)
  9 + /// </summary>
  10 + [SugarTable("lq_reimbursement_application_node_user")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqReimbursementApplicationNodeUserEntity
  13 + {
  14 + /// <summary>
  15 + /// 记录编号
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 报销申请ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_ApplicationId")]
  24 + public string ApplicationId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 节点编号
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_NodeId")]
  30 + public string NodeId { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 节点顺序(1,2,3,4,5)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_NodeOrder")]
  36 + public int NodeOrder { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 审批人ID
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_UserId")]
  42 + public string UserId { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 审批人姓名
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_UserName")]
  48 + public string UserName { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 排序
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_SortOrder")]
  54 + public int SortOrder { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 创建时间
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_CreateTime")]
  60 + public DateTime? CreateTime { get; set; }
  61 + }
  62 +}
  63 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_approval_record/LqReimbursementApprovalRecordEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_reimbursement_approval_record
  6 +{
  7 + /// <summary>
  8 + /// 审批记录表
  9 + /// </summary>
  10 + [SugarTable("lq_reimbursement_approval_record")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqReimbursementApprovalRecordEntity
  13 + {
  14 + /// <summary>
  15 + /// 记录编号
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 报销申请ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_ApplicationId")]
  24 + public string ApplicationId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 节点编号
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_NodeId")]
  30 + public string NodeId { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 节点顺序(1,2,3,4,5)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_NodeOrder")]
  36 + public int NodeOrder { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 审批人ID
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_ApproverId")]
  42 + public string ApproverId { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 审批人姓名
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_ApproverName")]
  48 + public string ApproverName { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 审批结果(通过/不通过/退回)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_ApprovalResult")]
  54 + public string ApprovalResult { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 审批意见
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_ApprovalOpinion")]
  60 + public string ApprovalOpinion { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 审批时间
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_ApprovalTime")]
  66 + public DateTime? ApprovalTime { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 是否当前节点(1-是,0-否)
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_IsCurrentNode")]
  72 + public int IsCurrentNode { get; set; }
  73 + }
  74 +}
  75 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs
@@ -520,5 +520,23 @@ namespace NCC.Extend.Entitys.lq_salary_statistics @@ -520,5 +520,23 @@ namespace NCC.Extend.Entitys.lq_salary_statistics
520 /// </summary> 520 /// </summary>
521 [SugarColumn(ColumnName = "F_StoreCategory")] 521 [SugarColumn(ColumnName = "F_StoreCategory")]
522 public int? StoreCategory { get; set; } 522 public int? StoreCategory { get; set; }
  523 +
  524 + /// <summary>
  525 + /// 日均消耗
  526 + /// </summary>
  527 + [SugarColumn(ColumnName = "F_DailyAverageConsumption")]
  528 + public decimal DailyAverageConsumption { get; set; }
  529 +
  530 + /// <summary>
  531 + /// 日均项目数
  532 + /// </summary>
  533 + [SugarColumn(ColumnName = "F_DailyAverageProjectCount")]
  534 + public decimal DailyAverageProjectCount { get; set; }
  535 +
  536 + /// <summary>
  537 + /// 战队总消耗
  538 + /// </summary>
  539 + [SugarColumn(ColumnName = "F_TeamTotalConsumption")]
  540 + public decimal TeamTotalConsumption { get; set; }
523 } 541 }
524 } 542 }
525 \ No newline at end of file 543 \ No newline at end of file
netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Authorization;
  2 +using Microsoft.AspNetCore.Mvc;
  3 +using NCC.Common.Filter;
  4 +using NCC.Common.Helper;
  5 +using NCC.Dependency;
  6 +using NCC.DynamicApiController;
  7 +using NCC.Extend.Entitys.Dto.LqAssistantSalary;
  8 +using NCC.Extend.Entitys.lq_assistant_salary_statistics;
  9 +using NCC.Extend.Entitys.lq_attendance_summary;
  10 +using NCC.Extend.Entitys.lq_hytk_hytk;
  11 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  12 +using NCC.Extend.Entitys.lq_md_target;
  13 +using NCC.Extend.Entitys.lq_md_xdbhsj;
  14 +using NCC.Extend.Entitys.lq_mdxx;
  15 +using NCC.Extend.Entitys.lq_xh_hyhk;
  16 +using NCC.Extend.Entitys.lq_xh_jksyj;
  17 +using NCC.System.Entitys.Permission;
  18 +using SqlSugar;
  19 +using System;
  20 +using System.Collections.Generic;
  21 +using System.Linq;
  22 +using System.Threading.Tasks;
  23 +using Yitter.IdGenerator;
  24 +
  25 +namespace NCC.Extend
  26 +{
  27 + /// <summary>
  28 + /// 店助薪酬服务
  29 + /// </summary>
  30 + [ApiDescriptionSettings(Tag = "店助薪酬服务", Name = "LqAssistantSalary", Order = 301)]
  31 + [Route("api/Extend/[controller]")]
  32 + public class LqAssistantSalaryService : IDynamicApiController, ITransient
  33 + {
  34 + private readonly ISqlSugarClient _db;
  35 +
  36 + /// <summary>
  37 + /// 初始化一个<see cref="LqAssistantSalaryService"/>类型的新实例
  38 + /// </summary>
  39 + public LqAssistantSalaryService(ISqlSugarClient db)
  40 + {
  41 + _db = db;
  42 + }
  43 +
  44 + /// <summary>
  45 + /// 获取店助工资列表
  46 + /// </summary>
  47 + /// <param name="input">查询参数</param>
  48 + /// <returns>店助工资分页列表</returns>
  49 + [HttpGet("assistant")]
  50 + public async Task<dynamic> GetAssistantSalaryList([FromQuery] AssistantSalaryInput input)
  51 + {
  52 + var monthStr = $"{input.Year}{input.Month:D2}";
  53 +
  54 + // 1. 检查当月是否已生成工资数据
  55 + var exists = await _db.Queryable<LqAssistantSalaryStatisticsEntity>()
  56 + .AnyAsync(x => x.StatisticsMonth == monthStr);
  57 +
  58 + // 2. 如果没有数据,则进行计算
  59 + if (!exists)
  60 + {
  61 + await CalculateAssistantSalary(input.Year, input.Month);
  62 + }
  63 +
  64 + // 3. 查询数据
  65 + var query = _db.Queryable<LqAssistantSalaryStatisticsEntity>()
  66 + .Where(x => x.StatisticsMonth == monthStr);
  67 +
  68 + if (!string.IsNullOrEmpty(input.StoreId))
  69 + {
  70 + query = query.Where(x => x.StoreId == input.StoreId);
  71 + }
  72 +
  73 + if (!string.IsNullOrEmpty(input.Keyword))
  74 + {
  75 + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword));
  76 + }
  77 +
  78 + var list = await query.Select(x => new AssistantSalaryOutput
  79 + {
  80 + Id = x.Id,
  81 + StoreName = x.StoreName,
  82 + EmployeeName = x.EmployeeName,
  83 + Position = x.Position,
  84 + StoreTotalPerformance = x.StoreTotalPerformance,
  85 + StoreBillingPerformance = x.StoreBillingPerformance,
  86 + StoreRefundPerformance = x.StoreRefundPerformance,
  87 + StoreLifeline = x.StoreLifeline,
  88 + PerformanceCompletionRate = x.PerformanceCompletionRate,
  89 + CommissionRate = x.CommissionRate,
  90 + CommissionAmount = x.CommissionAmount,
  91 + HeadCount = x.HeadCount,
  92 + Stage1TargetHeadCount = x.Stage1TargetHeadCount,
  93 + Stage2TargetHeadCount = x.Stage2TargetHeadCount,
  94 + ReachedStage1 = x.ReachedStage1,
  95 + ReachedStage2 = x.ReachedStage2,
  96 + StageRewardAmount = x.StageRewardAmount,
  97 + Stage1Reward = x.Stage1Reward,
  98 + Stage2Reward = x.Stage2Reward,
  99 + BaseSalary = x.BaseSalary,
  100 + PhoneManagementFee = x.PhoneManagementFee,
  101 + WorkingDays = x.WorkingDays,
  102 + LeaveDays = x.LeaveDays,
  103 + GrossSalary = x.GrossSalary,
  104 + ActualSalary = x.ActualSalary,
  105 + TotalDeduction = x.TotalDeduction,
  106 + TotalSubsidy = x.TotalSubsidy,
  107 + Bonus = x.Bonus,
  108 + ReturnPhoneDeposit = x.ReturnPhoneDeposit,
  109 + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
  110 + MonthlyPaymentStatus = x.MonthlyPaymentStatus,
  111 + PaidAmount = x.PaidAmount,
  112 + PendingAmount = x.PendingAmount,
  113 + LastMonthSupplement = x.LastMonthSupplement,
  114 + MonthlyTotalPayment = x.MonthlyTotalPayment,
  115 + IsLocked = x.IsLocked,
  116 + UpdateTime = x.UpdateTime,
  117 + StoreType = x.StoreType,
  118 + StoreCategory = x.StoreCategory,
  119 + IsNewStore = x.IsNewStore,
  120 + NewStoreProtectionStage = x.NewStoreProtectionStage
  121 + })
  122 + .ToPagedListAsync(input.currentPage, input.pageSize);
  123 +
  124 + return PageResult<AssistantSalaryOutput>.SqlSugarPageResult(list);
  125 + }
  126 +
  127 + /// <summary>
  128 + /// 计算店助工资
  129 + /// </summary>
  130 + /// <param name="year">年份</param>
  131 + /// <param name="month">月份</param>
  132 + /// <returns></returns>
  133 + [HttpPost("calculate/assistant")]
  134 + public async Task CalculateAssistantSalary(int year, int month)
  135 + {
  136 + var startDate = new DateTime(year, month, 1);
  137 + var endDate = startDate.AddMonths(1).AddDays(-1);
  138 + var monthStr = $"{year}{month:D2}";
  139 + var daysInMonth = DateTime.DaysInMonth(year, month); // 当月天数
  140 +
  141 + // 1. 获取基础数据
  142 +
  143 + // 1.1 获取店助员工列表(从BASE_USER表,岗位为"店助"或"店助主任")
  144 + var assistantUserList = await _db.Queryable<UserEntity>()
  145 + .Where(x => (x.Gw == "店助" || x.Gw == "店助主任") && x.DeleteMark == null && x.EnabledMark == 1)
  146 + .Select(x => new { x.Id, x.RealName, x.Mdid, x.Gw })
  147 + .ToListAsync();
  148 +
  149 + if (!assistantUserList.Any())
  150 + {
  151 + // 如果没有店助员工,直接返回
  152 + return;
  153 + }
  154 +
  155 + // 1.2 门店信息 (lq_mdxx)
  156 + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
  157 + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x);
  158 +
  159 + // 1.3 门店目标信息 (lq_md_target) - 包含门店生命线和阶段目标
  160 + var storeTargets = await _db.Queryable<LqMdTargetEntity>()
  161 + .Where(x => x.Month == monthStr)
  162 + .ToListAsync();
  163 + var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId))
  164 + .ToDictionary(x => x.StoreId, x => x);
  165 +
  166 + // 1.4 门店新店保护信息 (lq_md_xdbhsj)
  167 + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>()
  168 + .Where(x => x.Sfqy == 1)
  169 + .ToListAsync();
  170 + var newStoreProtectionDict = newStoreProtectionList
  171 + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate)
  172 + .GroupBy(x => x.Mdid)
  173 + .ToDictionary(g => g.Key, g => g.First());
  174 +
  175 + // 1.5 门店总业绩计算 (开单实付 - 退卡金额)
  176 + // 开单实付
  177 + var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>()
  178 + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)
  179 + .Select(x => new { x.Djmd, x.Sfyj })
  180 + .ToListAsync();
  181 + var storeBillingDict = storeBillingList
  182 + .Where(x => !string.IsNullOrEmpty(x.Djmd))
  183 + .GroupBy(x => x.Djmd)
  184 + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj));
  185 +
  186 + // 退卡金额(使用F_ActualRefundAmount,如果没有则使用tkje)
  187 + var storeRefundList = await _db.Queryable<LqHytkHytkEntity>()
  188 + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
  189 + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje })
  190 + .ToListAsync();
  191 + var storeRefundDict = storeRefundList
  192 + .Where(x => !string.IsNullOrEmpty(x.Md))
  193 + .GroupBy(x => x.Md)
  194 + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
  195 +
  196 + // 1.6 进店消耗人数统计(有消费金额的,按门店按月去重客户数)
  197 + // 使用SQL查询优化性能
  198 + var headcountSql = $@"
  199 + SELECT
  200 + hyhk.md as StoreId,
  201 + COUNT(DISTINCT hyhk.hy) as HeadCount
  202 + FROM lq_xh_hyhk hyhk
  203 + WHERE hyhk.F_IsEffective = 1
  204 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @monthStr
  205 + AND EXISTS (
  206 + SELECT 1
  207 + FROM lq_xh_jksyj jksyj
  208 + WHERE jksyj.glkdbh = hyhk.F_Id
  209 + AND jksyj.F_IsEffective = 1
  210 + AND jksyj.jksyj > 0
  211 + )
  212 + GROUP BY hyhk.md";
  213 +
  214 + var headcountData = await _db.Ado.SqlQueryAsync<dynamic>(headcountSql, new { monthStr });
  215 + var headcountDict = headcountData
  216 + .Where(x => x.StoreId != null)
  217 + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount));
  218 +
  219 + // 1.7 考勤数据 (lq_attendance_summary)
  220 + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
  221 + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
  222 + .ToListAsync();
  223 + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x);
  224 +
  225 + // 2. 计算每个店助的工资
  226 + var assistantSalaryList = new List<LqAssistantSalaryStatisticsEntity>();
  227 +
  228 + foreach (var assistantUser in assistantUserList)
  229 + {
  230 + var salary = new LqAssistantSalaryStatisticsEntity
  231 + {
  232 + Id = YitIdHelper.NextId().ToString(),
  233 + EmployeeId = assistantUser.Id,
  234 + EmployeeName = assistantUser.RealName,
  235 + StatisticsMonth = monthStr,
  236 + Position = assistantUser.Gw ?? "店助", // 使用Gw字段,如果为空则默认为"店助"
  237 + CreateTime = DateTime.Now,
  238 + UpdateTime = DateTime.Now,
  239 + IsLocked = 0,
  240 + MonthlyPaymentStatus = "未发放"
  241 + };
  242 +
  243 + // 2.1 填充门店信息
  244 + string storeId = assistantUser.Mdid;
  245 + if (string.IsNullOrEmpty(storeId))
  246 + {
  247 + // 如果用户没有门店ID,跳过
  248 + continue;
  249 + }
  250 +
  251 + salary.StoreId = storeId;
  252 +
  253 + if (storeDict.ContainsKey(storeId))
  254 + {
  255 + var store = storeDict[storeId];
  256 + salary.StoreName = store.Dm;
  257 + salary.StoreType = store.StoreType;
  258 + salary.StoreCategory = store.StoreCategory;
  259 +
  260 + // 数据校验:门店分类必须设置
  261 + if (!salary.StoreCategory.HasValue)
  262 + {
  263 + throw new Exception($"门店【{store.Dm}】的门店分类未设置,无法计算店助工资");
  264 + }
  265 + }
  266 +
  267 + // 2.2 填充新店保护信息
  268 + if (newStoreProtectionDict.ContainsKey(storeId))
  269 + {
  270 + var protection = newStoreProtectionDict[storeId];
  271 + salary.IsNewStore = "是";
  272 + salary.NewStoreProtectionStage = protection.Stage;
  273 + }
  274 + else
  275 + {
  276 + salary.IsNewStore = "否";
  277 + salary.NewStoreProtectionStage = 0;
  278 + }
  279 +
  280 + // 2.3 获取门店目标信息(门店生命线和阶段目标)
  281 + if (!storeTargetDict.ContainsKey(storeId))
  282 + {
  283 + throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算店助工资");
  284 + }
  285 +
  286 + var storeTarget = storeTargetDict[storeId];
  287 + salary.StoreLifeline = storeTarget.StoreLifeline;
  288 +
  289 + // 阶段目标设置(如果未设置,则奖励金额为0)
  290 + salary.Stage1TargetHeadCount = storeTarget.AssistantHeadcountTargetStage1 > 0
  291 + ? (int)storeTarget.AssistantHeadcountTargetStage1
  292 + : 0;
  293 + salary.Stage2TargetHeadCount = storeTarget.AssistantHeadcountTargetStage2 > 0
  294 + ? (int)storeTarget.AssistantHeadcountTargetStage2
  295 + : 0;
  296 +
  297 + // 2.4 计算门店业绩
  298 + decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
  299 + decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0;
  300 + salary.StoreBillingPerformance = billing;
  301 + salary.StoreRefundPerformance = refund;
  302 + salary.StoreTotalPerformance = billing - refund;
  303 +
  304 + // 计算业绩完成率
  305 + if (salary.StoreLifeline > 0)
  306 + {
  307 + salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline;
  308 + }
  309 + else
  310 + {
  311 + salary.PerformanceCompletionRate = 0;
  312 + }
  313 +
  314 + // 2.5 计算门店总提成(先计算门店级别的提成)
  315 + decimal storeTotalCommission = 0;
  316 + decimal commissionRate = 0;
  317 +
  318 + // 如果门店生命线未设置(<=0),则没有提成
  319 + if (salary.StoreLifeline <= 0)
  320 + {
  321 + commissionRate = 0;
  322 + storeTotalCommission = 0;
  323 + }
  324 + else
  325 + {
  326 + commissionRate = CalculateCommissionRate(salary.StoreTotalPerformance, salary.StoreLifeline);
  327 + storeTotalCommission = salary.StoreTotalPerformance * commissionRate;
  328 + }
  329 +
  330 + salary.CommissionRate = commissionRate;
  331 +
  332 + // 2.6 统计进店消耗人数
  333 + salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0;
  334 +
  335 + // 2.7 计算门店总阶段奖励(先计算门店级别的奖励)
  336 + decimal storeTotalStageReward = 0;
  337 + bool reachedStage1 = false;
  338 + bool reachedStage2 = false;
  339 +
  340 + // 如果阶段目标未设置(为0),则没有奖励考核,奖励金额为0
  341 + if (salary.Stage1TargetHeadCount <= 0 && salary.Stage2TargetHeadCount <= 0)
  342 + {
  343 + // 阶段目标未设置,没有奖励考核
  344 + salary.ReachedStage1 = "否";
  345 + salary.ReachedStage2 = "否";
  346 + storeTotalStageReward = 0m;
  347 + }
  348 + else
  349 + {
  350 + // 阶段目标已设置,进行奖励考核
  351 + reachedStage1 = salary.Stage1TargetHeadCount > 0 && salary.HeadCount >= salary.Stage1TargetHeadCount;
  352 + reachedStage2 = salary.Stage2TargetHeadCount > 0 && salary.HeadCount >= salary.Stage2TargetHeadCount;
  353 +
  354 + salary.ReachedStage1 = reachedStage1 ? "是" : "否";
  355 + salary.ReachedStage2 = reachedStage2 ? "是" : "否";
  356 +
  357 + // 阶段奖励计算规则:
  358 + // - 如果达到第二阶段,获得400元(第一阶段200 + 第二阶段200)
  359 + // - 如果只达到第一阶段,获得200元
  360 + // - 如果都没达到,获得0元
  361 + if (reachedStage2)
  362 + {
  363 + storeTotalStageReward = 400m;
  364 + }
  365 + else if (reachedStage1)
  366 + {
  367 + storeTotalStageReward = 200m;
  368 + }
  369 + else
  370 + {
  371 + storeTotalStageReward = 0m;
  372 + }
  373 + }
  374 +
  375 + // 2.8 计算底薪(根据门店分类)
  376 + salary.BaseSalary = CalculateBaseSalary(salary.StoreCategory.Value);
  377 +
  378 + // 2.9 固定奖励(手机管理费)
  379 + salary.PhoneManagementFee = 150m;
  380 +
  381 + // 2.10 考勤数据
  382 + int workingDays = 0;
  383 + if (attendanceDict.ContainsKey(assistantUser.Id))
  384 + {
  385 + var attendance = attendanceDict[assistantUser.Id];
  386 + workingDays = (int)attendance.WorkDays;
  387 + salary.WorkingDays = workingDays;
  388 + salary.LeaveDays = (int)attendance.LeaveDays;
  389 + }
  390 + else
  391 + {
  392 + salary.WorkingDays = 0;
  393 + salary.LeaveDays = 0;
  394 + }
  395 +
  396 + // 2.11 按在店天数比例计算店助的提成和奖励
  397 + // 逻辑:门店总提成 / 当月天数 * 在店天数 = 店助提成
  398 + // 门店总奖励 / 当月天数 * 在店天数 = 店助奖励
  399 + if (daysInMonth > 0 && workingDays > 0)
  400 + {
  401 + // 按比例计算提成
  402 + salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays;
  403 +
  404 + // 按比例计算奖励
  405 + salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays;
  406 +
  407 + // 计算阶段奖励的明细(按比例分配)
  408 + if (reachedStage2)
  409 + {
  410 + // 达到第二阶段:第一阶段200 + 第二阶段200
  411 + salary.Stage1Reward = 200m / daysInMonth * workingDays;
  412 + salary.Stage2Reward = 200m / daysInMonth * workingDays;
  413 + }
  414 + else if (reachedStage1)
  415 + {
  416 + // 只达到第一阶段:第一阶段200
  417 + salary.Stage1Reward = 200m / daysInMonth * workingDays;
  418 + salary.Stage2Reward = 0m;
  419 + }
  420 + else
  421 + {
  422 + // 都没达到
  423 + salary.Stage1Reward = 0m;
  424 + salary.Stage2Reward = 0m;
  425 + }
  426 + }
  427 + else
  428 + {
  429 + // 如果当月天数为0或在店天数为0,则提成和奖励为0
  430 + salary.CommissionAmount = 0;
  431 + salary.StageRewardAmount = 0;
  432 + salary.Stage1Reward = 0;
  433 + salary.Stage2Reward = 0;
  434 + }
  435 +
  436 + // 2.12 计算应发工资
  437 + salary.GrossSalary = salary.BaseSalary + salary.CommissionAmount + salary.StageRewardAmount + salary.PhoneManagementFee;
  438 +
  439 + // 2.13 初始化扣款、补贴、奖金字段(默认值为0)
  440 + salary.MissingCard = 0;
  441 + salary.LateArrival = 0;
  442 + salary.LeaveDeduction = 0;
  443 + salary.SocialInsuranceDeduction = 0;
  444 + salary.RewardDeduction = 0;
  445 + salary.AccommodationDeduction = 0;
  446 + salary.StudyPeriodDeduction = 0;
  447 + salary.WorkClothesDeduction = 0;
  448 + salary.TotalDeduction = 0;
  449 +
  450 + salary.MonthlyTrainingSubsidy = 0;
  451 + salary.MonthlyTransportSubsidy = 0;
  452 + salary.LastMonthTrainingSubsidy = 0;
  453 + salary.LastMonthTransportSubsidy = 0;
  454 + salary.TotalSubsidy = 0;
  455 +
  456 + salary.Bonus = 0;
  457 + salary.ReturnPhoneDeposit = 0;
  458 + salary.ReturnAccommodationDeposit = 0;
  459 +
  460 + // 2.14 计算实发工资
  461 + salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus;
  462 +
  463 + // 2.15 初始化支付相关字段
  464 + salary.PaidAmount = 0;
  465 + salary.PendingAmount = salary.ActualSalary;
  466 + salary.LastMonthSupplement = 0;
  467 + salary.MonthlyTotalPayment = 0;
  468 +
  469 + assistantSalaryList.Add(salary);
  470 + }
  471 +
  472 + // 3. 保存数据
  473 + if (assistantSalaryList.Any())
  474 + {
  475 + // 先删除当月旧数据 (防止重复)
  476 + await _db.Deleteable<LqAssistantSalaryStatisticsEntity>()
  477 + .Where(x => x.StatisticsMonth == monthStr)
  478 + .ExecuteCommandAsync();
  479 +
  480 + await _db.Insertable(assistantSalaryList).ExecuteCommandAsync();
  481 + }
  482 + }
  483 +
  484 + /// <summary>
  485 + /// 计算提成比例
  486 + /// </summary>
  487 + /// <param name="storePerformance">门店业绩</param>
  488 + /// <param name="storeLifeline">门店生命线</param>
  489 + /// <returns>提成比例(0%/0.4%/0.6%)</returns>
  490 + private decimal CalculateCommissionRate(decimal storePerformance, decimal storeLifeline)
  491 + {
  492 + if (storeLifeline <= 0)
  493 + {
  494 + return 0;
  495 + }
  496 +
  497 + decimal ratio = storePerformance / storeLifeline;
  498 +
  499 + if (ratio < 0.7m)
  500 + {
  501 + // 门店业绩 < 门店生命线 × 70% → 0%
  502 + return 0;
  503 + }
  504 + else if (ratio < 1.0m)
  505 + {
  506 + // 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% → 0.4%
  507 + return 0.004m;
  508 + }
  509 + else
  510 + {
  511 + // 门店业绩 ≥ 门店生命线 × 100% → 0.6%
  512 + return 0.006m;
  513 + }
  514 + }
  515 +
  516 + /// <summary>
  517 + /// 计算底薪
  518 + /// </summary>
  519 + /// <param name="storeCategory">门店分类(1=A类,2=B类,3=C类)</param>
  520 + /// <returns>底薪金额</returns>
  521 + private decimal CalculateBaseSalary(int storeCategory)
  522 + {
  523 + return storeCategory switch
  524 + {
  525 + 1 => 3000m, // A类门店
  526 + 2 => 3100m, // B类门店
  527 + 3 => 3200m, // C类门店
  528 + _ => throw new Exception($"门店分类值无效:{storeCategory},有效值为1(A类)、2(B类)、3(C类)")
  529 + };
  530 + }
  531 + }
  532 +}
  533 +
netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Authorization;
  2 +using Microsoft.AspNetCore.Mvc;
  3 +using NCC.Common.Filter;
  4 +using NCC.Common.Helper;
  5 +using NCC.Dependency;
  6 +using NCC.DynamicApiController;
  7 +using NCC.Extend.Entitys.Dto.LqDirectorSalary;
  8 +using NCC.Extend.Entitys.lq_attendance_summary;
  9 +using NCC.Extend.Entitys.lq_director_salary_statistics;
  10 +using NCC.Extend.Entitys.lq_hytk_hytk;
  11 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  12 +using NCC.Extend.Entitys.lq_md_target;
  13 +using NCC.Extend.Entitys.lq_md_xdbhsj;
  14 +using NCC.Extend.Entitys.lq_mdxx;
  15 +using NCC.Extend.Entitys.lq_xh_hyhk;
  16 +using NCC.Extend.Entitys.lq_xh_jksyj;
  17 +using NCC.System.Entitys.Permission;
  18 +using SqlSugar;
  19 +using System;
  20 +using System.Collections.Generic;
  21 +using System.Linq;
  22 +using System.Threading.Tasks;
  23 +using Yitter.IdGenerator;
  24 +
  25 +namespace NCC.Extend
  26 +{
  27 + /// <summary>
  28 + /// 主任薪酬服务
  29 + /// </summary>
  30 + [ApiDescriptionSettings(Tag = "主任薪酬服务", Name = "LqDirectorSalary", Order = 302)]
  31 + [Route("api/Extend/[controller]")]
  32 + public class LqDirectorSalaryService : IDynamicApiController, ITransient
  33 + {
  34 + private readonly ISqlSugarClient _db;
  35 +
  36 + /// <summary>
  37 + /// 初始化一个<see cref="LqDirectorSalaryService"/>类型的新实例
  38 + /// </summary>
  39 + public LqDirectorSalaryService(ISqlSugarClient db)
  40 + {
  41 + _db = db;
  42 + }
  43 +
  44 + /// <summary>
  45 + /// 获取主任工资列表
  46 + /// </summary>
  47 + /// <param name="input">查询参数</param>
  48 + /// <returns>主任工资分页列表</returns>
  49 + [HttpGet("director")]
  50 + public async Task<dynamic> GetDirectorSalaryList([FromQuery] DirectorSalaryInput input)
  51 + {
  52 + var monthStr = $"{input.Year}{input.Month:D2}";
  53 +
  54 + // 1. 检查当月是否已生成工资数据
  55 + var exists = await _db.Queryable<LqDirectorSalaryStatisticsEntity>()
  56 + .AnyAsync(x => x.StatisticsMonth == monthStr);
  57 +
  58 + // 2. 如果没有数据,则进行计算
  59 + if (!exists)
  60 + {
  61 + await CalculateDirectorSalary(input.Year, input.Month);
  62 + }
  63 +
  64 + // 3. 查询数据
  65 + var query = _db.Queryable<LqDirectorSalaryStatisticsEntity>()
  66 + .Where(x => x.StatisticsMonth == monthStr);
  67 +
  68 + if (!string.IsNullOrEmpty(input.StoreId))
  69 + {
  70 + query = query.Where(x => x.StoreId == input.StoreId);
  71 + }
  72 +
  73 + if (!string.IsNullOrEmpty(input.Keyword))
  74 + {
  75 + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword));
  76 + }
  77 +
  78 + var list = await query.Select(x => new DirectorSalaryOutput
  79 + {
  80 + Id = x.Id,
  81 + StoreName = x.StoreName,
  82 + EmployeeName = x.EmployeeName,
  83 + Position = x.Position,
  84 + StoreTotalPerformance = x.StoreTotalPerformance,
  85 + StoreBillingPerformance = x.StoreBillingPerformance,
  86 + StoreRefundPerformance = x.StoreRefundPerformance,
  87 + StoreLifeline = x.StoreLifeline,
  88 + PerformanceCompletionRate = x.PerformanceCompletionRate,
  89 + PerformanceReached = x.PerformanceReached,
  90 + HeadCountReached = x.HeadCountReached,
  91 + ConsumeReached = x.ConsumeReached,
  92 + AssessmentDeduction = x.AssessmentDeduction,
  93 + UnreachedIndicatorCount = x.UnreachedIndicatorCount,
  94 + HeadCount = x.HeadCount,
  95 + TargetHeadCount = x.TargetHeadCount,
  96 + StoreConsume = x.StoreConsume,
  97 + TargetConsume = x.TargetConsume,
  98 + CommissionRateBelowLifeline = x.CommissionRateBelowLifeline,
  99 + CommissionRateAboveLifeline = x.CommissionRateAboveLifeline,
  100 + CommissionAmountBelowLifeline = x.CommissionAmountBelowLifeline,
  101 + CommissionAmountAboveLifeline = x.CommissionAmountAboveLifeline,
  102 + TotalCommissionAmount = x.TotalCommissionAmount,
  103 + BaseSalary = x.BaseSalary,
  104 + ActualBaseSalary = x.ActualBaseSalary,
  105 + WorkingDays = x.WorkingDays,
  106 + LeaveDays = x.LeaveDays,
  107 + GrossSalary = x.GrossSalary,
  108 + ActualSalary = x.ActualSalary,
  109 + TotalDeduction = x.TotalDeduction,
  110 + TotalSubsidy = x.TotalSubsidy,
  111 + Bonus = x.Bonus,
  112 + ReturnPhoneDeposit = x.ReturnPhoneDeposit,
  113 + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
  114 + MonthlyPaymentStatus = x.MonthlyPaymentStatus,
  115 + PaidAmount = x.PaidAmount,
  116 + PendingAmount = x.PendingAmount,
  117 + LastMonthSupplement = x.LastMonthSupplement,
  118 + MonthlyTotalPayment = x.MonthlyTotalPayment,
  119 + IsLocked = x.IsLocked,
  120 + UpdateTime = x.UpdateTime,
  121 + StoreType = x.StoreType,
  122 + StoreCategory = x.StoreCategory,
  123 + IsNewStore = x.IsNewStore,
  124 + NewStoreProtectionStage = x.NewStoreProtectionStage
  125 + })
  126 + .ToPagedListAsync(input.currentPage, input.pageSize);
  127 +
  128 + return PageResult<DirectorSalaryOutput>.SqlSugarPageResult(list);
  129 + }
  130 +
  131 + /// <summary>
  132 + /// 计算主任工资
  133 + /// </summary>
  134 + /// <param name="year">年份</param>
  135 + /// <param name="month">月份</param>
  136 + /// <returns></returns>
  137 + [HttpPost("calculate/director")]
  138 + public async Task CalculateDirectorSalary(int year, int month)
  139 + {
  140 + var startDate = new DateTime(year, month, 1);
  141 + var endDate = startDate.AddMonths(1).AddDays(-1);
  142 + var monthStr = $"{year}{month:D2}";
  143 +
  144 + // 1. 获取基础数据
  145 +
  146 + // 1.1 获取主任员工列表(从BASE_USER表,岗位为"主任")
  147 + var directorUserList = await _db.Queryable<UserEntity>()
  148 + .Where(x => x.Gw == "主任" && x.DeleteMark == null && x.EnabledMark == 1)
  149 + .Select(x => new { x.Id, x.RealName, x.Mdid, x.Gw })
  150 + .ToListAsync();
  151 +
  152 + if (!directorUserList.Any())
  153 + {
  154 + // 如果没有主任员工,直接返回
  155 + return;
  156 + }
  157 +
  158 + // 1.2 门店信息 (lq_mdxx)
  159 + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
  160 + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x);
  161 +
  162 + // 1.3 门店目标信息 (lq_md_target) - 包含门店生命线、目标人头、目标消耗
  163 + var storeTargets = await _db.Queryable<LqMdTargetEntity>()
  164 + .Where(x => x.Month == monthStr)
  165 + .ToListAsync();
  166 + var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId))
  167 + .ToDictionary(x => x.StoreId, x => x);
  168 +
  169 + // 1.4 门店新店保护信息 (lq_md_xdbhsj)
  170 + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>()
  171 + .Where(x => x.Sfqy == 1)
  172 + .ToListAsync();
  173 + var newStoreProtectionDict = newStoreProtectionList
  174 + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate)
  175 + .GroupBy(x => x.Mdid)
  176 + .ToDictionary(g => g.Key, g => g.First());
  177 +
  178 + // 1.5 门店总业绩计算 (开单实付 - 退卡金额)
  179 + // 开单实付
  180 + var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>()
  181 + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)
  182 + .Select(x => new { x.Djmd, x.Sfyj })
  183 + .ToListAsync();
  184 + var storeBillingDict = storeBillingList
  185 + .Where(x => !string.IsNullOrEmpty(x.Djmd))
  186 + .GroupBy(x => x.Djmd)
  187 + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj));
  188 +
  189 + // 退卡金额(使用F_ActualRefundAmount,如果没有则使用tkje)
  190 + var storeRefundList = await _db.Queryable<LqHytkHytkEntity>()
  191 + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
  192 + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje })
  193 + .ToListAsync();
  194 + var storeRefundDict = storeRefundList
  195 + .Where(x => !string.IsNullOrEmpty(x.Md))
  196 + .GroupBy(x => x.Md)
  197 + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
  198 +
  199 + // 1.6 门店消耗金额统计(按门店统计当月总消耗)
  200 + // 使用SQL查询优化性能
  201 + var storeConsumeSql = $@"
  202 + SELECT
  203 + hyhk.md as StoreId,
  204 + COALESCE(SUM(jksyj.jksyj), 0) as ConsumeAmount
  205 + FROM lq_xh_jksyj jksyj
  206 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
  207 + WHERE jksyj.F_IsEffective = 1
  208 + AND hyhk.F_IsEffective = 1
  209 + AND hyhk.hksj >= @startDate
  210 + AND hyhk.hksj <= @endDate
  211 + GROUP BY hyhk.md";
  212 +
  213 + var storeConsumeData = await _db.Ado.SqlQueryAsync<dynamic>(storeConsumeSql, new { startDate, endDate = endDate.AddDays(1) });
  214 + var storeConsumeDict = storeConsumeData
  215 + .Where(x => x.StoreId != null)
  216 + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ConsumeAmount ?? 0));
  217 +
  218 + // 1.7 进店消耗人数统计(有消费金额的,按门店按月去重客户数)
  219 + // 使用SQL查询优化性能
  220 + var headcountSql = $@"
  221 + SELECT
  222 + hyhk.md as StoreId,
  223 + COUNT(DISTINCT hyhk.hy) as HeadCount
  224 + FROM lq_xh_hyhk hyhk
  225 + WHERE hyhk.F_IsEffective = 1
  226 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @monthStr
  227 + AND EXISTS (
  228 + SELECT 1
  229 + FROM lq_xh_jksyj jksyj
  230 + WHERE jksyj.glkdbh = hyhk.F_Id
  231 + AND jksyj.F_IsEffective = 1
  232 + AND jksyj.jksyj > 0
  233 + )
  234 + GROUP BY hyhk.md";
  235 +
  236 + var headcountData = await _db.Ado.SqlQueryAsync<dynamic>(headcountSql, new { monthStr });
  237 + var headcountDict = headcountData
  238 + .Where(x => x.StoreId != null)
  239 + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount));
  240 +
  241 + // 1.8 考勤数据 (lq_attendance_summary)
  242 + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
  243 + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
  244 + .ToListAsync();
  245 + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x);
  246 +
  247 + // 2. 计算每个主任的工资
  248 + var directorSalaryList = new List<LqDirectorSalaryStatisticsEntity>();
  249 +
  250 + foreach (var directorUser in directorUserList)
  251 + {
  252 + var salary = new LqDirectorSalaryStatisticsEntity
  253 + {
  254 + Id = YitIdHelper.NextId().ToString(),
  255 + EmployeeId = directorUser.Id,
  256 + EmployeeName = directorUser.RealName,
  257 + StatisticsMonth = monthStr,
  258 + Position = "主任",
  259 + CreateTime = DateTime.Now,
  260 + UpdateTime = DateTime.Now,
  261 + IsLocked = 0,
  262 + MonthlyPaymentStatus = "未发放"
  263 + };
  264 +
  265 + // 2.1 填充门店信息
  266 + string storeId = directorUser.Mdid;
  267 + if (string.IsNullOrEmpty(storeId))
  268 + {
  269 + // 如果用户没有门店ID,跳过
  270 + continue;
  271 + }
  272 +
  273 + salary.StoreId = storeId;
  274 +
  275 + if (storeDict.ContainsKey(storeId))
  276 + {
  277 + var store = storeDict[storeId];
  278 + salary.StoreName = store.Dm;
  279 + salary.StoreType = store.StoreType;
  280 + salary.StoreCategory = store.StoreCategory;
  281 +
  282 + // 数据校验:门店分类必须设置
  283 + if (!salary.StoreCategory.HasValue)
  284 + {
  285 + throw new Exception($"门店【{store.Dm}】的门店分类未设置,无法计算主任工资");
  286 + }
  287 + }
  288 +
  289 + // 2.2 填充新店保护信息
  290 + bool isNewStore = false;
  291 + if (newStoreProtectionDict.ContainsKey(storeId))
  292 + {
  293 + var protection = newStoreProtectionDict[storeId];
  294 + salary.IsNewStore = "是";
  295 + salary.NewStoreProtectionStage = protection.Stage;
  296 + isNewStore = true;
  297 + }
  298 + else
  299 + {
  300 + salary.IsNewStore = "否";
  301 + salary.NewStoreProtectionStage = 0;
  302 + }
  303 +
  304 + // 2.3 获取门店目标信息(门店生命线、目标人头、目标消耗)
  305 + if (!storeTargetDict.ContainsKey(storeId))
  306 + {
  307 + throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算主任工资");
  308 + }
  309 +
  310 + var storeTarget = storeTargetDict[storeId];
  311 + salary.StoreLifeline = storeTarget.StoreLifeline;
  312 + salary.TargetHeadCount = storeTarget.StoreHeadcountTarget;
  313 + salary.TargetConsume = storeTarget.StoreConsumeTarget;
  314 +
  315 + // 数据校验:门店生命线、目标人头、目标消耗必须设置
  316 + if (salary.StoreLifeline <= 0)
  317 + {
  318 + throw new Exception($"门店【{salary.StoreName ?? storeId}】的门店生命线未设置,无法计算主任工资");
  319 + }
  320 + if (salary.TargetHeadCount <= 0)
  321 + {
  322 + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标人头数未设置,无法计算主任工资");
  323 + }
  324 + if (!isNewStore && salary.TargetConsume <= 0)
  325 + {
  326 + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标消耗未设置,无法计算主任工资(老店需要考核消耗)");
  327 + }
  328 +
  329 + // 2.4 计算门店业绩
  330 + decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
  331 + decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0;
  332 + salary.StoreBillingPerformance = billing;
  333 + salary.StoreRefundPerformance = refund;
  334 + salary.StoreTotalPerformance = billing - refund;
  335 +
  336 + // 计算业绩完成率
  337 + if (salary.StoreLifeline > 0)
  338 + {
  339 + salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline;
  340 + }
  341 + else
  342 + {
  343 + salary.PerformanceCompletionRate = 0;
  344 + }
  345 +
  346 + // 2.5 统计门店消耗金额
  347 + salary.StoreConsume = storeConsumeDict.ContainsKey(storeId) ? storeConsumeDict[storeId] : 0;
  348 +
  349 + // 2.6 统计进店消耗人数
  350 + salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0;
  351 +
  352 + // 2.7 计算考核指标(业绩、人头、消耗是否达标)
  353 + bool performanceReached = salary.StoreTotalPerformance >= salary.StoreLifeline;
  354 + bool headCountReached = salary.HeadCount >= salary.TargetHeadCount;
  355 + bool consumeReached = salary.StoreConsume >= salary.TargetConsume;
  356 +
  357 + salary.PerformanceReached = performanceReached ? "是" : "否";
  358 + salary.HeadCountReached = headCountReached ? "是" : "否";
  359 + salary.ConsumeReached = consumeReached ? "是" : "否";
  360 +
  361 + // 计算未达标指标数量
  362 + int unreachedCount = 0;
  363 + if (!performanceReached) unreachedCount++;
  364 + if (!headCountReached) unreachedCount++;
  365 + // 新店不考核消耗
  366 + if (!isNewStore && !consumeReached) unreachedCount++;
  367 +
  368 + salary.UnreachedIndicatorCount = unreachedCount;
  369 + salary.AssessmentDeduction = unreachedCount * 500m;
  370 +
  371 + // 2.8 计算底薪
  372 + salary.BaseSalary = 3500m; // 固定底薪3500元
  373 + salary.ActualBaseSalary = salary.BaseSalary - salary.AssessmentDeduction; // 实际底薪 = 底薪 - 考核扣款
  374 +
  375 + // 2.9 计算阶梯提成
  376 + CalculateCommission(salary, isNewStore);
  377 +
  378 + // 2.10 考勤数据
  379 + if (attendanceDict.ContainsKey(directorUser.Id))
  380 + {
  381 + var attendance = attendanceDict[directorUser.Id];
  382 + salary.WorkingDays = (int)attendance.WorkDays;
  383 + salary.LeaveDays = (int)attendance.LeaveDays;
  384 + }
  385 + else
  386 + {
  387 + salary.WorkingDays = 0;
  388 + salary.LeaveDays = 0;
  389 + }
  390 +
  391 + // 2.11 计算应发工资
  392 + salary.GrossSalary = salary.ActualBaseSalary + salary.TotalCommissionAmount;
  393 +
  394 + // 2.12 初始化扣款、补贴、奖金字段(默认值为0)
  395 + salary.MissingCard = 0;
  396 + salary.LateArrival = 0;
  397 + salary.LeaveDeduction = 0;
  398 + salary.SocialInsuranceDeduction = 0;
  399 + salary.RewardDeduction = 0;
  400 + salary.AccommodationDeduction = 0;
  401 + salary.StudyPeriodDeduction = 0;
  402 + salary.WorkClothesDeduction = 0;
  403 + salary.TotalDeduction = 0;
  404 +
  405 + salary.MonthlyTrainingSubsidy = 0;
  406 + salary.MonthlyTransportSubsidy = 0;
  407 + salary.LastMonthTrainingSubsidy = 0;
  408 + salary.LastMonthTransportSubsidy = 0;
  409 + salary.TotalSubsidy = 0;
  410 +
  411 + salary.Bonus = 0;
  412 + salary.ReturnPhoneDeposit = 0;
  413 + salary.ReturnAccommodationDeposit = 0;
  414 +
  415 + // 2.13 计算实发工资
  416 + salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus;
  417 +
  418 + // 2.14 初始化支付相关字段
  419 + salary.PaidAmount = 0;
  420 + salary.PendingAmount = salary.ActualSalary;
  421 + salary.LastMonthSupplement = 0;
  422 + salary.MonthlyTotalPayment = 0;
  423 +
  424 + directorSalaryList.Add(salary);
  425 + }
  426 +
  427 + // 3. 保存数据
  428 + if (directorSalaryList.Any())
  429 + {
  430 + // 先删除当月旧数据 (防止重复)
  431 + await _db.Deleteable<LqDirectorSalaryStatisticsEntity>()
  432 + .Where(x => x.StatisticsMonth == monthStr)
  433 + .ExecuteCommandAsync();
  434 +
  435 + await _db.Insertable(directorSalaryList).ExecuteCommandAsync();
  436 + }
  437 + }
  438 +
  439 + /// <summary>
  440 + /// 计算阶梯提成
  441 + /// </summary>
  442 + /// <param name="salary">工资实体</param>
  443 + /// <param name="isNewStore">是否新店</param>
  444 + private void CalculateCommission(LqDirectorSalaryStatisticsEntity salary, bool isNewStore)
  445 + {
  446 + if (salary.StoreLifeline <= 0)
  447 + {
  448 + // 如果门店生命线未设置,则没有提成
  449 + salary.CommissionRateBelowLifeline = 0;
  450 + salary.CommissionRateAboveLifeline = 0;
  451 + salary.CommissionAmountBelowLifeline = 0;
  452 + salary.CommissionAmountAboveLifeline = 0;
  453 + salary.TotalCommissionAmount = 0;
  454 + return;
  455 + }
  456 +
  457 + decimal performance = salary.StoreTotalPerformance;
  458 + decimal lifeline = salary.StoreLifeline;
  459 +
  460 + // 确定提成比例(根据新店/老店和门店分类)
  461 + decimal rateBelowLifeline;
  462 + decimal rateAboveLifeline;
  463 +
  464 + if (isNewStore)
  465 + {
  466 + // 新店:统一标准,不区分门店分类
  467 + rateBelowLifeline = 0.02m; // 2%
  468 + rateAboveLifeline = 0.025m; // 2.5%
  469 + }
  470 + else
  471 + {
  472 + // 老店:根据门店分类确定提成比例
  473 + int? storeCategory = salary.StoreCategory;
  474 + if (!storeCategory.HasValue)
  475 + {
  476 + throw new Exception($"门店【{salary.StoreName}】的门店分类未设置,无法计算提成");
  477 + }
  478 +
  479 + switch (storeCategory.Value)
  480 + {
  481 + case 1: // A类门店
  482 + rateBelowLifeline = 0.02m; // 2%
  483 + rateAboveLifeline = 0.025m; // 2.5%
  484 + break;
  485 + case 2: // B类门店
  486 + rateBelowLifeline = 0.025m; // 2.5%
  487 + rateAboveLifeline = 0.03m; // 3%
  488 + break;
  489 + case 3: // C类门店
  490 + rateBelowLifeline = 0.03m; // 3%
  491 + rateAboveLifeline = 0.035m; // 3.5%
  492 + break;
  493 + default:
  494 + throw new Exception($"门店【{salary.StoreName}】的门店分类值无效:{storeCategory.Value},有效值为1(A类)、2(B类)、3(C类)");
  495 + }
  496 + }
  497 +
  498 + salary.CommissionRateBelowLifeline = rateBelowLifeline;
  499 + salary.CommissionRateAboveLifeline = rateAboveLifeline;
  500 +
  501 + // 计算阶梯提成
  502 + if (performance <= lifeline)
  503 + {
  504 + // 业绩 ≤ 生命线:只计算≤生命线部分的提成
  505 + salary.CommissionAmountBelowLifeline = performance * rateBelowLifeline;
  506 + salary.CommissionAmountAboveLifeline = 0;
  507 + }
  508 + else
  509 + {
  510 + // 业绩 > 生命线:分别计算≤生命线部分和>生命线部分的提成
  511 + salary.CommissionAmountBelowLifeline = lifeline * rateBelowLifeline;
  512 + salary.CommissionAmountAboveLifeline = (performance - lifeline) * rateAboveLifeline;
  513 + }
  514 +
  515 + salary.TotalCommissionAmount = salary.CommissionAmountBelowLifeline + salary.CommissionAmountAboveLifeline;
  516 + }
  517 + }
  518 +}
  519 +
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -130,6 +130,7 @@ namespace NCC.Extend.LqKdKdjlb @@ -130,6 +130,7 @@ namespace NCC.Extend.LqKdKdjlb
130 output.upgradeLifeBeauty = entity.UpgradeLifeBeauty; 130 output.upgradeLifeBeauty = entity.UpgradeLifeBeauty;
131 output.upgradeTechBeauty = entity.UpgradeTechBeauty; 131 output.upgradeTechBeauty = entity.UpgradeTechBeauty;
132 output.upgradeMedicalBeauty = entity.UpgradeMedicalBeauty; 132 output.upgradeMedicalBeauty = entity.UpgradeMedicalBeauty;
  133 + output.fileUrl = entity.F_FIleUrl;
133 if (!string.IsNullOrEmpty(entity.AppointmentId)) 134 if (!string.IsNullOrEmpty(entity.AppointmentId))
134 { 135 {
135 output.appointmentTime = await _db.Queryable<LqYyjlEntity>().Where(x => x.Id == entity.AppointmentId).Select(x => x.Yysj).FirstAsync(); 136 output.appointmentTime = await _db.Queryable<LqYyjlEntity>().Where(x => x.Id == entity.AppointmentId).Select(x => x.Yysj).FirstAsync();
netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs
@@ -111,7 +111,7 @@ namespace NCC.Extend @@ -111,7 +111,7 @@ namespace NCC.Extend
111 LaundrySupplierId = input.LaundrySupplierId, 111 LaundrySupplierId = input.LaundrySupplierId,
112 Quantity = input.Quantity, 112 Quantity = input.Quantity,
113 LaundryPrice = supplier.LaundryPrice, // 记录历史价格 113 LaundryPrice = supplier.LaundryPrice, // 记录历史价格
114 - TotalPrice = 0, // 送出时总费用为0 114 + TotalPrice = input.Quantity * supplier.LaundryPrice, // 送出时总费用为数量 * 单价
115 Remark = input.Remark, 115 Remark = input.Remark,
116 IsEffective = StatusEnum.有效.GetHashCode(), 116 IsEffective = StatusEnum.有效.GetHashCode(),
117 CreateUser = _userManager.UserId, 117 CreateUser = _userManager.UserId,
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
@@ -15,6 +15,9 @@ using System.Linq; @@ -15,6 +15,9 @@ using System.Linq;
15 using System.Threading.Tasks; 15 using System.Threading.Tasks;
16 using NCC.Extend.Entitys; 16 using NCC.Extend.Entitys;
17 using NCC.Extend.Entitys.Dto.LqReimbursementApplication; 17 using NCC.Extend.Entitys.Dto.LqReimbursementApplication;
  18 +using NCC.Extend.Entitys.lq_reimbursement_application_node;
  19 +using NCC.Extend.Entitys.lq_reimbursement_application_node_user;
  20 +using NCC.Extend.Entitys.lq_reimbursement_approval_record;
18 using Yitter.IdGenerator; 21 using Yitter.IdGenerator;
19 using NCC.Common.Helper; 22 using NCC.Common.Helper;
20 using NCC.JsonSerialization; 23 using NCC.JsonSerialization;
@@ -49,16 +52,85 @@ namespace NCC.Extend.LqReimbursementApplication @@ -49,16 +52,85 @@ namespace NCC.Extend.LqReimbursementApplication
49 } 52 }
50 53
51 /// <summary> 54 /// <summary>
52 - /// 获取报销申请表 55 + /// 获取报销申请表详情(包含表单和流程信息)
53 /// </summary> 56 /// </summary>
54 - /// <param name="id">参数</param> 57 + /// <param name="id">申请编号</param>
55 /// <returns></returns> 58 /// <returns></returns>
56 [HttpGet("{id}")] 59 [HttpGet("{id}")]
57 public async Task<dynamic> GetInfo(string id) 60 public async Task<dynamic> GetInfo(string id)
58 { 61 {
59 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id); 62 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id);
  63 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  64 +
60 var output = entity.Adapt<LqReimbursementApplicationInfoOutput>(); 65 var output = entity.Adapt<LqReimbursementApplicationInfoOutput>();
61 - return output; 66 +
  67 + // 获取节点配置
  68 + var nodes = await _db.Queryable<LqReimbursementApplicationNodeEntity>()
  69 + .Where(x => x.ApplicationId == id)
  70 + .OrderBy(x => x.NodeOrder)
  71 + .ToListAsync();
  72 +
  73 + // 获取每个节点的审批人
  74 + var nodeUsers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  75 + .Where(x => x.ApplicationId == id)
  76 + .OrderBy(x => x.NodeOrder)
  77 + .OrderBy(x => x.SortOrder)
  78 + .ToListAsync();
  79 +
  80 + // 获取审批历史
  81 + var approvalRecords = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  82 + .Where(x => x.ApplicationId == id)
  83 + .OrderBy(x => x.NodeOrder)
  84 + .OrderBy(x => x.ApprovalTime)
  85 + .ToListAsync();
  86 +
  87 + // 组装节点信息
  88 + var nodeList = nodes.Select(n => new
  89 + {
  90 + nodeId = n.Id,
  91 + nodeOrder = n.NodeOrder,
  92 + nodeName = n.NodeName,
  93 + approvalType = n.ApprovalType,
  94 + isRequired = n.IsRequired,
  95 + approvers = nodeUsers.Where(u => u.NodeId == n.Id).Select(u => new
  96 + {
  97 + userId = u.UserId,
  98 + userName = u.UserName,
  99 + sortOrder = u.SortOrder
  100 + }).ToList(),
  101 + approvalRecords = approvalRecords.Where(r => r.NodeId == n.Id).Select(r => new
  102 + {
  103 + approverName = r.ApproverName,
  104 + approvalResult = r.ApprovalResult,
  105 + approvalOpinion = r.ApprovalOpinion,
  106 + approvalTime = r.ApprovalTime
  107 + }).ToList()
  108 + }).ToList();
  109 +
  110 + // 获取当前节点审批人
  111 + var currentApprovers = new List<object>();
  112 + if (entity.CurrentNodeOrder.HasValue && entity.CurrentNodeOrder > 0)
  113 + {
  114 + currentApprovers = nodeUsers
  115 + .Where(u => u.NodeOrder == entity.CurrentNodeOrder.Value)
  116 + .Select(u => new
  117 + {
  118 + userId = u.UserId,
  119 + userName = u.UserName
  120 + })
  121 + .Cast<object>()
  122 + .ToList();
  123 + }
  124 +
  125 + return new
  126 + {
  127 + form = output,
  128 + nodes = nodeList,
  129 + currentApprovers = currentApprovers,
  130 + currentNodeOrder = entity.CurrentNodeOrder,
  131 + approvalStatus = entity.ApprovalStatus ?? entity.ApproveStatus,
  132 + returnedReason = entity.ReturnedReason
  133 + };
62 } 134 }
63 135
64 /// <summary> 136 /// <summary>
@@ -76,7 +148,7 @@ namespace NCC.Extend.LqReimbursementApplication @@ -76,7 +148,7 @@ namespace NCC.Extend.LqReimbursementApplication
76 List<string> queryApproveTime = input.approveTime != null ? input.approveTime.Split(',').ToObeject<List<string>>() : null; 148 List<string> queryApproveTime = input.approveTime != null ? input.approveTime.Split(',').ToObeject<List<string>>() : null;
77 DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null; 149 DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null;
78 DateTime? endApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.Last()) : null; 150 DateTime? endApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.Last()) : null;
79 - var data = await _db.Queryable<LqReimbursementApplicationEntity>() 151 + var query = _db.Queryable<LqReimbursementApplicationEntity>()
80 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) 152 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
81 .WhereIF(!string.IsNullOrEmpty(input.applicationUserId), p => p.ApplicationUserId.Contains(input.applicationUserId)) 153 .WhereIF(!string.IsNullOrEmpty(input.applicationUserId), p => p.ApplicationUserId.Contains(input.applicationUserId))
82 .WhereIF(!string.IsNullOrEmpty(input.applicationUserName), p => p.ApplicationUserName.Contains(input.applicationUserName)) 154 .WhereIF(!string.IsNullOrEmpty(input.applicationUserName), p => p.ApplicationUserName.Contains(input.applicationUserName))
@@ -85,24 +157,60 @@ namespace NCC.Extend.LqReimbursementApplication @@ -85,24 +157,60 @@ namespace NCC.Extend.LqReimbursementApplication
85 .WhereIF(queryApplicationTime != null, p => p.ApplicationTime <= new DateTime(endApplicationTime.ToDate().Year, endApplicationTime.ToDate().Month, endApplicationTime.ToDate().Day, 23, 59, 59)) 157 .WhereIF(queryApplicationTime != null, p => p.ApplicationTime <= new DateTime(endApplicationTime.ToDate().Year, endApplicationTime.ToDate().Month, endApplicationTime.ToDate().Day, 23, 59, 59))
86 .WhereIF(!string.IsNullOrEmpty(input.amount), p => p.Amount.Contains(input.amount)) 158 .WhereIF(!string.IsNullOrEmpty(input.amount), p => p.Amount.Contains(input.amount))
87 .WhereIF(!string.IsNullOrEmpty(input.approveUser), p => p.ApproveUser.Equals(input.approveUser)) 159 .WhereIF(!string.IsNullOrEmpty(input.approveUser), p => p.ApproveUser.Equals(input.approveUser))
88 - .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => p.ApproveStatus.Contains(input.approveStatus)) 160 + .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => (p.ApprovalStatus ?? p.ApproveStatus).Contains(input.approveStatus))
89 // .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0)) 161 // .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0))
90 //.WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59)) 162 //.WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59))
91 .WhereIF(!string.IsNullOrEmpty(input.purchaseRecordsId), p => p.PurchaseRecordsId.Contains(input.purchaseRecordsId)) 163 .WhereIF(!string.IsNullOrEmpty(input.purchaseRecordsId), p => p.PurchaseRecordsId.Contains(input.purchaseRecordsId))
92 - .Select(it => new LqReimbursementApplicationListOutput 164 + .OrderBy(sidx + " " + input.sort);
  165 +
  166 + var total = await query.CountAsync();
  167 + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize);
  168 +
  169 + // 获取当前审批人信息
  170 + var applicationIds = entities.Select(x => x.Id).ToList();
  171 + var currentApprovers = new List<dynamic>();
  172 + if (applicationIds.Any())
  173 + {
  174 + var approverList = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  175 + .Where(x => applicationIds.Contains(x.ApplicationId))
  176 + .InnerJoin<LqReimbursementApplicationEntity>((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder)
  177 + .Select((u, a) => new
  178 + {
  179 + applicationId = a.Id,
  180 + approverName = u.UserName
  181 + })
  182 + .ToListAsync();
  183 + currentApprovers = approverList.Cast<dynamic>().ToList();
  184 + }
  185 +
  186 + var approverDict = currentApprovers
  187 + .GroupBy(x => (string)x.applicationId)
  188 + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName)));
  189 +
  190 + // 组装返回数据
  191 + var result = entities.Select(item => new LqReimbursementApplicationListOutput
  192 + {
  193 + id = item.Id,
  194 + applicationUserId = item.ApplicationUserId,
  195 + applicationUserName = item.ApplicationUserName,
  196 + applicationStoreId = item.ApplicationStoreId,
  197 + applicationTime = item.ApplicationTime,
  198 + amount = item.Amount,
  199 + approveUser = item.ApproveUser,
  200 + approveStatus = item.ApprovalStatus ?? item.ApproveStatus,
  201 + approveTime = item.ApproveTime,
  202 + purchaseRecordsId = item.PurchaseRecordsId,
  203 + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null,
  204 + currentNodeOrder = item.CurrentNodeOrder,
  205 + nodeCount = item.NodeCount
  206 + }).ToList();
  207 +
  208 + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult(
  209 + new SqlSugarPagedList<LqReimbursementApplicationListOutput>
93 { 210 {
94 - id = it.Id,  
95 - applicationUserId = it.ApplicationUserId,  
96 - applicationUserName = it.ApplicationUserName,  
97 - applicationStoreId = it.ApplicationStoreId,  
98 - applicationTime = it.ApplicationTime,  
99 - amount = it.Amount,  
100 - approveUser = it.ApproveUser,  
101 - approveStatus = it.ApproveStatus,  
102 - approveTime = it.ApproveTime,  
103 - purchaseRecordsId = it.PurchaseRecordsId,  
104 - }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize);  
105 - return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult(data); 211 + list = result,
  212 + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total }
  213 + });
106 } 214 }
107 215
108 /// <summary> 216 /// <summary>
@@ -122,11 +230,99 @@ namespace NCC.Extend.LqReimbursementApplication @@ -122,11 +230,99 @@ namespace NCC.Extend.LqReimbursementApplication
122 //开启事务 230 //开启事务
123 _db.BeginTran(); 231 _db.BeginTran();
124 232
125 - // 保存报销申请表  
126 - var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); 233 + // 1. 验证节点配置
  234 + if (input.nodes == null || input.nodes.Count < 3 || input.nodes.Count > 5)
  235 + {
  236 + throw new Exception("节点数量必须在3-5个之间");
  237 + }
  238 +
  239 + // 验证节点顺序是否连续(1, 2, 3, ...)
  240 + var nodeOrders = input.nodes.Select(n => n.nodeOrder).OrderBy(x => x).ToList();
  241 + for (int i = 0; i < nodeOrders.Count; i++)
  242 + {
  243 + if (nodeOrders[i] != i + 1)
  244 + {
  245 + throw new Exception($"节点顺序必须连续,从1开始");
  246 + }
  247 + }
  248 +
  249 + // 验证每个节点至少有一个审批人
  250 + foreach (var node in input.nodes)
  251 + {
  252 + if (node.approverIds == null || node.approverIds.Count == 0)
  253 + {
  254 + throw new Exception($"节点{node.nodeOrder}({node.nodeName})必须至少指定一个审批人");
  255 + }
  256 + }
  257 +
  258 + // 2. 设置报销申请初始状态
  259 + entity.NodeCount = input.nodes.Count;
  260 + entity.CurrentNodeOrder = 0;
  261 + entity.ApprovalStatus = "待审批";
  262 + entity.ApplicationTime = DateTime.Now;
  263 + if (string.IsNullOrEmpty(entity.ApplicationUserId))
  264 + {
  265 + entity.ApplicationUserId = userInfo.userId;
  266 + }
  267 + if (string.IsNullOrEmpty(entity.ApplicationUserName))
  268 + {
  269 + entity.ApplicationUserName = userInfo.userName;
  270 + }
  271 +
  272 + // 3. 保存报销申请表(不使用IgnoreColumns,确保新字段被保存)
  273 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
127 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); 274 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
128 275
129 - // 更新购买记录的审批单编号和审批状态为"待审批" 276 + // 4. 创建节点配置
  277 + if (input.nodes != null && input.nodes.Count > 0)
  278 + {
  279 + foreach (var nodeConfig in input.nodes)
  280 + {
  281 + var node = new LqReimbursementApplicationNodeEntity
  282 + {
  283 + Id = YitIdHelper.NextId().ToString(),
  284 + ApplicationId = entity.Id,
  285 + NodeOrder = nodeConfig.nodeOrder,
  286 + NodeName = nodeConfig.nodeName,
  287 + ApprovalType = nodeConfig.approvalType ?? "会签",
  288 + IsRequired = 1,
  289 + CreateTime = DateTime.Now
  290 + };
  291 + var nodeResult = await _db.Insertable(node).ExecuteCommandAsync();
  292 + if (nodeResult <= 0)
  293 + {
  294 + throw new Exception($"创建节点{nodeConfig.nodeOrder}失败");
  295 + }
  296 +
  297 + // 5. 创建节点审批人
  298 + if (nodeConfig.approverIds != null && nodeConfig.approverIds.Count > 0)
  299 + {
  300 + for (int i = 0; i < nodeConfig.approverIds.Count; i++)
  301 + {
  302 + var nodeUser = new LqReimbursementApplicationNodeUserEntity
  303 + {
  304 + Id = YitIdHelper.NextId().ToString(),
  305 + ApplicationId = entity.Id,
  306 + NodeId = node.Id,
  307 + NodeOrder = nodeConfig.nodeOrder,
  308 + UserId = nodeConfig.approverIds[i],
  309 + UserName = nodeConfig.approverNames != null && i < nodeConfig.approverNames.Count
  310 + ? nodeConfig.approverNames[i]
  311 + : null,
  312 + SortOrder = i + 1,
  313 + CreateTime = DateTime.Now
  314 + };
  315 + var userResult = await _db.Insertable(nodeUser).ExecuteCommandAsync();
  316 + if (userResult <= 0)
  317 + {
  318 + throw new Exception($"创建节点{nodeConfig.nodeOrder}的审批人{nodeConfig.approverIds[i]}失败");
  319 + }
  320 + }
  321 + }
  322 + }
  323 + }
  324 +
  325 + // 6. 更新购买记录的审批单编号和审批状态为"待审批"
130 if (input.selectedPurchaseRecordIds != null && input.selectedPurchaseRecordIds.Count > 0) 326 if (input.selectedPurchaseRecordIds != null && input.selectedPurchaseRecordIds.Count > 0)
131 { 327 {
132 // 先更新ApplicationId 328 // 先更新ApplicationId
@@ -370,107 +566,582 @@ namespace NCC.Extend.LqReimbursementApplication @@ -370,107 +566,582 @@ namespace NCC.Extend.LqReimbursementApplication
370 } 566 }
371 567
372 /// <summary> 568 /// <summary>
373 - /// 通过审批 569 + /// 提交审批(进入第一个节点)
374 /// </summary> 570 /// </summary>
375 /// <param name="id">申请编号</param> 571 /// <param name="id">申请编号</param>
376 /// <returns></returns> 572 /// <returns></returns>
377 - [HttpPost("{id}/Actions/Approve")]  
378 - public async Task Approve(string id) 573 + [HttpPost("{id}/Actions/SubmitApproval")]
  574 + public async Task SubmitApproval(string id)
379 { 575 {
380 - var userInfo = await _userManager.GetUserInfo();  
381 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id); 576 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id);
382 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); 577 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
383 578
  579 + // 允许"待审批"和"已退回"状态的申请提交审批
  580 + if (entity.CurrentNodeOrder != 0 && entity.ApprovalStatus != "已退回")
  581 + {
  582 + throw new Exception("该申请已经提交审批,不能重复提交");
  583 + }
  584 +
  585 + if (entity.NodeCount == null || entity.NodeCount < 3 || entity.NodeCount > 5)
  586 + {
  587 + throw new Exception("节点配置异常,无法提交审批");
  588 + }
  589 +
384 try 590 try
385 { 591 {
386 - //开启事务  
387 _db.BeginTran(); 592 _db.BeginTran();
388 593
389 - // 更新申请表的审批状态  
390 - entity.ApproveStatus = "已审批";  
391 - entity.ApproveTime = DateTime.Now;  
392 - entity.ApproveUser = userInfo.userId; 594 + // 获取第一个节点
  595 + var firstNode = await _db.Queryable<LqReimbursementApplicationNodeEntity>()
  596 + .Where(x => x.ApplicationId == id && x.NodeOrder == 1)
  597 + .FirstAsync();
  598 +
  599 + if (firstNode == null)
  600 + {
  601 + throw new Exception("未找到第一个审批节点配置");
  602 + }
  603 +
  604 + // 更新报销申请状态
  605 + entity.CurrentNodeOrder = 1;
  606 + entity.CurrentNodeId = firstNode.Id;
  607 + entity.ApprovalStatus = "审批中";
393 await _db.Updateable(entity).ExecuteCommandAsync(); 608 await _db.Updateable(entity).ExecuteCommandAsync();
394 609
395 - // 更新关联的购买记录  
396 - if (!string.IsNullOrEmpty(entity.PurchaseRecordsId)) 610 + // 为第一个节点的每个审批人创建待审批记录
  611 + var firstNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  612 + .Where(x => x.ApplicationId == id && x.NodeOrder == 1)
  613 + .ToListAsync();
  614 +
  615 + foreach (var approver in firstNodeApprovers)
397 { 616 {
398 - var purchaseIds = entity.PurchaseRecordsId.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToList();  
399 - if (purchaseIds.Count > 0) 617 + var record = new LqReimbursementApprovalRecordEntity
400 { 618 {
401 - await _db.Updateable<LqPurchaseRecordsEntity>()  
402 - .SetColumns(it => new LqPurchaseRecordsEntity  
403 - {  
404 - ApproveStatus = "已审批",  
405 - ApproveTime = DateTime.Now,  
406 - ApproveUser = userInfo.userId  
407 - })  
408 - .Where(it => purchaseIds.Contains(it.Id))  
409 - .ExecuteCommandAsync();  
410 - } 619 + Id = YitIdHelper.NextId().ToString(),
  620 + ApplicationId = id,
  621 + NodeId = firstNode.Id,
  622 + NodeOrder = 1,
  623 + ApproverId = approver.UserId,
  624 + ApproverName = approver.UserName,
  625 + ApprovalResult = "待审批",
  626 + IsCurrentNode = 1,
  627 + ApprovalTime = null
  628 + };
  629 + await _db.Insertable(record).ExecuteCommandAsync();
411 } 630 }
412 631
413 - //关闭事务  
414 _db.CommitTran(); 632 _db.CommitTran();
415 } 633 }
416 catch (Exception) 634 catch (Exception)
417 { 635 {
418 - //回滚事务  
419 _db.RollbackTran(); 636 _db.RollbackTran();
420 throw; 637 throw;
421 } 638 }
422 } 639 }
423 640
424 /// <summary> 641 /// <summary>
425 - /// 拒绝审批 642 + /// 审批操作(通过/不通过/退回)
426 /// </summary> 643 /// </summary>
427 /// <param name="id">申请编号</param> 644 /// <param name="id">申请编号</param>
  645 + /// <param name="result">审批结果:通过/不通过/退回</param>
  646 + /// <param name="opinion">审批意见</param>
428 /// <returns></returns> 647 /// <returns></returns>
429 - [HttpPost("{id}/Actions/Reject")]  
430 - public async Task Reject(string id) 648 + [HttpPost("{id}/Actions/Approve")]
  649 + public async Task Approve(string id, [FromQuery] string result, [FromQuery] string opinion = "")
431 { 650 {
432 var userInfo = await _userManager.GetUserInfo(); 651 var userInfo = await _userManager.GetUserInfo();
433 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id); 652 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id);
434 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); 653 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
435 654
  655 + if (entity.CurrentNodeOrder == null || entity.CurrentNodeOrder == 0)
  656 + {
  657 + throw new Exception("该申请尚未提交审批");
  658 + }
  659 +
  660 + if (entity.ApprovalStatus != "审批中")
  661 + {
  662 + throw new Exception($"该申请当前状态为{entity.ApprovalStatus},无法进行审批操作");
  663 + }
  664 +
  665 + // 验证当前用户是否有审批权限(管理员可以审批所有节点)
  666 + var isAdmin = userInfo.isAdministrator;
  667 + if (!isAdmin)
  668 + {
  669 + var hasPermission = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  670 + .Where(x => x.ApplicationId == id
  671 + && x.NodeOrder == entity.CurrentNodeOrder
  672 + && x.UserId == userInfo.userId)
  673 + .AnyAsync();
  674 +
  675 + if (!hasPermission)
  676 + {
  677 + throw new Exception("您没有当前节点的审批权限");
  678 + }
  679 + }
  680 +
  681 + // 检查是否已经审批过
  682 + var hasApproved = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  683 + .Where(x => x.ApplicationId == id
  684 + && x.NodeOrder == entity.CurrentNodeOrder
  685 + && x.ApproverId == userInfo.userId
  686 + && x.ApprovalResult != "待审批")
  687 + .AnyAsync();
  688 +
  689 + if (hasApproved)
  690 + {
  691 + throw new Exception("您已经审批过该节点");
  692 + }
  693 +
436 try 694 try
437 { 695 {
438 - //开启事务  
439 _db.BeginTran(); 696 _db.BeginTran();
440 697
441 - // 更新申请表的审批状态  
442 - entity.ApproveStatus = "未通过";  
443 - entity.ApproveTime = DateTime.Now;  
444 - entity.ApproveUser = userInfo.userId;  
445 - await _db.Updateable(entity).ExecuteCommandAsync(); 698 + // 获取当前节点信息
  699 + var currentNode = await _db.Queryable<LqReimbursementApplicationNodeEntity>()
  700 + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
  701 + .FirstAsync();
  702 +
  703 + if (currentNode == null)
  704 + {
  705 + throw new Exception("未找到当前节点配置");
  706 + }
446 707
447 - // 更新关联的购买记录  
448 - if (!string.IsNullOrEmpty(entity.PurchaseRecordsId)) 708 + // 更新待审批记录为已审批(如果存在待审批记录)
  709 + var existingRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  710 + .Where(x => x.ApplicationId == id
  711 + && x.NodeOrder == entity.CurrentNodeOrder
  712 + && x.ApproverId == userInfo.userId
  713 + && x.ApprovalResult == "待审批")
  714 + .FirstAsync();
  715 +
  716 + if (existingRecord != null)
  717 + {
  718 + // 更新现有记录
  719 + existingRecord.ApprovalResult = result;
  720 + existingRecord.ApprovalOpinion = opinion;
  721 + existingRecord.ApprovalTime = DateTime.Now;
  722 + await _db.Updateable(existingRecord).ExecuteCommandAsync();
  723 + }
  724 + else
449 { 725 {
450 - var purchaseIds = entity.PurchaseRecordsId.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToList();  
451 - if (purchaseIds.Count > 0) 726 + // 创建新记录(如果不存在)
  727 + var approvalRecord = new LqReimbursementApprovalRecordEntity
452 { 728 {
453 - await _db.Updateable<LqPurchaseRecordsEntity>()  
454 - .SetColumns(it => new LqPurchaseRecordsEntity  
455 - {  
456 - ApproveStatus = "未通过",  
457 - ApproveTime = DateTime.Now,  
458 - ApproveUser = userInfo.userId  
459 - })  
460 - .Where(it => purchaseIds.Contains(it.Id)) 729 + Id = YitIdHelper.NextId().ToString(),
  730 + ApplicationId = id,
  731 + NodeId = currentNode.Id,
  732 + NodeOrder = entity.CurrentNodeOrder.Value,
  733 + ApproverId = userInfo.userId,
  734 + ApproverName = userInfo.userName,
  735 + ApprovalResult = result,
  736 + ApprovalOpinion = opinion,
  737 + ApprovalTime = DateTime.Now,
  738 + IsCurrentNode = 1
  739 + };
  740 + await _db.Insertable(approvalRecord).ExecuteCommandAsync();
  741 + }
  742 +
  743 + // 根据审批结果处理
  744 + if (result == "不通过")
  745 + {
  746 + // 不通过:审批结束
  747 + entity.ApprovalStatus = "未通过";
  748 + await _db.Updateable(entity).ExecuteCommandAsync();
  749 +
  750 + // 更新所有购买记录状态为"未通过"(通过ApplicationId关联更新)
  751 + await _db.Updateable<LqPurchaseRecordsEntity>()
  752 + .SetColumns(it => new LqPurchaseRecordsEntity
  753 + {
  754 + ApproveStatus = "未通过",
  755 + ApproveTime = DateTime.Now,
  756 + ApproveUser = userInfo.userId
  757 + })
  758 + .Where(it => it.ApplicationId == id)
  759 + .ExecuteCommandAsync();
  760 + }
  761 + else if (result == "退回")
  762 + {
  763 + // 退回:退回到上一节点
  764 + if (entity.CurrentNodeOrder == 1)
  765 + {
  766 + // 退回到申请人
  767 + entity.CurrentNodeOrder = 0;
  768 + entity.ApprovalStatus = "已退回";
  769 + entity.ReturnedNodeOrder = 0;
  770 + entity.ReturnedReason = opinion;
  771 + entity.CurrentNodeId = null;
  772 + }
  773 + else
  774 + {
  775 + // 退回到上一节点
  776 + entity.CurrentNodeOrder -= 1;
  777 + entity.ReturnedNodeOrder = entity.CurrentNodeOrder;
  778 + entity.ReturnedReason = opinion;
  779 +
  780 + // 获取上一节点信息
  781 + var prevNode = await _db.Queryable<LqReimbursementApplicationNodeEntity>()
  782 + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
  783 + .FirstAsync();
  784 +
  785 + if (prevNode != null)
  786 + {
  787 + entity.CurrentNodeId = prevNode.Id;
  788 + }
  789 +
  790 + // 清除上一节点的所有审批记录(重新审批)
  791 + await _db.Deleteable<LqReimbursementApprovalRecordEntity>()
  792 + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
461 .ExecuteCommandAsync(); 793 .ExecuteCommandAsync();
  794 +
  795 + // 为上一节点的每个审批人创建新的待审批记录
  796 + var prevNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  797 + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
  798 + .ToListAsync();
  799 +
  800 + foreach (var approver in prevNodeApprovers)
  801 + {
  802 + var record = new LqReimbursementApprovalRecordEntity
  803 + {
  804 + Id = YitIdHelper.NextId().ToString(),
  805 + ApplicationId = id,
  806 + NodeId = prevNode.Id,
  807 + NodeOrder = entity.CurrentNodeOrder.Value,
  808 + ApproverId = approver.UserId,
  809 + ApproverName = approver.UserName,
  810 + ApprovalResult = "待审批",
  811 + IsCurrentNode = 1,
  812 + ApprovalTime = null
  813 + };
  814 + await _db.Insertable(record).ExecuteCommandAsync();
  815 + }
  816 + }
  817 +
  818 + await _db.Updateable(entity).ExecuteCommandAsync();
  819 + }
  820 + else if (result == "通过")
  821 + {
  822 + // 通过:判断是否需要进入下一个节点
  823 + bool shouldMoveToNext = false;
  824 +
  825 + if (currentNode.ApprovalType == "或签")
  826 + {
  827 + // 或签:任意一个审批人通过,立即进入下一个节点
  828 + shouldMoveToNext = true;
  829 + }
  830 + else // 会签
  831 + {
  832 + // 会签:检查是否所有审批人都已通过
  833 + var approvers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  834 + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder)
  835 + .Select(x => x.UserId)
  836 + .ToListAsync();
  837 +
  838 + var approvedUsers = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  839 + .Where(x => x.ApplicationId == id
  840 + && x.NodeOrder == entity.CurrentNodeOrder
  841 + && x.ApprovalResult == "通过")
  842 + .Select(x => x.ApproverId)
  843 + .Distinct()
  844 + .ToListAsync();
  845 +
  846 + if (approvers.Count == approvedUsers.Count)
  847 + {
  848 + // 所有人都已通过
  849 + shouldMoveToNext = true;
  850 + }
  851 + }
  852 +
  853 + if (shouldMoveToNext)
  854 + {
  855 + // 进入下一个节点或完成审批
  856 + await MoveToNextNode(id, userInfo.userId);
462 } 857 }
463 } 858 }
464 859
465 - //关闭事务  
466 _db.CommitTran(); 860 _db.CommitTran();
467 } 861 }
468 catch (Exception) 862 catch (Exception)
469 { 863 {
470 - //回滚事务  
471 _db.RollbackTran(); 864 _db.RollbackTran();
472 throw; 865 throw;
473 } 866 }
474 } 867 }
  868 +
  869 + /// <summary>
  870 + /// 进入下一个节点
  871 + /// </summary>
  872 + private async Task MoveToNextNode(string applicationId, string approveUserId)
  873 + {
  874 + var entity = await _db.Queryable<LqReimbursementApplicationEntity>()
  875 + .FirstAsync(x => x.Id == applicationId);
  876 +
  877 + // 判断是否是最后一个节点
  878 + if (entity.CurrentNodeOrder >= entity.NodeCount)
  879 + {
  880 + // 审批完成
  881 + entity.CurrentNodeOrder = entity.NodeCount + 1;
  882 + entity.ApprovalStatus = "已通过";
  883 + entity.CurrentNodeId = null;
  884 + await _db.Updateable(entity).ExecuteCommandAsync();
  885 +
  886 + // 更新所有购买记录状态为"已审批"
  887 + // 通过ApplicationId关联更新,因为PurchaseRecordsId可能为空
  888 + await _db.Updateable<LqPurchaseRecordsEntity>()
  889 + .SetColumns(it => new LqPurchaseRecordsEntity
  890 + {
  891 + ApproveStatus = "已审批",
  892 + ApproveTime = DateTime.Now,
  893 + ApproveUser = approveUserId
  894 + })
  895 + .Where(it => it.ApplicationId == applicationId)
  896 + .ExecuteCommandAsync();
  897 + }
  898 + else
  899 + {
  900 + // 进入下一个节点
  901 + entity.CurrentNodeOrder += 1;
  902 +
  903 + // 获取下一个节点信息
  904 + var nextNode = await _db.Queryable<LqReimbursementApplicationNodeEntity>()
  905 + .Where(x => x.ApplicationId == applicationId && x.NodeOrder == entity.CurrentNodeOrder)
  906 + .FirstAsync();
  907 +
  908 + if (nextNode == null)
  909 + {
  910 + throw new Exception($"未找到节点{entity.CurrentNodeOrder}的配置");
  911 + }
  912 +
  913 + entity.CurrentNodeId = nextNode.Id;
  914 + entity.ApprovalStatus = "审批中";
  915 + await _db.Updateable(entity).ExecuteCommandAsync();
  916 +
  917 + // 清除之前的当前节点标记
  918 + await _db.Updateable<LqReimbursementApprovalRecordEntity>()
  919 + .SetColumns(it => new LqReimbursementApprovalRecordEntity { IsCurrentNode = 0 })
  920 + .Where(it => it.ApplicationId == applicationId)
  921 + .ExecuteCommandAsync();
  922 +
  923 + // 为下一个节点的每个审批人创建待审批记录
  924 + var nextNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  925 + .Where(x => x.ApplicationId == applicationId && x.NodeOrder == entity.CurrentNodeOrder)
  926 + .ToListAsync();
  927 +
  928 + foreach (var approver in nextNodeApprovers)
  929 + {
  930 + var record = new LqReimbursementApprovalRecordEntity
  931 + {
  932 + Id = YitIdHelper.NextId().ToString(),
  933 + ApplicationId = applicationId,
  934 + NodeId = nextNode.Id,
  935 + NodeOrder = entity.CurrentNodeOrder.Value,
  936 + ApproverId = approver.UserId,
  937 + ApproverName = approver.UserName,
  938 + ApprovalResult = "待审批",
  939 + IsCurrentNode = 1,
  940 + ApprovalTime = null
  941 + };
  942 + await _db.Insertable(record).ExecuteCommandAsync();
  943 + }
  944 + }
  945 + }
  946 +
  947 + /// <summary>
  948 + /// 获取所有待办列表(管理员用,所有待审批的申请)
  949 + /// </summary>
  950 + /// <param name="input">查询参数</param>
  951 + /// <returns></returns>
  952 + [HttpGet("Actions/AllPendingApproval")]
  953 + public async Task<dynamic> GetAllPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input)
  954 + {
  955 + var sidx = input.sidx == null ? "id" : input.sidx;
  956 +
  957 + // 管理员可以查看所有待审批的申请
  958 + var query = _db.Queryable<LqReimbursementApplicationEntity>()
  959 + .Where(x => x.ApprovalStatus == "审批中")
  960 + .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId))
  961 + .OrderBy(sidx + " " + input.sort);
  962 +
  963 + var total = await query.CountAsync();
  964 + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize);
  965 +
  966 + // 获取当前审批人信息
  967 + var applicationIds = entities.Select(x => x.Id).ToList();
  968 + var currentApprovers = new List<dynamic>();
  969 + if (applicationIds.Any())
  970 + {
  971 + var approverList = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  972 + .Where(x => applicationIds.Contains(x.ApplicationId))
  973 + .InnerJoin<LqReimbursementApplicationEntity>((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder)
  974 + .Select((u, a) => new
  975 + {
  976 + applicationId = a.Id,
  977 + approverName = u.UserName
  978 + })
  979 + .ToListAsync();
  980 + currentApprovers = approverList.Cast<dynamic>().ToList();
  981 + }
  982 +
  983 + var approverDict = currentApprovers
  984 + .GroupBy(x => (string)x.applicationId)
  985 + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName)));
  986 +
  987 + // 组装返回数据
  988 + var result = entities.Select(item => new LqReimbursementApplicationListOutput
  989 + {
  990 + id = item.Id,
  991 + applicationUserId = item.ApplicationUserId,
  992 + applicationUserName = item.ApplicationUserName,
  993 + applicationStoreId = item.ApplicationStoreId,
  994 + applicationTime = item.ApplicationTime,
  995 + amount = item.Amount,
  996 + approveUser = item.ApproveUser,
  997 + approveStatus = item.ApprovalStatus ?? item.ApproveStatus,
  998 + approveTime = item.ApproveTime,
  999 + purchaseRecordsId = item.PurchaseRecordsId,
  1000 + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null,
  1001 + currentNodeOrder = item.CurrentNodeOrder,
  1002 + nodeCount = item.NodeCount
  1003 + }).ToList();
  1004 +
  1005 + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult(
  1006 + new SqlSugarPagedList<LqReimbursementApplicationListOutput>
  1007 + {
  1008 + list = result,
  1009 + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total }
  1010 + });
  1011 + }
  1012 +
  1013 + /// <summary>
  1014 + /// 获取待审批列表(当前用户作为审批人的申请)
  1015 + /// </summary>
  1016 + /// <param name="input">查询参数</param>
  1017 + /// <returns></returns>
  1018 + [HttpGet("Actions/PendingApproval")]
  1019 + public async Task<dynamic> GetPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input)
  1020 + {
  1021 + var userInfo = await _userManager.GetUserInfo();
  1022 + var sidx = input.sidx == null ? "id" : input.sidx;
  1023 +
  1024 + // 查询当前用户作为审批人的节点
  1025 + var userNodeOrders = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  1026 + .Where(x => x.UserId == userInfo.userId)
  1027 + .Select(x => new { x.ApplicationId, x.NodeOrder })
  1028 + .ToListAsync();
  1029 +
  1030 + if (!userNodeOrders.Any())
  1031 + {
  1032 + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult(
  1033 + new SqlSugarPagedList<LqReimbursementApplicationListOutput>
  1034 + {
  1035 + list = new List<LqReimbursementApplicationListOutput>(),
  1036 + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = 0 }
  1037 + });
  1038 + }
  1039 +
  1040 + // 获取用户有权限的申请ID和节点顺序
  1041 + var userApplications = userNodeOrders
  1042 + .GroupBy(x => x.ApplicationId)
  1043 + .ToDictionary(g => g.Key, g => g.Select(x => x.NodeOrder).ToList());
  1044 +
  1045 + var applicationIds = userApplications.Keys.ToList();
  1046 +
  1047 + // 查询这些申请中,当前节点是用户有权限的节点,且状态为"审批中"的申请
  1048 + var query = _db.Queryable<LqReimbursementApplicationEntity>()
  1049 + .Where(x => applicationIds.Contains(x.Id) && x.ApprovalStatus == "审批中")
  1050 + .Where(x => userApplications.ContainsKey(x.Id)
  1051 + && userApplications[x.Id].Contains(x.CurrentNodeOrder ?? 0))
  1052 + .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId))
  1053 + .OrderBy(sidx + " " + input.sort);
  1054 +
  1055 + var total = await query.CountAsync();
  1056 + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize);
  1057 +
  1058 + // 获取当前审批人信息
  1059 + var resultApplicationIds = entities.Select(x => x.Id).ToList();
  1060 + var currentApprovers = new List<dynamic>();
  1061 + if (resultApplicationIds.Any())
  1062 + {
  1063 + var approverList = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>()
  1064 + .Where(x => resultApplicationIds.Contains(x.ApplicationId))
  1065 + .InnerJoin<LqReimbursementApplicationEntity>((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder)
  1066 + .Select((u, a) => new
  1067 + {
  1068 + applicationId = a.Id,
  1069 + approverName = u.UserName
  1070 + })
  1071 + .ToListAsync();
  1072 + currentApprovers = approverList.Cast<dynamic>().ToList();
  1073 + }
  1074 +
  1075 + var approverDict = currentApprovers
  1076 + .GroupBy(x => (string)x.applicationId)
  1077 + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName)));
  1078 +
  1079 + // 组装返回数据
  1080 + var result = entities.Select(item => new LqReimbursementApplicationListOutput
  1081 + {
  1082 + id = item.Id,
  1083 + applicationUserId = item.ApplicationUserId,
  1084 + applicationUserName = item.ApplicationUserName,
  1085 + applicationStoreId = item.ApplicationStoreId,
  1086 + applicationTime = item.ApplicationTime,
  1087 + amount = item.Amount,
  1088 + approveUser = item.ApproveUser,
  1089 + approveStatus = item.ApprovalStatus ?? item.ApproveStatus,
  1090 + approveTime = item.ApproveTime,
  1091 + purchaseRecordsId = item.PurchaseRecordsId,
  1092 + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null,
  1093 + currentNodeOrder = item.CurrentNodeOrder,
  1094 + nodeCount = item.NodeCount
  1095 + }).ToList();
  1096 +
  1097 + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult(
  1098 + new SqlSugarPagedList<LqReimbursementApplicationListOutput>
  1099 + {
  1100 + list = result,
  1101 + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total }
  1102 + });
  1103 + }
  1104 +
  1105 + /// <summary>
  1106 + /// 获取审批历史
  1107 + /// </summary>
  1108 + /// <param name="id">申请编号</param>
  1109 + /// <returns></returns>
  1110 + [HttpGet("{id}/Actions/ApprovalHistory")]
  1111 + public async Task<dynamic> GetApprovalHistory(string id)
  1112 + {
  1113 + // 先查询审批记录
  1114 + var approvalRecords = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
  1115 + .Where(x => x.ApplicationId == id)
  1116 + .OrderBy(x => x.NodeOrder)
  1117 + .OrderBy(x => x.ApprovalTime)
  1118 + .ToListAsync();
  1119 +
  1120 + // 获取节点信息
  1121 + if (approvalRecords.Any())
  1122 + {
  1123 + var nodeIds = approvalRecords.Select(x => x.NodeId).Distinct().ToList();
  1124 + var nodes = await _db.Queryable<LqReimbursementApplicationNodeEntity>()
  1125 + .Where(x => nodeIds.Contains(x.Id))
  1126 + .ToListAsync();
  1127 +
  1128 + var nodeDict = nodes.ToDictionary(x => x.Id, x => x.NodeName);
  1129 +
  1130 + // 组装结果
  1131 + var records = approvalRecords.Select(r => new
  1132 + {
  1133 + nodeOrder = r.NodeOrder,
  1134 + nodeName = nodeDict.ContainsKey(r.NodeId) ? nodeDict[r.NodeId] : null,
  1135 + approverName = r.ApproverName,
  1136 + approvalResult = r.ApprovalResult,
  1137 + approvalOpinion = r.ApprovalOpinion,
  1138 + approvalTime = r.ApprovalTime
  1139 + }).ToList();
  1140 +
  1141 + return records;
  1142 + }
  1143 +
  1144 + return new List<object>();
  1145 + }
475 } 1146 }
476 } 1147 }
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
@@ -47,10 +47,11 @@ namespace NCC.Extend @@ -47,10 +47,11 @@ namespace NCC.Extend
47 /// <param name="input">查询参数</param> 47 /// <param name="input">查询参数</param>
48 /// <returns>健康师工资额外计算分页列表</returns> 48 /// <returns>健康师工资额外计算分页列表</returns>
49 [HttpGet] 49 [HttpGet]
50 - public async Task<PageResult<SalaryExtraCalculationOutput>> GetList([FromQuery] SalaryExtraCalculationInput input) 50 + public async Task<dynamic> GetList([FromQuery] SalaryExtraCalculationInput input)
51 { 51 {
52 - var query = _db.Queryable<LqSalaryExtraCalculationEntity>()  
53 - .LeftJoin<UserEntity>((ec, u) => ec.EmployeeId == u.Id); 52 + var query = _db.Queryable<LqSalaryExtraCalculationEntity, UserEntity>((ec, u) => new JoinQueryInfos(
  53 + JoinType.Left, u.Id == SqlFunc.ToString(ec.EmployeeId)))
  54 + .Where((ec, u) => u.Id != null && (u.DeleteMark == null || u.DeleteMark == 0));
54 55
55 // 年份筛选 56 // 年份筛选
56 if (input.Year.HasValue) 57 if (input.Year.HasValue)
@@ -76,7 +77,7 @@ namespace NCC.Extend @@ -76,7 +77,7 @@ namespace NCC.Extend
76 query = query.Where((ec, u) => u.RealName.Contains(input.Keyword) || u.MobilePhone.Contains(input.Keyword)); 77 query = query.Where((ec, u) => u.RealName.Contains(input.Keyword) || u.MobilePhone.Contains(input.Keyword));
77 } 78 }
78 79
79 - var list = await query.Select((ec, u) => new SalaryExtraCalculationOutput 80 + var data = await query.Select((ec, u) => new SalaryExtraCalculationOutput
80 { 81 {
81 Id = ec.Id, 82 Id = ec.Id,
82 EmployeeId = ec.EmployeeId, 83 EmployeeId = ec.EmployeeId,
@@ -94,9 +95,11 @@ namespace NCC.Extend @@ -94,9 +95,11 @@ namespace NCC.Extend
94 OtherPerformanceAdd = ec.OtherPerformanceAdd, 95 OtherPerformanceAdd = ec.OtherPerformanceAdd,
95 OtherPerformanceSubtract = ec.OtherPerformanceSubtract 96 OtherPerformanceSubtract = ec.OtherPerformanceSubtract
96 }) 97 })
  98 + .MergeTable()
  99 + .OrderByIF(!string.IsNullOrEmpty(input.sidx), input.sidx + " " + input.sort)
97 .ToPagedListAsync(input.currentPage, input.pageSize); 100 .ToPagedListAsync(input.currentPage, input.pageSize);
98 101
99 - return PageResult<SalaryExtraCalculationOutput>.SqlSugarPageResult(list); 102 + return PageResult<SalaryExtraCalculationOutput>.SqlSugarPageResult(data);
100 } 103 }
101 104
102 /// <summary> 105 /// <summary>
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
@@ -30,9 +30,9 @@ using System.Threading.Tasks; @@ -30,9 +30,9 @@ using System.Threading.Tasks;
30 namespace NCC.Extend 30 namespace NCC.Extend
31 { 31 {
32 /// <summary> 32 /// <summary>
33 - /// 薪酬服务 33 + /// 健康师薪酬服务
34 /// </summary> 34 /// </summary>
35 - [ApiDescriptionSettings(Tag = "薪酬服务", Name = "LqSalary", Order = 300)] 35 + [ApiDescriptionSettings(Tag = "健康师薪酬服务", Name = "LqSalary", Order = 300)]
36 [Route("api/Extend/[controller]")] 36 [Route("api/Extend/[controller]")]
37 public class LqSalaryService : IDynamicApiController, ITransient 37 public class LqSalaryService : IDynamicApiController, ITransient
38 { 38 {
@@ -52,7 +52,7 @@ namespace NCC.Extend @@ -52,7 +52,7 @@ namespace NCC.Extend
52 /// <param name="input">查询参数</param> 52 /// <param name="input">查询参数</param>
53 /// <returns>健康师工资分页列表</returns> 53 /// <returns>健康师工资分页列表</returns>
54 [HttpGet("health-coach")] 54 [HttpGet("health-coach")]
55 - public async Task<PageResult<HealthCoachSalaryOutput>> GetHealthCoachSalaryList([FromQuery] HealthCoachSalaryInput input) 55 + public async Task<dynamic> GetHealthCoachSalaryList([FromQuery] HealthCoachSalaryInput input)
56 { 56 {
57 var monthStr = $"{input.Year}{input.Month:D2}"; 57 var monthStr = $"{input.Year}{input.Month:D2}";
58 58
@@ -83,40 +83,93 @@ namespace NCC.Extend @@ -83,40 +83,93 @@ namespace NCC.Extend
83 var list = await query.Select(x => new HealthCoachSalaryOutput 83 var list = await query.Select(x => new HealthCoachSalaryOutput
84 { 84 {
85 Id = x.Id, 85 Id = x.Id,
  86 + StoreId = x.StoreId,
86 StoreName = x.StoreName, 87 StoreName = x.StoreName,
  88 + EmployeeId = x.EmployeeId,
87 EmployeeName = x.EmployeeName, 89 EmployeeName = x.EmployeeName,
88 Position = x.Position, 90 Position = x.Position,
  91 + GoldTriangleId = x.GoldTriangleId,
89 GoldTriangleTeam = x.GoldTriangleTeam, 92 GoldTriangleTeam = x.GoldTriangleTeam,
90 TotalPerformance = x.TotalPerformance, 93 TotalPerformance = x.TotalPerformance,
91 BasePerformance = x.BasePerformance, 94 BasePerformance = x.BasePerformance,
92 CooperationPerformance = x.CooperationPerformance, 95 CooperationPerformance = x.CooperationPerformance,
  96 + BaseRewardPerformance = x.BaseRewardPerformance,
  97 + CooperationRewardPerformance = x.CooperationRewardPerformance,
  98 + ActualBasePerformance = x.ActualBasePerformance,
  99 + ActualCooperationPerformance = x.ActualCooperationPerformance,
93 RewardPerformance = x.RewardPerformance, 100 RewardPerformance = x.RewardPerformance,
  101 + StoreTotalPerformance = x.StoreTotalPerformance,
  102 + TeamPerformance = x.TeamPerformance,
  103 + Percentage = x.Percentage,
  104 + NewCustomerPerformance = x.NewCustomerPerformance,
  105 + NewCustomerConversionRate = x.NewCustomerConversionRate,
  106 + NewCustomerPoint = x.NewCustomerPoint,
  107 + UpgradeCustomerCount = x.UpgradeCustomerCount,
  108 + UpgradePerformance = x.UpgradePerformance,
  109 + UpgradePoint = x.UpgradePoint,
  110 + NewCustomerCommission = x.NewCustomerPerformanceCommission,
  111 + UpgradeCommission = x.UpgradePerformanceCommission,
  112 + OtherPerformanceAdd = x.OtherPerformanceAdd,
  113 + OtherPerformanceSubtract = x.OtherPerformanceSubtract,
94 Consumption = x.Consumption, 114 Consumption = x.Consumption,
95 ProjectCount = x.ProjectCount, 115 ProjectCount = x.ProjectCount,
96 CustomerCount = x.CustomerCount, 116 CustomerCount = x.CustomerCount,
97 WorkingDays = x.WorkingDays, 117 WorkingDays = x.WorkingDays,
98 - HealthCoachBaseSalary = x.HealthCoachBaseSalary, 118 + LeaveDays = x.LeaveDays,
  119 + CommissionPoint = x.CommissionPoint,
  120 + BasePerformanceCommission = x.BasePerformanceCommission,
  121 + CooperationPerformanceCommission = x.CooperationPerformanceCommission,
  122 + ConsultantCommission = x.ConsultantCommission,
  123 + StoreTZoneCommission = x.StoreTZoneCommission,
99 TotalCommission = x.TotalCommission, 124 TotalCommission = x.TotalCommission,
  125 + HealthCoachBaseSalary = x.HealthCoachBaseSalary,
100 HandworkFee = x.HandworkFee, 126 HandworkFee = x.HandworkFee,
  127 + OutherHandworkFee = x.OutherHandworkFee,
  128 + TransportationAllowance = x.TransportationAllowance,
  129 + LessRest = x.LessRest,
  130 + FullAttendance = x.FullAttendance,
  131 + CalculatedGrossSalary = x.CalculatedGrossSalary,
  132 + GuaranteedSalary = x.GuaranteedSalary,
  133 + GuaranteedLeaveDeduction = x.GuaranteedLeaveDeduction,
  134 + GuaranteedBaseSalary = x.GuaranteedBaseSalary,
  135 + GuaranteedSupplement = x.GuaranteedSupplement,
  136 + FinalGrossSalary = x.FinalGrossSalary,
  137 + MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy,
  138 + MonthlyTransportSubsidy = x.MonthlyTransportSubsidy,
  139 + LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy,
  140 + LastMonthTransportSubsidy = x.LastMonthTransportSubsidy,
101 TotalSubsidy = x.TotalSubsidy, 141 TotalSubsidy = x.TotalSubsidy,
  142 + MissingCard = x.MissingCard,
  143 + LateArrival = x.LateArrival,
  144 + LeaveDeduction = x.LeaveDeduction,
  145 + SocialInsuranceDeduction = x.SocialInsuranceDeduction,
  146 + RewardDeduction = x.RewardDeduction,
  147 + AccommodationDeduction = x.AccommodationDeduction,
  148 + StudyPeriodDeduction = x.StudyPeriodDeduction,
  149 + WorkClothesDeduction = x.WorkClothesDeduction,
102 TotalDeduction = x.TotalDeduction, 150 TotalDeduction = x.TotalDeduction,
  151 + Bonus = x.Bonus,
  152 + ReturnPhoneDeposit = x.ReturnPhoneDeposit,
  153 + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
103 ActualSalary = x.ActualSalary, 154 ActualSalary = x.ActualSalary,
  155 + MonthlyPaymentStatus = x.MonthlyPaymentStatus,
  156 + PaidAmount = x.PaidAmount,
  157 + PendingAmount = x.PendingAmount,
  158 + LastMonthSupplement = x.LastMonthSupplement,
  159 + MonthlyTotalPayment = x.MonthlyTotalPayment,
  160 + StatisticsMonth = x.StatisticsMonth,
104 IsLocked = x.IsLocked, 161 IsLocked = x.IsLocked,
  162 + CreateTime = x.CreateTime,
  163 + CreateUser = x.CreateUser,
105 UpdateTime = x.UpdateTime, 164 UpdateTime = x.UpdateTime,
  165 + UpdateUser = x.UpdateUser,
106 IsNewStore = x.IsNewStore, 166 IsNewStore = x.IsNewStore,
107 NewStoreProtectionStage = x.NewStoreProtectionStage, 167 NewStoreProtectionStage = x.NewStoreProtectionStage,
108 StoreType = x.StoreType, 168 StoreType = x.StoreType,
109 StoreCategory = x.StoreCategory, 169 StoreCategory = x.StoreCategory,
110 - ActualBasePerformance = x.ActualBasePerformance,  
111 - ActualCooperationPerformance = x.ActualCooperationPerformance,  
112 - NewCustomerCommission = x.NewCustomerPerformanceCommission,  
113 - UpgradeCommission = x.UpgradePerformanceCommission,  
114 - NewCustomerPerformance = x.NewCustomerPerformance,  
115 - NewCustomerConversionRate = x.NewCustomerConversionRate,  
116 - NewCustomerPoint = x.NewCustomerPoint,  
117 - UpgradeCustomerCount = x.UpgradeCustomerCount,  
118 - UpgradePerformance = x.UpgradePerformance,  
119 - UpgradePoint = x.UpgradePoint 170 + DailyAverageConsumption = x.DailyAverageConsumption,
  171 + DailyAverageProjectCount = x.DailyAverageProjectCount,
  172 + TeamTotalConsumption = x.TeamTotalConsumption
120 }) 173 })
121 .ToPagedListAsync(input.currentPage, input.pageSize); 174 .ToPagedListAsync(input.currentPage, input.pageSize);
122 175
@@ -360,8 +413,8 @@ namespace NCC.Extend @@ -360,8 +413,8 @@ namespace NCC.Extend
360 salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0")); 413 salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0"));
361 414
362 // 新客与升单业绩 415 // 新客与升单业绩
363 - salary.NewCustomerPerformance = myPerf.Where(x => x.Sfskdd == "是").Sum(x => decimal.Parse(x.Jksyj ?? "0"));  
364 - salary.UpgradePerformance = myPerf.Where(x => x.Sfskdd == "否").Sum(x => decimal.Parse(x.Jksyj ?? "0")); 416 + salary.NewCustomerPerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "是")).Sum(x => decimal.Parse(x.Jksyj ?? "0"));
  417 + salary.UpgradePerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "否")).Sum(x => decimal.Parse(x.Jksyj ?? "0"));
365 418
366 // 2.1.1 填充额外计算数据 419 // 2.1.1 填充额外计算数据
367 if (extraCalculationDict.ContainsKey(empId)) 420 if (extraCalculationDict.ContainsKey(empId))
@@ -419,6 +472,19 @@ namespace NCC.Extend @@ -419,6 +472,19 @@ namespace NCC.Extend
419 salary.WorkingDays = myAtt?.WorkDays ?? 0; 472 salary.WorkingDays = myAtt?.WorkDays ?? 0;
420 salary.LeaveDays = myAtt?.LeaveDays ?? 0; 473 salary.LeaveDays = myAtt?.LeaveDays ?? 0;
421 474
  475 + // 计算日均消耗和日均项目数 (用于底薪计算)
  476 + // 逻辑: 总消耗/在店天数, 总项目数/在店天数
  477 + if (salary.WorkingDays > 0)
  478 + {
  479 + salary.DailyAverageConsumption = salary.Consumption / salary.WorkingDays;
  480 + salary.DailyAverageProjectCount = salary.ProjectCount / salary.WorkingDays;
  481 + }
  482 + else
  483 + {
  484 + salary.DailyAverageConsumption = 0;
  485 + salary.DailyAverageProjectCount = 0;
  486 + }
  487 +
422 // 2.4 到店人头 488 // 2.4 到店人头
423 var myHeadcount = headcountList.FirstOrDefault(x => x.PersonId == empId); 489 var myHeadcount = headcountList.FirstOrDefault(x => x.PersonId == empId);
424 salary.CustomerCount = myHeadcount?.Count ?? 0; 490 salary.CustomerCount = myHeadcount?.Count ?? 0;
@@ -458,7 +524,7 @@ namespace NCC.Extend @@ -458,7 +524,7 @@ namespace NCC.Extend
458 } 524 }
459 525
460 // 3. 处理战队逻辑 (考勤规则) 526 // 3. 处理战队逻辑 (考勤规则)
461 - // 规则:若出勤天数 < 21天,则该健康师不计入战队,按单人计算。 527 + // 规则:若出勤天数 < 20天,则该健康师不计入战队,按单人计算。
462 528
463 // 按战队分组 529 // 按战队分组
464 var teamGroups = employeeStats.Values 530 var teamGroups = employeeStats.Values
@@ -473,7 +539,7 @@ namespace NCC.Extend @@ -473,7 +539,7 @@ namespace NCC.Extend
473 539
474 foreach (var member in group) 540 foreach (var member in group)
475 { 541 {
476 - if (member.WorkingDays >= 21) 542 + if (member.WorkingDays >= 20)
477 { 543 {
478 validMembers.Add(member); 544 validMembers.Add(member);
479 } 545 }
@@ -491,13 +557,15 @@ namespace NCC.Extend @@ -491,13 +557,15 @@ namespace NCC.Extend
491 member.Position = "健康师"; // 降级为健康师 557 member.Position = "健康师"; // 降级为健康师
492 } 558 }
493 559
494 - // 计算有效战队的总业绩 560 + // 计算有效战队的总业绩和总消耗
495 var teamTotalPerformance = validMembers.Sum(x => x.TotalPerformance); 561 var teamTotalPerformance = validMembers.Sum(x => x.TotalPerformance);
  562 + var teamTotalConsumption = validMembers.Sum(x => x.Consumption);
496 563
497 - // 更新有效成员的战队业绩 564 + // 更新有效成员的战队业绩和战队总消耗
498 foreach (var member in validMembers) 565 foreach (var member in validMembers)
499 { 566 {
500 member.TeamPerformance = teamTotalPerformance; 567 member.TeamPerformance = teamTotalPerformance;
  568 + member.TeamTotalConsumption = teamTotalConsumption;
501 } 569 }
502 } 570 }
503 571
@@ -518,11 +586,18 @@ namespace NCC.Extend @@ -518,11 +586,18 @@ namespace NCC.Extend
518 int newStoreStage = salary.NewStoreProtectionStage; 586 int newStoreStage = salary.NewStoreProtectionStage;
519 587
520 // 4.1 底薪计算 588 // 4.1 底薪计算
521 - salary.HealthCoachBaseSalary = CalculateBaseSalary(salary.Consumption, salary.ProjectCount, isNewStore); 589 + // 4.1 底薪计算
  590 + // 传入当月天数用于计算标准日均
  591 + int daysInMonth = DateTime.DaysInMonth(year, month);
  592 + salary.HealthCoachBaseSalary = CalculateBaseSalary(
  593 + salary.DailyAverageConsumption,
  594 + salary.DailyAverageProjectCount,
  595 + daysInMonth,
  596 + isNewStore);
522 597
523 // 4.2 提成计算 598 // 4.2 提成计算
524 - // 业绩门槛: 个人总业绩 <= 6000 无提成  
525 - if (salary.TotalPerformance <= 6000) 599 + // 业绩门槛: 战队成员个人总业绩 <= 6000 无提成
  600 + if (!string.IsNullOrEmpty(salary.GoldTriangleId) && salary.TotalPerformance <= 6000)
526 { 601 {
527 salary.TotalCommission = 0; 602 salary.TotalCommission = 0;
528 salary.BasePerformanceCommission = 0; 603 salary.BasePerformanceCommission = 0;
@@ -564,17 +639,17 @@ namespace NCC.Extend @@ -564,17 +639,17 @@ namespace NCC.Extend
564 { 639 {
565 if (newStoreStage == 1) 640 if (newStoreStage == 1)
566 { 641 {
567 - // 第一阶段:计算新客转化率提成 642 + // 第一阶段:计算新客转化率提成 (需乘以0.95)
568 salary.NewCustomerPerformanceCommission = CalculateNewCustomerConversionCommission( 643 salary.NewCustomerPerformanceCommission = CalculateNewCustomerConversionCommission(
569 salary.NewCustomerPerformance, 644 salary.NewCustomerPerformance,
570 - salary.NewCustomerConversionRate); 645 + salary.NewCustomerConversionRate) * 0.95m;
571 } 646 }
572 else if (newStoreStage == 2) 647 else if (newStoreStage == 2)
573 { 648 {
574 - // 第二阶段:计算升单人头提成 649 + // 第二阶段:计算升单人头提成 (需乘以0.95)
575 salary.UpgradePerformanceCommission = CalculateUpgradeCustomerCommission( 650 salary.UpgradePerformanceCommission = CalculateUpgradeCustomerCommission(
576 salary.UpgradePerformance, 651 salary.UpgradePerformance,
577 - salary.UpgradeCustomerCount); 652 + salary.UpgradeCustomerCount) * 0.95m;
578 } 653 }
579 // 第三阶段:不计算新客/升单提成 654 // 第三阶段:不计算新客/升单提成
580 } 655 }
@@ -590,11 +665,19 @@ namespace NCC.Extend @@ -590,11 +665,19 @@ namespace NCC.Extend
590 isNewStore); 665 isNewStore);
591 } 666 }
592 667
  668 + // 计算门店T区提成
  669 + // 规则:姓名包含"T区" -> 门店总业绩 * 0.05 * 0.05
  670 + if (!string.IsNullOrEmpty(salary.EmployeeName) && salary.EmployeeName.Contains("T区"))
  671 + {
  672 + salary.StoreTZoneCommission = salary.StoreTotalPerformance * 0.05m * 0.05m;
  673 + }
  674 +
593 salary.TotalCommission = salary.BasePerformanceCommission 675 salary.TotalCommission = salary.BasePerformanceCommission
594 + salary.CooperationPerformanceCommission 676 + salary.CooperationPerformanceCommission
595 + salary.ConsultantCommission 677 + salary.ConsultantCommission
596 + salary.NewCustomerPerformanceCommission 678 + salary.NewCustomerPerformanceCommission
597 - + salary.UpgradePerformanceCommission; 679 + + salary.UpgradePerformanceCommission
  680 + + salary.StoreTZoneCommission;
598 } 681 }
599 682
600 // 计算占比 683 // 计算占比
@@ -623,25 +706,39 @@ namespace NCC.Extend @@ -623,25 +706,39 @@ namespace NCC.Extend
623 /// <summary> 706 /// <summary>
624 /// 计算底薪 707 /// 计算底薪
625 /// </summary> 708 /// </summary>
626 - private decimal CalculateBaseSalary(decimal consumption, decimal projectCount, bool isNewStore) 709 + private decimal CalculateBaseSalary(decimal dailyAvgConsumption, decimal dailyAvgProjectCount, int daysInMonth, bool isNewStore)
627 { 710 {
628 - // 0星:<1w 或 <96个 -> 1800  
629 - // 1星:>=1w 且 >=96个 -> 2000  
630 - // 2星:>=2w 且 >=126个 -> 2200  
631 - // 3星:>=4w 且 >=156个 -> 2400 711 + // 规则调整:按日均计算
  712 + // 一星:月消耗 10000 / 当月天数,项目数 96 / 当月天数
  713 + // 二星:月消耗 20000 / 当月天数,项目数 126 / 当月天数
  714 + // 三星:月消耗 40000 / 当月天数,项目数 156 / 当月天数
  715 +
  716 + // 计算各星级日均标准
  717 + decimal consStar1 = 10000m / daysInMonth;
  718 + decimal consStar2 = 20000m / daysInMonth;
  719 + decimal consStar3 = 40000m / daysInMonth;
  720 +
  721 + decimal projStar1 = 96m / daysInMonth;
  722 + decimal projStar2 = 126m / daysInMonth;
  723 + decimal projStar3 = 156m / daysInMonth;
  724 +
  725 + // 0星:未达标 -> 1800
  726 + // 1星:>=1w标准 且 >=96个标准 -> 2000
  727 + // 2星:>=2w标准 且 >=126个标准 -> 2200
  728 + // 3星:>=4w标准 且 >=156个标准 -> 2400
632 729
633 // 特殊规则:若消耗或项目数中仅一项未达标(0星),底薪按1星(2000元)计算 730 // 特殊规则:若消耗或项目数中仅一项未达标(0星),底薪按1星(2000元)计算
634 // 新店规则:新店底薪最低为1星(2000元),不满足1星按1星算 731 // 新店规则:新店底薪最低为1星(2000元),不满足1星按1星算
635 732
636 int starCons = 0; 733 int starCons = 0;
637 - if (consumption >= 40000) starCons = 3;  
638 - else if (consumption >= 20000) starCons = 2;  
639 - else if (consumption >= 10000) starCons = 1; 734 + if (dailyAvgConsumption >= consStar3) starCons = 3;
  735 + else if (dailyAvgConsumption >= consStar2) starCons = 2;
  736 + else if (dailyAvgConsumption >= consStar1) starCons = 1;
640 737
641 int starProj = 0; 738 int starProj = 0;
642 - if (projectCount >= 156) starProj = 3;  
643 - else if (projectCount >= 126) starProj = 2;  
644 - else if (projectCount >= 96) starProj = 1; 739 + if (dailyAvgProjectCount >= projStar3) starProj = 3;
  740 + else if (dailyAvgProjectCount >= projStar2) starProj = 2;
  741 + else if (dailyAvgProjectCount >= projStar1) starProj = 1;
645 742
646 int finalStar = Math.Min(starCons, starProj); 743 int finalStar = Math.Min(starCons, starProj);
647 744
@@ -713,6 +810,10 @@ namespace NCC.Extend @@ -713,6 +810,10 @@ namespace NCC.Extend
713 // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X% 810 // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X%
714 // 4. 新店顾问不考核消耗 811 // 4. 新店顾问不考核消耗
715 812
  813 + // 使用传入的 teamMembers 计算总消耗,或者直接使用 TeamTotalConsumption (如果已计算)
  814 + // 但为了保险起见,这里重新计算或使用已有的逻辑
  815 + // 注意:CalculateConsultantCommission 方法签名未变,但逻辑需确认 teamConsumption 来源
  816 + // 在调用此方法前,teamMembers 已经有了 Consumption 数据
716 var teamConsumption = teamMembers.Sum(x => x.Consumption); 817 var teamConsumption = teamMembers.Sum(x => x.Consumption);
717 818
718 // 计算组员(非顾问)业绩总和 819 // 计算组员(非顾问)业绩总和
netcore/src/Modularity/System/NCC.System.Entitys/Entity/Permission/UserEntity.cs
@@ -358,5 +358,11 @@ namespace NCC.System.Entitys.Permission @@ -358,5 +358,11 @@ namespace NCC.System.Entitys.Permission
358 [SugarColumn(ColumnName = "F_GW")] 358 [SugarColumn(ColumnName = "F_GW")]
359 public string Gw { get; set; } 359 public string Gw { get; set; }
360 360
  361 + /// <summary>
  362 + /// 是否在职(1-在职,0-离职)
  363 + /// </summary>
  364 + [SugarColumn(ColumnName = "F_IsOnJob")]
  365 + public int? IsOnJob { get; set; }
  366 +
361 } 367 }
362 } 368 }
sql/创建主任工资统计表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建主任工资统计表
  3 +-- 功能:存储主任每月的工资计算数据,包括底薪、提成、考核扣款、扣款、补贴、奖金、支付等信息
  4 +-- 创建时间:2025年
  5 +-- ============================================
  6 +
  7 +-- 删除表(如果存在)
  8 +DROP TABLE IF EXISTS lq_director_salary_statistics;
  9 +
  10 +-- ============================================
  11 +-- 创建主任工资统计表
  12 +-- ============================================
  13 +CREATE TABLE lq_director_salary_statistics (
  14 + -- 主键
  15 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID',
  16 +
  17 + -- 一、基础信息字段
  18 + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID',
  19 + F_StoreName VARCHAR(200) NOT NULL COMMENT '门店名称',
  20 + F_Position VARCHAR(50) NOT NULL DEFAULT '主任' COMMENT '核算岗位(固定为"主任")',
  21 + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名',
  22 + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID',
  23 + F_StatisticsMonth VARCHAR(20) NOT NULL COMMENT '统计月份(YYYYMM格式)',
  24 + F_StoreType INT NULL COMMENT '门店类型(200平/旗舰店)',
  25 + F_StoreCategory INT NOT NULL COMMENT '门店分类(1=A类,2=B类,3=C类)',
  26 + F_IsNewStore VARCHAR(10) NULL COMMENT '是否新店(是/否)',
  27 + F_NewStoreProtectionStage INT NULL DEFAULT 0 COMMENT '新店保护阶段(0/1/2)',
  28 +
  29 + -- 二、业绩相关字段
  30 + F_StoreTotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店总业绩(门店开单业绩-门店退卡业绩)',
  31 + F_StoreBillingPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店开单业绩',
  32 + F_StoreRefundPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店退卡业绩',
  33 + F_StoreLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店生命线',
  34 + F_PerformanceCompletionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩完成率(门店业绩/门店生命线)',
  35 +
  36 + -- 三、考核相关字段
  37 + F_PerformanceReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '业绩是否达标(是/否,业绩≥生命线为是)',
  38 + F_HeadCountReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '人头是否达标(是/否,实际人头≥目标人头为是)',
  39 + F_ConsumeReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '消耗是否达标(是/否,实际消耗≥目标消耗为是,仅老店考核)',
  40 + F_AssessmentDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '考核扣款金额(未达标指标数×500)',
  41 + F_UnreachedIndicatorCount INT NOT NULL DEFAULT 0 COMMENT '未达标指标数量(老店最多3个,新店最多2个)',
  42 +
  43 + -- 四、人头相关字段
  44 + F_HeadCount INT NOT NULL DEFAULT 0 COMMENT '进店消耗人数(有消费金额的,按门店按月去重客户数)',
  45 + F_TargetHeadCount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '目标人头数(从lq_md_target获取)',
  46 +
  47 + -- 五、消耗相关字段
  48 + F_StoreConsume DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店消耗金额(门店当月总消耗)',
  49 + F_TargetConsume DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '目标消耗金额(从lq_md_target获取)',
  50 +
  51 + -- 六、提成相关字段(阶梯提成)
  52 + F_CommissionRateBelowLifeline DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '≤生命线部分提成比例(老店:A类2%,B类2.5%,C类3%;新店:统一2%)',
  53 + F_CommissionRateAboveLifeline DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '>生命线部分提成比例(老店:A类2.5%,B类3%,C类3.5%;新店:统一2.5%)',
  54 + F_CommissionAmountBelowLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '≤生命线部分提成金额',
  55 + F_CommissionAmountAboveLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '>生命线部分提成金额',
  56 + F_TotalCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成总金额(≤生命线部分+>生命线部分)',
  57 +
  58 + -- 七、底薪相关字段
  59 + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 3500.00 COMMENT '底薪金额(固定3500元)',
  60 + F_ActualBaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实际底薪(底薪-考核扣款)',
  61 +
  62 + -- 八、考勤相关字段
  63 + F_WorkingDays INT NOT NULL DEFAULT 0 COMMENT '在店天数',
  64 + F_LeaveDays INT NOT NULL DEFAULT 0 COMMENT '请假天数',
  65 +
  66 + -- 九、工资计算字段
  67 + F_GrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '应发工资(实际底薪+提成总金额)',
  68 + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(应发工资-扣款合计+补贴合计+奖金)',
  69 +
  70 + -- 十、扣款相关字段
  71 + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款',
  72 + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款',
  73 + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款',
  74 + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保',
  75 + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励',
  76 + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费',
  77 + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用',
  78 + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用',
  79 + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计',
  80 +
  81 + -- 十一、补贴相关字段
  82 + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴',
  83 + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴',
  84 + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴',
  85 + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴',
  86 + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计',
  87 +
  88 + -- 十二、奖金相关字段
  89 + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金',
  90 + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金',
  91 + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金',
  92 +
  93 + -- 十三、支付相关字段
  94 + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)',
  95 + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额',
  96 + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额',
  97 + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月',
  98 + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额',
  99 +
  100 + -- 十四、系统字段
  101 + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)',
  102 + F_CreateTime DATETIME NOT NULL COMMENT '创建时间',
  103 + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间',
  104 + F_CreateUser VARCHAR(50) NULL COMMENT '创建人',
  105 + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人',
  106 +
  107 + -- 主键约束
  108 + PRIMARY KEY (F_Id),
  109 +
  110 + -- 唯一索引:确保同一员工同一月份只有一条记录
  111 + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth),
  112 +
  113 + -- 普通索引
  114 + KEY `idx_store_id` (F_StoreId),
  115 + KEY `idx_statistics_month` (F_StatisticsMonth),
  116 + KEY `idx_employee_id` (F_EmployeeId),
  117 + KEY `idx_store_category` (F_StoreCategory),
  118 + KEY `idx_create_time` (F_CreateTime)
  119 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主任工资统计表';
  120 +
  121 +-- ============================================
  122 +-- 表结构说明
  123 +-- ============================================
  124 +/*
  125 +表名:lq_director_salary_statistics(主任工资统计表)
  126 +
  127 +功能说明:
  128 +1. 存储主任每月的工资计算数据
  129 +2. 包括底薪、提成、考核扣款、扣款、补贴、奖金、支付等信息
  130 +3. 支持按门店、员工、月份查询
  131 +
  132 +主要字段说明:
  133 +- F_StoreCategory:门店分类(1=A类,2=B类,3=C类),用于确定提成比例
  134 +- F_StoreTotalPerformance:门店总业绩,用于计算提成
  135 +- F_StoreLifeline:门店生命线,用于判断提成比例和考核
  136 +- F_HeadCount:进店消耗人数,用于考核
  137 +- F_StoreConsume:门店消耗金额,用于考核(仅老店)
  138 +- F_BaseSalary:底薪(固定3500元)
  139 +- F_AssessmentDeduction:考核扣款(未达标指标数×500)
  140 +- F_ActualBaseSalary:实际底薪(底薪-考核扣款)
  141 +- F_CommissionAmountBelowLifeline:≤生命线部分提成金额
  142 +- F_CommissionAmountAboveLifeline:>生命线部分提成金额
  143 +- F_TotalCommissionAmount:提成总金额(阶梯提成)
  144 +
  145 +索引说明:
  146 +- 主键索引:F_Id
  147 +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录)
  148 +- 普通索引:
  149 + - F_StoreId:按门店查询
  150 + - F_StatisticsMonth:按月份查询
  151 + - F_EmployeeId:按员工查询
  152 + - F_StoreCategory:按门店分类查询
  153 + - F_CreateTime:按创建时间查询
  154 +
  155 +数据校验要求:
  156 +1. 门店分类(F_StoreCategory)必须设置,不允许为NULL
  157 +2. 门店生命线(F_StoreLifeline)必须设置,未设置应报错
  158 +3. 目标人头数(F_TargetHeadCount)必须设置(用于考核)
  159 +4. 目标消耗(F_TargetConsume)必须设置(老店考核用)
  160 +
  161 +计算公式:
  162 +- 应发工资 = 实际底薪 + 提成总金额
  163 +- 实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金
  164 +- 业绩完成率 = 门店业绩 / 门店生命线 × 100%
  165 +- 实际底薪 = 底薪(3500)- 考核扣款
  166 +- 考核扣款 = 未达标指标数 × 500
  167 +- 提成总金额 = ≤生命线部分提成金额 + >生命线部分提成金额
  168 +
  169 +提成计算规则(阶梯提成):
  170 +老店:
  171 +- A类门店:≤生命线部分2%,>生命线部分2.5%
  172 +- B类门店:≤生命线部分2.5%,>生命线部分3%
  173 +- C类门店:≤生命线部分3%,>生命线部分3.5%
  174 +
  175 +新店(统一标准):
  176 +- ≤生命线部分2%,>生命线部分2.5%
  177 +
  178 +考核规则:
  179 +老店(3个指标):
  180 +- 业绩考核:门店业绩是否达到门店生命线
  181 +- 人头考核:进店消耗人数是否达到目标人头数
  182 +- 消耗考核:门店消耗是否达到目标消耗
  183 +- 每个未达标指标扣500元
  184 +
  185 +新店(2个指标):
  186 +- 业绩考核:门店业绩是否达到门店生命线
  187 +- 人头考核:进店消耗人数是否达到目标人头数
  188 +- 每个未达标指标扣500元
  189 +- 新店不考核消耗
  190 +*/
  191 +
sql/创建店助工资统计表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建店助工资统计表
  3 +-- 功能:存储店助每月的工资计算数据,包括底薪、提成、阶段奖励、扣款、补贴、奖金、支付等信息
  4 +-- 创建时间:2025年
  5 +-- ============================================
  6 +
  7 +-- 删除表(如果存在)
  8 +DROP TABLE IF EXISTS lq_assistant_salary_statistics;
  9 +
  10 +-- ============================================
  11 +-- 创建店助工资统计表
  12 +-- ============================================
  13 +CREATE TABLE lq_assistant_salary_statistics (
  14 + -- 主键
  15 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID',
  16 +
  17 + -- 一、基础信息字段
  18 + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID',
  19 + F_StoreName VARCHAR(200) NOT NULL COMMENT '门店名称',
  20 + F_Position VARCHAR(50) NOT NULL COMMENT '核算岗位(店助/店助主任)',
  21 + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名',
  22 + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID',
  23 + F_StatisticsMonth VARCHAR(20) NOT NULL COMMENT '统计月份(YYYYMM格式)',
  24 + F_StoreType INT NULL COMMENT '门店类型(200平/旗舰店)',
  25 + F_StoreCategory INT NOT NULL COMMENT '门店分类(1=A类,2=B类,3=C类)',
  26 + F_IsNewStore VARCHAR(10) NULL COMMENT '是否新店(是/否)',
  27 + F_NewStoreProtectionStage INT NULL DEFAULT 0 COMMENT '新店保护阶段(0/1/2)',
  28 +
  29 + -- 二、业绩相关字段
  30 + F_StoreTotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店总业绩(门店开单业绩-门店退卡业绩)',
  31 + F_StoreBillingPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店开单业绩',
  32 + F_StoreRefundPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店退卡业绩',
  33 + F_StoreLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店生命线',
  34 + F_PerformanceCompletionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩完成率(门店业绩/门店生命线)',
  35 +
  36 + -- 三、提成相关字段
  37 + F_CommissionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '提成比例(0%/0.4%/0.6%)',
  38 + F_CommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成金额(门店业绩×提成比例)',
  39 +
  40 + -- 四、阶段奖励相关字段
  41 + F_HeadCount INT NOT NULL DEFAULT 0 COMMENT '进店消耗人数(有消费金额的,按门店按月去重客户数)',
  42 + F_Stage1TargetHeadCount INT NOT NULL DEFAULT 0 COMMENT '第一阶段目标人数',
  43 + F_Stage2TargetHeadCount INT NOT NULL DEFAULT 0 COMMENT '第二阶段目标人数',
  44 + F_ReachedStage1 VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '是否达到第一阶段(是/否)',
  45 + F_ReachedStage2 VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '是否达到第二阶段(是/否)',
  46 + F_StageRewardAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '阶段奖励金额(0/200/400元)',
  47 + F_Stage1Reward DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '第一阶段奖励金额(0或200元)',
  48 + F_Stage2Reward DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '第二阶段奖励金额(0或200元)',
  49 +
  50 + -- 五、底薪相关字段
  51 + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '底薪金额(A类3000,B类3100,C类3200)',
  52 +
  53 + -- 六、固定奖励字段
  54 + F_PhoneManagementFee DECIMAL(18,2) NOT NULL DEFAULT 150.00 COMMENT '手机管理费(固定150元/月)',
  55 +
  56 + -- 七、考勤相关字段
  57 + F_WorkingDays INT NOT NULL DEFAULT 0 COMMENT '在店天数',
  58 + F_LeaveDays INT NOT NULL DEFAULT 0 COMMENT '请假天数',
  59 +
  60 + -- 八、工资计算字段
  61 + F_GrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '应发工资(底薪+提成+阶段奖励+固定奖励)',
  62 + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(应发工资-扣款合计+补贴合计+奖金)',
  63 +
  64 + -- 九、扣款相关字段
  65 + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款',
  66 + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款',
  67 + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款',
  68 + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保',
  69 + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励',
  70 + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费',
  71 + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用',
  72 + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用',
  73 + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计',
  74 +
  75 + -- 十、补贴相关字段
  76 + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴',
  77 + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴',
  78 + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴',
  79 + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴',
  80 + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计',
  81 +
  82 + -- 十一、奖金相关字段
  83 + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金',
  84 + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金',
  85 + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金',
  86 +
  87 + -- 十二、支付相关字段
  88 + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)',
  89 + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额',
  90 + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额',
  91 + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月',
  92 + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额',
  93 +
  94 + -- 十三、系统字段
  95 + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)',
  96 + F_CreateTime DATETIME NOT NULL COMMENT '创建时间',
  97 + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间',
  98 + F_CreateUser VARCHAR(50) NULL COMMENT '创建人',
  99 + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人',
  100 +
  101 + -- 主键约束
  102 + PRIMARY KEY (F_Id),
  103 +
  104 + -- 唯一索引:确保同一员工同一月份只有一条记录
  105 + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth),
  106 +
  107 + -- 普通索引
  108 + KEY `idx_store_id` (F_StoreId),
  109 + KEY `idx_statistics_month` (F_StatisticsMonth),
  110 + KEY `idx_employee_id` (F_EmployeeId),
  111 + KEY `idx_store_category` (F_StoreCategory),
  112 + KEY `idx_create_time` (F_CreateTime)
  113 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店助工资统计表';
  114 +
  115 +-- ============================================
  116 +-- 表结构说明
  117 +-- ============================================
  118 +/*
  119 +表名:lq_assistant_salary_statistics(店助工资统计表)
  120 +
  121 +功能说明:
  122 +1. 存储店助每月的工资计算数据
  123 +2. 包括底薪、提成、阶段奖励、扣款、补贴、奖金、支付等信息
  124 +3. 支持按门店、员工、月份查询
  125 +
  126 +主要字段说明:
  127 +- F_StoreCategory:门店分类(1=A类,2=B类,3=C类),用于确定底薪
  128 +- F_StoreTotalPerformance:门店总业绩,用于计算提成
  129 +- F_StoreLifeline:门店生命线,用于判断提成比例
  130 +- F_HeadCount:进店消耗人数,用于判断阶段奖励
  131 +- F_Stage1TargetHeadCount/F_Stage2TargetHeadCount:阶段目标人数
  132 +- F_BaseSalary:底薪(A类3000,B类3100,C类3200)
  133 +- F_CommissionAmount:提成金额(门店业绩×提成比例)
  134 +- F_StageRewardAmount:阶段奖励金额(0/200/400元)
  135 +- F_PhoneManagementFee:手机管理费(固定150元/月)
  136 +
  137 +索引说明:
  138 +- 主键索引:F_Id
  139 +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录)
  140 +- 普通索引:
  141 + - F_StoreId:按门店查询
  142 + - F_StatisticsMonth:按月份查询
  143 + - F_EmployeeId:按员工查询
  144 + - F_StoreCategory:按门店分类查询
  145 + - F_CreateTime:按创建时间查询
  146 +
  147 +数据校验要求:
  148 +1. 门店分类(F_StoreCategory)必须设置,不允许为NULL
  149 +2. 门店生命线(F_StoreLifeline)必须设置,未设置应报错
  150 +3. 阶段目标(F_Stage1TargetHeadCount、F_Stage2TargetHeadCount)必须设置,未设置应报错
  151 +
  152 +计算公式:
  153 +- 应发工资 = 底薪 + 提成金额 + 阶段奖励金额 + 手机管理费
  154 +- 实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金
  155 +- 业绩完成率 = 门店业绩 / 门店生命线 × 100%
  156 +- 提成比例:根据门店业绩与门店生命线的比例确定(0%/0.4%/0.6%)
  157 +- 阶段奖励:根据进店消耗人数是否达到阶段目标(0/200/400元)
  158 +*/
  159 +
sql/创建报销多级审批流程表.sql 0 → 100644
  1 +-- 报销多级审批流程改造 - 数据库表结构
  2 +-- 执行时间:2024年
  3 +-- 说明:支持3-5个动态审批节点,每个报销申请在创建时设置节点和审批人,支持通过/不通过/退回操作
  4 +
  5 +-- 1. 报销申请节点表(每个报销申请的节点配置)
  6 +CREATE TABLE IF NOT EXISTS `lq_reimbursement_application_node` (
  7 + `F_Id` varchar(50) NOT NULL COMMENT '节点编号',
  8 + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID',
  9 + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)',
  10 + `F_NodeName` varchar(100) DEFAULT NULL COMMENT '节点名称',
  11 + `F_ApprovalType` varchar(20) DEFAULT '会签' COMMENT '审批类型(会签/或签)',
  12 + `F_IsRequired` int DEFAULT 1 COMMENT '是否必审(1-必审,0-可选)',
  13 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  14 + PRIMARY KEY (`F_Id`),
  15 + KEY `idx_application_id` (`F_ApplicationId`),
  16 + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`)
  17 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报销申请节点表';
  18 +
  19 +-- 2. 报销申请节点审批人表(每个节点的审批人,在创建报销申请时指定)
  20 +CREATE TABLE IF NOT EXISTS `lq_reimbursement_application_node_user` (
  21 + `F_Id` varchar(50) NOT NULL COMMENT '记录编号',
  22 + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID',
  23 + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号',
  24 + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)',
  25 + `F_UserId` varchar(50) NOT NULL COMMENT '审批人ID',
  26 + `F_UserName` varchar(100) DEFAULT NULL COMMENT '审批人姓名',
  27 + `F_SortOrder` int DEFAULT 0 COMMENT '排序',
  28 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  29 + PRIMARY KEY (`F_Id`),
  30 + KEY `idx_application_id` (`F_ApplicationId`),
  31 + KEY `idx_node_id` (`F_NodeId`),
  32 + KEY `idx_user_id` (`F_UserId`),
  33 + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`),
  34 + UNIQUE KEY `uk_application_node_user` (`F_ApplicationId`, `F_NodeId`, `F_UserId`)
  35 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报销申请节点审批人表';
  36 +
  37 +-- 3. 审批记录表
  38 +CREATE TABLE IF NOT EXISTS `lq_reimbursement_approval_record` (
  39 + `F_Id` varchar(50) NOT NULL COMMENT '记录编号',
  40 + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID',
  41 + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号',
  42 + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)',
  43 + `F_ApproverId` varchar(50) NOT NULL COMMENT '审批人ID',
  44 + `F_ApproverName` varchar(100) DEFAULT NULL COMMENT '审批人姓名',
  45 + `F_ApprovalResult` varchar(20) NOT NULL COMMENT '审批结果(通过/不通过/退回)',
  46 + `F_ApprovalOpinion` varchar(500) DEFAULT NULL COMMENT '审批意见',
  47 + `F_ApprovalTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '审批时间',
  48 + `F_IsCurrentNode` int DEFAULT 0 COMMENT '是否当前节点(1-是,0-否)',
  49 + PRIMARY KEY (`F_Id`),
  50 + KEY `idx_application_id` (`F_ApplicationId`),
  51 + KEY `idx_node_id` (`F_NodeId`),
  52 + KEY `idx_approver_id` (`F_ApproverId`),
  53 + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`)
  54 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='审批记录表';
  55 +
  56 +-- 4. 修改报销申请表,添加新字段
  57 +ALTER TABLE `lq_reimbursement_application`
  58 + ADD COLUMN `F_NodeCount` int DEFAULT 0 COMMENT '节点数量(3,4,5)' AFTER `F_PurchaseRecordsId`,
  59 + ADD COLUMN `F_CurrentNodeOrder` int DEFAULT 0 COMMENT '当前审批节点(0-待审批,1-N节点,N+1-已完成)' AFTER `F_NodeCount`,
  60 + ADD COLUMN `F_CurrentNodeId` varchar(50) DEFAULT NULL COMMENT '当前节点ID' AFTER `F_CurrentNodeOrder`,
  61 + ADD COLUMN `F_ApprovalStatus` varchar(20) DEFAULT '待审批' COMMENT '审批状态(待审批/审批中/已通过/未通过/已退回)' AFTER `F_CurrentNodeId`,
  62 + ADD COLUMN `F_ReturnedNodeOrder` int DEFAULT NULL COMMENT '退回节点' AFTER `F_ApprovalStatus`,
  63 + ADD COLUMN `F_ReturnedReason` varchar(500) DEFAULT NULL COMMENT '退回原因' AFTER `F_ReturnedNodeOrder`,
  64 + ADD KEY `idx_current_node` (`F_CurrentNodeId`),
  65 + ADD KEY `idx_approval_status` (`F_ApprovalStatus`),
  66 + ADD KEY `idx_node_count` (`F_NodeCount`);
  67 +
  68 +-- 5. 数据迁移:更新已有数据的审批状态
  69 +-- 已审批的数据(假设为4个节点)
  70 +UPDATE `lq_reimbursement_application`
  71 +SET `F_NodeCount` = 4,
  72 + `F_CurrentNodeOrder` = 5,
  73 + `F_ApprovalStatus` = '已通过'
  74 +WHERE `F_ApproveStatus` = '已审批';
  75 +
  76 +-- 未通过的数据(假设为4个节点)
  77 +UPDATE `lq_reimbursement_application`
  78 +SET `F_NodeCount` = 4,
  79 + `F_CurrentNodeOrder` = 1,
  80 + `F_ApprovalStatus` = '未通过'
  81 +WHERE `F_ApproveStatus` = '未通过';
  82 +
  83 +-- 待审批的数据
  84 +UPDATE `lq_reimbursement_application`
  85 +SET `F_NodeCount` = 4, -- 默认4个节点,实际使用时需要根据具体情况设置
  86 + `F_CurrentNodeOrder` = 0,
  87 + `F_ApprovalStatus` = '待审批'
  88 +WHERE `F_ApproveStatus` = '待审批' OR `F_ApproveStatus` IS NULL;
  89 +
sql/添加BASE_USER表是否在职字段.sql 0 → 100644
  1 +-- 在 BASE_USER 表中添加是否在职字段
  2 +-- 执行时间:2024年
  3 +-- 说明:添加 F_IsOnJob 字段,用于标识员工是否在职(1-在职,0-离职)
  4 +
  5 +ALTER TABLE BASE_USER
  6 +ADD COLUMN F_IsOnJob INT DEFAULT 1 COMMENT '是否在职(1-在职,0-离职)' AFTER F_GW;
  7 +
  8 +-- 更新现有数据:默认所有用户为在职状态(如果字段已存在,此语句会报错,可忽略)
  9 +-- UPDATE BASE_USER SET F_IsOnJob = 1 WHERE F_IsOnJob IS NULL;
  10 +
主任工资计算规则梳理.md 0 → 100644
  1 +# 主任工资计算规则梳理
  2 +
  3 +## 📋 概述
  4 +
  5 +主任工资由以下几个部分组成:
  6 +1. **底薪**:固定3500元,根据考核指标扣款
  7 +2. **提成**:根据门店业绩与门店生命线的关系,使用阶梯提成模式计算
  8 +
  9 +---
  10 +
  11 +## 💰 工资组成规则
  12 +
  13 +### 1. 底薪规则
  14 +
  15 +**固定底薪**:3500元
  16 +
  17 +#### 老店主任底薪考核
  18 +
  19 +**考核指标**(3个):
  20 +1. **业绩考核**:门店业绩是否达到门店生命线
  21 +2. **人头考核**:进店消耗人数是否达到目标人头数
  22 +3. **消耗考核**:门店消耗是否达到目标消耗
  23 +
  24 +**扣款规则**:
  25 +- 每个指标未达到:扣除500元
  26 +- 如果3个指标都未达到:扣除1500元(500 × 3)
  27 +
  28 +**计算公式**:
  29 +```
  30 +底薪 = 3500 - (未达标指标数 × 500)
  31 +```
  32 +
  33 +#### 新店主任底薪考核
  34 +
  35 +**考核指标**(2个):
  36 +1. **业绩考核**:门店业绩是否达到门店生命线
  37 +2. **人头考核**:进店消耗人数是否达到目标人头数
  38 +
  39 +**扣款规则**:
  40 +- 每个指标未达到:扣除500元
  41 +- 如果2个指标都未达到:扣除1000元(500 × 2)
  42 +
  43 +**计算公式**:
  44 +```
  45 +底薪 = 3500 - (未达标指标数 × 500)
  46 +```
  47 +
  48 +**注意**:新店不考核消耗指标
  49 +
  50 +---
  51 +
  52 +### 2. 提成规则
  53 +
  54 +**提成计算方式**:阶梯提成模式
  55 +
  56 +#### 老店主任提成规则
  57 +
  58 +根据门店分类(A、B、C类)和业绩是否超过生命线,使用不同的阶梯提成比例:
  59 +
  60 +| 门店分类 | 业绩 ≤ 生命线部分 | 业绩 > 生命线部分 |
  61 +|---------|----------------|-----------------|
  62 +| A类门店 | 2% | 2.5% |
  63 +| B类门店 | 2.5% | 3% |
  64 +| C类门店 | 3% | 3.5% |
  65 +
  66 +**计算公式**:
  67 +```
  68 +如果 业绩 ≤ 生命线:
  69 + 提成 = 业绩 × 对应提成比例(≤生命线部分)
  70 +
  71 +如果 业绩 > 生命线:
  72 + 提成 = 生命线 × 对应提成比例(≤生命线部分) + (业绩 - 生命线) × 对应提成比例(>生命线部分)
  73 +```
  74 +
  75 +**计算示例**:
  76 +- 生命线:100,000元
  77 +- 业绩:150,000元
  78 +- 门店分类:A类
  79 +
  80 +计算过程:
  81 +1. 业绩(150,000)> 生命线(100,000)
  82 +2. ≤生命线部分:100,000 × 2% = 2,000元
  83 +3. >生命线部分:(150,000 - 100,000) × 2.5% = 50,000 × 2.5% = 1,250元
  84 +4. 总提成:2,000 + 1,250 = 3,250元
  85 +
  86 +#### 新店主任提成规则
  87 +
  88 +**统一标准**(不区分A、B、C类门店):
  89 +
  90 +| 业绩范围 | 提成比例 |
  91 +|---------|---------|
  92 +| 业绩 ≤ 生命线部分 | 2% |
  93 +| 业绩 > 生命线部分 | 2.5% |
  94 +
  95 +**计算公式**:
  96 +```
  97 +如果 业绩 ≤ 生命线:
  98 + 提成 = 业绩 × 2%
  99 +
  100 +如果 业绩 > 生命线:
  101 + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5%
  102 +```
  103 +
  104 +**计算示例**:
  105 +- 生命线:100,000元
  106 +- 业绩:150,000元
  107 +
  108 +计算过程:
  109 +1. 业绩(150,000)> 生命线(100,000)
  110 +2. ≤生命线部分:100,000 × 2% = 2,000元
  111 +3. >生命线部分:(150,000 - 100,000) × 2.5% = 50,000 × 2.5% = 1,250元
  112 +4. 总提成:2,000 + 1,250 = 3,250元
  113 +
  114 +---
  115 +
  116 +## 📊 最终工资计算
  117 +
  118 +### 计算公式
  119 +
  120 +```
  121 +最终工资 = 底薪 + 提成
  122 +```
  123 +
  124 +其中:
  125 +- **底薪** = 3500 - (未达标指标数 × 500)
  126 +- **提成** = 根据阶梯提成规则计算
  127 +
  128 +### 计算示例
  129 +
  130 +#### 示例1:老店主任(A类门店,全部达标)
  131 +
  132 +**基础数据**:
  133 +- 门店分类:A类
  134 +- 门店生命线:100,000元
  135 +- 门店业绩:150,000元
  136 +- 目标人头:100人
  137 +- 实际人头:120人
  138 +- 目标消耗:80,000元
  139 +- 实际消耗:90,000元
  140 +- 是否新店:否
  141 +
  142 +**计算过程**:
  143 +
  144 +1. **底薪计算**:
  145 + - 业绩考核:150,000 ≥ 100,000 ✓(达标)
  146 + - 人头考核:120 ≥ 100 ✓(达标)
  147 + - 消耗考核:90,000 ≥ 80,000 ✓(达标)
  148 + - 未达标指标数:0
  149 + - 底薪 = 3500 - (0 × 500) = 3500元
  150 +
  151 +2. **提成计算**:
  152 + - 业绩(150,000)> 生命线(100,000)
  153 + - ≤生命线部分:100,000 × 2% = 2,000元
  154 + - >生命线部分:(150,000 - 100,000) × 2.5% = 1,250元
  155 + - 总提成 = 2,000 + 1,250 = 3,250元
  156 +
  157 +3. **最终工资**:
  158 + - 最终工资 = 3500 + 3250 = 6,750元
  159 +
  160 +#### 示例2:老店主任(B类门店,部分未达标)
  161 +
  162 +**基础数据**:
  163 +- 门店分类:B类
  164 +- 门店生命线:100,000元
  165 +- 门店业绩:80,000元
  166 +- 目标人头:100人
  167 +- 实际人头:90人
  168 +- 目标消耗:80,000元
  169 +- 实际消耗:75,000元
  170 +- 是否新店:否
  171 +
  172 +**计算过程**:
  173 +
  174 +1. **底薪计算**:
  175 + - 业绩考核:80,000 < 100,000 ✗(未达标)
  176 + - 人头考核:90 < 100 ✗(未达标)
  177 + - 消耗考核:75,000 < 80,000 ✗(未达标)
  178 + - 未达标指标数:3
  179 + - 底薪 = 3500 - (3 × 500) = 3500 - 1500 = 2000元
  180 +
  181 +2. **提成计算**:
  182 + - 业绩(80,000)< 生命线(100,000)
  183 + - 提成 = 80,000 × 2.5% = 2,000元
  184 +
  185 +3. **最终工资**:
  186 + - 最终工资 = 2000 + 2000 = 4,000元
  187 +
  188 +#### 示例3:新店主任(全部达标)
  189 +
  190 +**基础数据**:
  191 +- 门店生命线:100,000元
  192 +- 门店业绩:150,000元
  193 +- 目标人头:100人
  194 +- 实际人头:120人
  195 +- 是否新店:是
  196 +
  197 +**计算过程**:
  198 +
  199 +1. **底薪计算**:
  200 + - 业绩考核:150,000 ≥ 100,000 ✓(达标)
  201 + - 人头考核:120 ≥ 100 ✓(达标)
  202 + - 未达标指标数:0
  203 + - 底薪 = 3500 - (0 × 500) = 3500元
  204 +
  205 +2. **提成计算**:
  206 + - 业绩(150,000)> 生命线(100,000)
  207 + - ≤生命线部分:100,000 × 2% = 2,000元
  208 + - >生命线部分:(150,000 - 100,000) × 2.5% = 1,250元
  209 + - 总提成 = 2,000 + 1,250 = 3,250元
  210 +
  211 +3. **最终工资**:
  212 + - 最终工资 = 3500 + 3250 = 6,750元
  213 +
  214 +---
  215 +
  216 +## 🔍 数据来源说明
  217 +
  218 +### 1. 门店业绩
  219 +
  220 +**数据来源**:
  221 +- 开单业绩:`lq_kd_kdjlb` 表
  222 + - 字段:`Djmd`(门店ID)、`Sfyj`(实付业绩)、`Kdrq`(开单日期)
  223 + - 条件:`F_IsEffective = 1`(有效记录)
  224 +- 退卡业绩:`lq_hytk_hytk` 表
  225 + - 字段:`Md`(门店ID)、`F_ActualRefundAmount`(实际退卡金额,优先使用,如果没有则用`Tkje`)、`Tksj`(退卡时间)
  226 + - 条件:`F_IsEffective = 1`(有效记录)
  227 +
  228 +**计算公式**:
  229 +```
  230 +门店业绩 = SUM(开单实付) - SUM(退卡金额)
  231 +```
  232 +
  233 +### 2. 门店生命线
  234 +
  235 +**数据来源**:`lq_md_target` 表
  236 +- 字段:`F_StoreLifeline`(门店生命线)
  237 +- 查询条件:`F_StoreId` = 门店ID,`F_Month` = 统计月份(YYYYMM格式)
  238 +
  239 +### 3. 门店分类
  240 +
  241 +**数据来源**:`lq_mdxx` 表
  242 +- 字段:`F_StoreCategory`(门店分类)
  243 + - `1` = A类门店
  244 + - `2` = B类门店
  245 + - `3` = C类门店
  246 +
  247 +### 4. 人头数据(进店消耗人数)
  248 +
  249 +**数据来源**:
  250 +- 主表:`lq_xh_hyhk`(耗卡记录表)
  251 +- 关联表:`lq_xh_jksyj`(健康师业绩表,用于判断是否有消费金额)
  252 +
  253 +**统计规则**:
  254 +- 有消费金额的,按门店按月去重客户数
  255 +- 只统计有效记录(`F_IsEffective = 1`)
  256 +- 只统计有消费金额的记录(`jksyj > 0`)
  257 +
  258 +**目标人头数**:
  259 +- 数据来源:`lq_md_target` 表
  260 +- 字段:`F_StoreHeadcountTarget`(门店人头目标)
  261 +
  262 +### 5. 消耗数据
  263 +
  264 +**数据来源**:`lq_xh_jksyj` 表
  265 +- 字段:`jksyj`(健康师业绩,即消耗金额)
  266 +- 关联:通过 `glkdbh` 关联到 `lq_xh_hyhk.F_Id`
  267 +- 条件:`F_IsEffective = 1`(有效记录)
  268 +
  269 +**统计规则**:
  270 +- 按门店统计当月总消耗金额
  271 +
  272 +**目标消耗**:
  273 +- 数据来源:`lq_md_target` 表
  274 +- 字段:`F_StoreConsumeTarget`(门店消耗目标)
  275 +
  276 +### 6. 新店信息
  277 +
  278 +**数据来源**:`lq_md_xdbhsj` 表
  279 +- 字段:
  280 + - `Mdid`(门店ID)
  281 + - `Bhkssj`(保护开始时间)
  282 + - `Bhjssj`(保护结束时间)
  283 + - `Sfqy`(是否启用,1=启用)
  284 + - `Stage`(新店保护阶段)
  285 +
  286 +**判断逻辑**:
  287 +- 如果统计月份的第一天在保护期内(`Bhkssj <= startDate && Bhjssj >= startDate`),则为新店
  288 +- 否则为老店
  289 +
  290 +### 7. 主任员工信息
  291 +
  292 +**数据来源**:`BASE_USER` 表
  293 +- 字段:
  294 + - `F_Id`(员工ID)
  295 + - `F_RealName`(员工姓名)
  296 + - `F_Gw`(岗位,应为"主任")
  297 + - `F_Mdid`(门店ID)
  298 +
  299 +**查询条件**:
  300 +- `F_Gw = "主任"`
  301 +- `F_DeleteMark = null`(未删除)
  302 +- `F_EnabledMark = 1`(启用)
  303 +
  304 +---
  305 +
  306 +## 📝 计算流程
  307 +
  308 +### 1. 获取基础数据
  309 +
  310 +- 从 `BASE_USER` 获取主任员工列表(岗位为"主任")
  311 +- 获取门店信息(`lq_mdxx`)
  312 +- 获取门店目标信息(`lq_md_target`)
  313 +- 获取新店保护信息(`lq_md_xdbhsj`)
  314 +
  315 +### 2. 计算门店业绩
  316 +
  317 +- 统计开单业绩(`lq_kd_kdjlb`)
  318 +- 统计退卡业绩(`lq_hytk_hytk`)
  319 +- 计算门店总业绩 = 开单业绩 - 退卡业绩
  320 +
  321 +### 3. 计算门店消耗
  322 +
  323 +- 统计门店当月总消耗金额(`lq_xh_jksyj`)
  324 +
  325 +### 4. 统计进店消耗人数
  326 +
  327 +- 统计有消费金额的,按门店按月去重客户数
  328 +
  329 +### 5. 判断新店/老店
  330 +
  331 +- 根据新店保护信息判断是否为新店
  332 +
  333 +### 6. 计算底薪
  334 +
  335 +- 判断业绩是否达标(业绩 ≥ 生命线)
  336 +- 判断人头是否达标(实际人头 ≥ 目标人头)
  337 +- 判断消耗是否达标(实际消耗 ≥ 目标消耗,仅老店考核)
  338 +- 根据未达标指标数量计算扣款
  339 +- 底薪 = 3500 - (未达标指标数 × 500)
  340 +
  341 +### 7. 计算提成
  342 +
  343 +- 判断新店/老店
  344 +- 判断业绩是否超过生命线
  345 +- 根据门店分类(老店)和业绩是否超过生命线,确定阶梯提成比例
  346 +- 计算提成金额(阶梯提成模式)
  347 +
  348 +### 8. 计算最终工资
  349 +
  350 +- 最终工资 = 底薪 + 提成
  351 +
  352 +---
  353 +
  354 +## ⚠️ 注意事项
  355 +
  356 +### 1. 数据校验
  357 +
  358 +- 门店分类必须设置,未设置应报错
  359 +- 门店生命线必须设置,未设置应报错
  360 +- 门店人头目标必须设置(用于考核)
  361 +- 门店消耗目标必须设置(老店考核用)
  362 +
  363 +### 2. 数据一致性
  364 +
  365 +- 门店业绩计算逻辑必须与其他统计接口保持一致
  366 +- 人头统计逻辑必须与其他统计接口保持一致
  367 +- 消耗统计逻辑必须与其他统计接口保持一致
  368 +
  369 +### 3. 边界情况
  370 +
  371 +- 如果门店没有业绩数据,业绩为0
  372 +- 如果门店没有人头数据,人头为0
  373 +- 如果门店没有消耗数据,消耗为0
  374 +- 新店不考核消耗,只考核业绩和人头
  375 +
  376 +### 4. 提成计算注意事项
  377 +
  378 +- **阶梯提成模式**:必须严格按照阶梯提成规则计算,不能使用单一提成比例
  379 +- **业绩分段计算**:
  380 + - 如果业绩 ≤ 生命线:只计算 ≤ 生命线部分的提成
  381 + - 如果业绩 > 生命线:分别计算 ≤ 生命线部分和 > 生命线部分的提成,然后相加
  382 +
  383 +---
  384 +
  385 +## 📋 关键计算公式总结
  386 +
  387 +### 底薪计算公式
  388 +
  389 +```
  390 +底薪 = 3500 - (未达标指标数 × 500)
  391 +```
  392 +
  393 +其中:
  394 +- **老店**:未达标指标数 = 未达标的指标数量(业绩、人头、消耗,最多3个)
  395 +- **新店**:未达标指标数 = 未达标的指标数量(业绩、人头,最多2个)
  396 +
  397 +### 提成计算公式
  398 +
  399 +#### 老店提成计算公式
  400 +
  401 +**A类门店**:
  402 +```
  403 +如果 业绩 ≤ 生命线:
  404 + 提成 = 业绩 × 2%
  405 +如果 业绩 > 生命线:
  406 + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5%
  407 +```
  408 +
  409 +**B类门店**:
  410 +```
  411 +如果 业绩 ≤ 生命线:
  412 + 提成 = 业绩 × 2.5%
  413 +如果 业绩 > 生命线:
  414 + 提成 = 生命线 × 2.5% + (业绩 - 生命线) × 3%
  415 +```
  416 +
  417 +**C类门店**:
  418 +```
  419 +如果 业绩 ≤ 生命线:
  420 + 提成 = 业绩 × 3%
  421 +如果 业绩 > 生命线:
  422 + 提成 = 生命线 × 3% + (业绩 - 生命线) × 3.5%
  423 +```
  424 +
  425 +#### 新店提成计算公式
  426 +
  427 +```
  428 +如果 业绩 ≤ 生命线:
  429 + 提成 = 业绩 × 2%
  430 +如果 业绩 > 生命线:
  431 + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5%
  432 +```
  433 +
  434 +---
  435 +
  436 +## ✅ 验证要点
  437 +
  438 +1. **底薪计算验证**:
  439 + - 验证考核指标判断是否正确
  440 + - 验证扣款金额是否正确(每个未达标指标扣500元)
  441 + - 验证新店不考核消耗
  442 +
  443 +2. **提成计算验证**:
  444 + - 验证阶梯提成计算是否正确
  445 + - 验证业绩分段计算是否正确
  446 + - 验证不同门店分类的提成比例是否正确
  447 + - 验证新店统一提成标准是否正确
  448 +
  449 +3. **数据来源验证**:
  450 + - 验证门店业绩计算是否正确
  451 + - 验证人头统计是否正确
  452 + - 验证消耗统计是否正确
  453 + - 验证新店判断是否正确
  454 +
健康师工资信息说明.html
@@ -192,6 +192,7 @@ @@ -192,6 +192,7 @@
192 <div class="item"><span class="label">其他业绩加:</span><span class="value">4,000.00</span></div> 192 <div class="item"><span class="label">其他业绩加:</span><span class="value">4,000.00</span></div>
193 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div> 193 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div>
194 <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div> 194 <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div>
  195 + <div class="item"><span class="label">队伍总消耗:</span><span class="value">68,582.05</span></div>
195 <div class="item"><span class="label">占比:</span><span class="value">0.18</span></div> 196 <div class="item"><span class="label">占比:</span><span class="value">0.18</span></div>
196 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div> 197 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div>
197 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div> 198 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div>
@@ -206,7 +207,9 @@ @@ -206,7 +207,9 @@
206 <div class="section-title">消耗与项目数据</div> 207 <div class="section-title">消耗与项目数据</div>
207 <div class="grid"> 208 <div class="grid">
208 <div class="item"><span class="label">消耗:</span><span class="value">22,650.24</span></div> 209 <div class="item"><span class="label">消耗:</span><span class="value">22,650.24</span></div>
  210 + <div class="item"><span class="label">日均消耗:</span><span class="value">838.90</span></div>
209 <div class="item"><span class="label">项目数:</span><span class="value">114.00</span></div> 211 <div class="item"><span class="label">项目数:</span><span class="value">114.00</span></div>
  212 + <div class="item"><span class="label">日均项目数:</span><span class="value">4.22</span></div>
210 <div class="item"><span class="label">到店人头:</span><span class="value">57</span></div> 213 <div class="item"><span class="label">到店人头:</span><span class="value">57</span></div>
211 </div> 214 </div>
212 </div> 215 </div>
@@ -237,7 +240,7 @@ @@ -237,7 +240,7 @@
237 <div class="section"> 240 <div class="section">
238 <div class="section-title">底薪与补贴</div> 241 <div class="section-title">底薪与补贴</div>
239 <div class="grid"> 242 <div class="grid">
240 - <div class="item"><span class="label">健康师底薪:</span><span class="value">2,000.00</span></div> 243 + <div class="item"><span class="label">健康师底薪:</span><span class="value">2,200.00</span></div>
241 <div class="item"><span class="label">手工费:</span><span class="value">1,583.00</span></div> 244 <div class="item"><span class="label">手工费:</span><span class="value">1,583.00</span></div>
242 <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div> 245 <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div>
243 <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div> 246 <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div>
@@ -249,7 +252,7 @@ @@ -249,7 +252,7 @@
249 <div class="total-section"> 252 <div class="total-section">
250 <div class="total-row final"> 253 <div class="total-row final">
251 <span>实发工资</span> 254 <span>实发工资</span>
252 - <span>5,050.98</span> 255 + <span>5,250.98</span>
253 </div> 256 </div>
254 </div> 257 </div>
255 </div> 258 </div>
@@ -284,16 +287,16 @@ @@ -284,16 +287,16 @@
284 <div class="calc-item"> 287 <div class="calc-item">
285 <div class="calc-label">6. 顾问提成 (740.39)</div> 288 <div class="calc-label">6. 顾问提成 (740.39)</div>
286 <div class="calc-formula">92,548.30 × 0.8% = 740.39</div> 289 <div class="calc-formula">92,548.30 × 0.8% = 740.39</div>
287 - <div class="calc-note">未达顾问提成标准</div> 290 + <div class="calc-note">高级顾问: 战队业绩(92,548.30)≥6万 且 战队消耗(68,582.05)≥6万 → 提成率 0.8%</div>
288 </div> 291 </div>
289 <div class="calc-item"> 292 <div class="calc-item">
290 - <div class="calc-label">7. 实发工资 (5,050.98)</div>  
291 - <div class="calc-formula">2,000.00 + 1,467.98 + 1,583.00 = 5,050.98</div> 293 + <div class="calc-label">7. 实发工资 (5,250.98)</div>
  294 + <div class="calc-formula">2,200.00 + 1,467.98 + 1,583.00 = 5,250.98</div>
292 <div class="calc-note">底薪 + 提成合计 + 手工费</div> 295 <div class="calc-note">底薪 + 提成合计 + 手工费</div>
293 </div> 296 </div>
294 </div> 297 </div>
295 -  
296 </div> 298 </div>
  299 +
297 300
298 301
299 <!-- 案例: 李芳 --> 302 <!-- 案例: 李芳 -->
@@ -329,6 +332,7 @@ @@ -329,6 +332,7 @@
329 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div> 332 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div>
330 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div> 333 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div>
331 <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div> 334 <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div>
  335 + <div class="item"><span class="label">队伍总消耗:</span><span class="value">68,582.05</span></div>
332 <div class="item"><span class="label">占比:</span><span class="value">0.38</span></div> 336 <div class="item"><span class="label">占比:</span><span class="value">0.38</span></div>
333 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div> 337 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div>
334 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div> 338 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div>
@@ -343,7 +347,9 @@ @@ -343,7 +347,9 @@
343 <div class="section-title">消耗与项目数据</div> 347 <div class="section-title">消耗与项目数据</div>
344 <div class="grid"> 348 <div class="grid">
345 <div class="item"><span class="label">消耗:</span><span class="value">18,341.43</span></div> 349 <div class="item"><span class="label">消耗:</span><span class="value">18,341.43</span></div>
  350 + <div class="item"><span class="label">日均消耗:</span><span class="value">797.45</span></div>
346 <div class="item"><span class="label">项目数:</span><span class="value">96.00</span></div> 351 <div class="item"><span class="label">项目数:</span><span class="value">96.00</span></div>
  352 + <div class="item"><span class="label">日均项目数:</span><span class="value">4.17</span></div>
347 <div class="item"><span class="label">到店人头:</span><span class="value">50</span></div> 353 <div class="item"><span class="label">到店人头:</span><span class="value">50</span></div>
348 </div> 354 </div>
349 </div> 355 </div>
@@ -418,20 +424,20 @@ @@ -418,20 +424,20 @@
418 </div> 424 </div>
419 425
420 426
421 - <!-- 案例: 罗丹 --> 427 + <!-- 案例: 刘恬恬 -->
422 <div class="salary-row"> 428 <div class="salary-row">
423 <div class="salary-card"> 429 <div class="salary-card">
424 <div class="card-header" style="background-color: #007bff;"> 430 <div class="card-header" style="background-color: #007bff;">
425 - <h2>罗丹 <span style="font-size: 14px; font-weight: normal;">(健康师)</span></h2> 431 + <h2>刘恬恬 <span style="font-size: 14px; font-weight: normal;">(健康师)</span></h2>
426 <span class="record-id">ID: 766260517810472197</span> 432 <span class="record-id">ID: 766260517810472197</span>
427 </div> 433 </div>
428 434
429 <div class="section"> 435 <div class="section">
430 <div class="section-title">基本信息</div> 436 <div class="section-title">基本信息</div>
431 <div class="grid"> 437 <div class="grid">
432 - <div class="item"><span class="label">姓名:</span><span class="value">罗丹</span></div> 438 + <div class="item"><span class="label">姓名:</span><span class="value">刘恬恬</span></div>
433 <div class="item"><span class="label">门店:</span><span class="value">绿纤468店</span></div> 439 <div class="item"><span class="label">门店:</span><span class="value">绿纤468店</span></div>
434 - <div class="item"><span class="label">员工ID:</span><span class="value">13540428522</span></div> 440 + <div class="item"><span class="label">员工ID:</span><span class="value">18863128472</span></div>
435 <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div> 441 <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div>
436 <div class="item"><span class="label">岗位:</span><span class="value">健康师</span></div> 442 <div class="item"><span class="label">岗位:</span><span class="value">健康师</span></div>
437 <div class="item"><span class="label">金三角战队:</span><span class="value">精英队 (3人)</span></div> 443 <div class="item"><span class="label">金三角战队:</span><span class="value">精英队 (3人)</span></div>
@@ -444,29 +450,32 @@ @@ -444,29 +450,32 @@
444 <div class="section-title">业绩数据</div> 450 <div class="section-title">业绩数据</div>
445 <div class="grid"> 451 <div class="grid">
446 <div class="item"><span class="label">总业绩:</span><span class="value">40,840.10</span></div> 452 <div class="item"><span class="label">总业绩:</span><span class="value">40,840.10</span></div>
447 - <div class="item"><span class="label">基础业绩:</span><span class="value">23,190.10</span></div>  
448 - <div class="item"><span class="label">合作业绩:</span><span class="value">17,650.00</span></div> 453 + <div class="item"><span class="label">基础业绩:</span><span class="value">28,000.50</span></div>
  454 + <div class="item"><span class="label">合作业绩:</span><span class="value">12,839.60</span></div>
449 <div class="item"><span class="label">基础奖励业绩:</span><span class="value">0.00</span></div> 455 <div class="item"><span class="label">基础奖励业绩:</span><span class="value">0.00</span></div>
450 <div class="item"><span class="label">合作奖励业绩:</span><span class="value">0.00</span></div> 456 <div class="item"><span class="label">合作奖励业绩:</span><span class="value">0.00</span></div>
451 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div> 457 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div>
452 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div> 458 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div>
453 <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div> 459 <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div>
  460 + <div class="item"><span class="label">队伍总消耗:</span><span class="value">68,582.05</span></div>
454 <div class="item"><span class="label">占比:</span><span class="value">0.44</span></div> 461 <div class="item"><span class="label">占比:</span><span class="value">0.44</span></div>
455 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div> 462 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div>
456 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div> 463 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div>
457 <div class="item"><span class="label">升单业绩:</span><span class="value">0.00</span></div> 464 <div class="item"><span class="label">升单业绩:</span><span class="value">0.00</span></div>
458 <div class="item"><span class="label">升单人头数:</span><span class="value">0</span></div> 465 <div class="item"><span class="label">升单人头数:</span><span class="value">0</span></div>
459 - <div class="item"><span class="label">实际基础业绩:</span><span class="value">23,190.10</span></div>  
460 - <div class="item"><span class="label">实际合作业绩:</span><span class="value">17,650.00</span></div> 466 + <div class="item"><span class="label">实际基础业绩:</span><span class="value">28,000.50</span></div>
  467 + <div class="item"><span class="label">实际合作业绩:</span><span class="value">12,839.60</span></div>
461 </div> 468 </div>
462 </div> 469 </div>
463 470
464 <div class="section"> 471 <div class="section">
465 <div class="section-title">消耗与项目数据</div> 472 <div class="section-title">消耗与项目数据</div>
466 <div class="grid"> 473 <div class="grid">
467 - <div class="item"><span class="label">消耗:</span><span class="value">28,095.53</span></div>  
468 - <div class="item"><span class="label">项目数:</span><span class="value">119.00</span></div>  
469 - <div class="item"><span class="label">到店人头:</span><span class="value">64</span></div> 474 + <div class="item"><span class="label">消耗:</span><span class="value">27,590.38</span></div>
  475 + <div class="item"><span class="label">日均消耗:</span><span class="value">1,061.17</span></div>
  476 + <div class="item"><span class="label">项目数:</span><span class="value">101.50</span></div>
  477 + <div class="item"><span class="label">日均项目数:</span><span class="value">3.90</span></div>
  478 + <div class="item"><span class="label">到店人头:</span><span class="value">51</span></div>
470 </div> 479 </div>
471 </div> 480 </div>
472 481
@@ -484,12 +493,12 @@ @@ -484,12 +493,12 @@
484 <div class="section-title">提成计算</div> 493 <div class="section-title">提成计算</div>
485 <div class="grid"> 494 <div class="grid">
486 <div class="item"><span class="label">提点:</span><span class="value">0.05</span></div> 495 <div class="item"><span class="label">提点:</span><span class="value">0.05</span></div>
487 - <div class="item"><span class="label">基础业绩提成:</span><span class="value">1,101.53</span></div>  
488 - <div class="item"><span class="label">合作业绩提成:</span><span class="value">544.94</span></div> 496 + <div class="item"><span class="label">基础业绩提成:</span><span class="value">1,330.02</span></div>
  497 + <div class="item"><span class="label">合作业绩提成:</span><span class="value">396.42</span></div>
489 <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div> 498 <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div>
490 <div class="item"><span class="label">新客业绩提成:</span><span class="value">0.00</span></div> 499 <div class="item"><span class="label">新客业绩提成:</span><span class="value">0.00</span></div>
491 <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div> 500 <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div>
492 - <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">1,646.47</span></div> 501 + <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">1,726.45</span></div>
493 </div> 502 </div>
494 </div> 503 </div>
495 504
@@ -497,7 +506,7 @@ @@ -497,7 +506,7 @@
497 <div class="section-title">底薪与补贴</div> 506 <div class="section-title">底薪与补贴</div>
498 <div class="grid"> 507 <div class="grid">
499 <div class="item"><span class="label">健康师底薪:</span><span class="value">2,000.00</span></div> 508 <div class="item"><span class="label">健康师底薪:</span><span class="value">2,000.00</span></div>
500 - <div class="item"><span class="label">手工费:</span><span class="value">1,310.00</span></div> 509 + <div class="item"><span class="label">手工费:</span><span class="value">1,302.00</span></div>
501 <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div> 510 <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div>
502 <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div> 511 <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div>
503 <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div> 512 <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div>
@@ -508,7 +517,7 @@ @@ -508,7 +517,7 @@
508 <div class="total-section"> 517 <div class="total-section">
509 <div class="total-row final"> 518 <div class="total-row final">
510 <span>实发工资</span> 519 <span>实发工资</span>
511 - <span>4,956.47</span> 520 + <span>5,028.45</span>
512 </div> 521 </div>
513 </div> 522 </div>
514 </div> 523 </div>
@@ -521,18 +530,18 @@ @@ -521,18 +530,18 @@
521 <div class="calc-note">根据提成点表查询得出</div> 530 <div class="calc-note">根据提成点表查询得出</div>
522 </div> 531 </div>
523 <div class="calc-item"> 532 <div class="calc-item">
524 - <div class="calc-label">2. 基础业绩提成 (1,101.53)</div>  
525 - <div class="calc-formula">23,190.10 × 0.95 × 5% = 1,101.53</div> 533 + <div class="calc-label">2. 基础业绩提成 (1,330.02)</div>
  534 + <div class="calc-formula">28,000.50 × 0.95 × 5% = 1,330.02</div>
526 <div class="calc-note">实际基础业绩 × 95% × 提成点</div> 535 <div class="calc-note">实际基础业绩 × 95% × 提成点</div>
527 </div> 536 </div>
528 <div class="calc-item"> 537 <div class="calc-item">
529 - <div class="calc-label">3. 合作业绩提成 (544.94)</div>  
530 - <div class="calc-formula">17,650.00 × 0.95 × 0.65 × 5% = 544.94</div> 538 + <div class="calc-label">3. 合作业绩提成 (396.42)</div>
  539 + <div class="calc-formula">12,839.60 × 0.95 × 0.65 × 5% = 396.42</div>
531 <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div> 540 <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div>
532 </div> 541 </div>
533 <div class="calc-item"> 542 <div class="calc-item">
534 - <div class="calc-label">4. 实发工资 (4,956.47)</div>  
535 - <div class="calc-formula">2,000.00 + 1,646.47 + 1,310.00 = 4,956.47</div> 543 + <div class="calc-label">4. 实发工资 (5,028.45)</div>
  544 + <div class="calc-formula">2,000.00 + 1,726.45 + 1,302.00 = 5,028.45</div>
536 <div class="calc-note">底薪 + 提成合计 + 手工费</div> 545 <div class="calc-note">底薪 + 提成合计 + 手工费</div>
537 </div> 546 </div>
538 </div> 547 </div>
@@ -573,6 +582,7 @@ @@ -573,6 +582,7 @@
573 <div class="item"><span class="label">其他业绩加:</span><span class="value">6,189.21</span></div> 582 <div class="item"><span class="label">其他业绩加:</span><span class="value">6,189.21</span></div>
574 <div class="item"><span class="label">其他业绩减:</span><span class="value">162.07</span></div> 583 <div class="item"><span class="label">其他业绩减:</span><span class="value">162.07</span></div>
575 <div class="item"><span class="label">队伍业绩:</span><span class="value">28,235.40</span></div> 584 <div class="item"><span class="label">队伍业绩:</span><span class="value">28,235.40</span></div>
  585 + <div class="item"><span class="label">队伍总消耗:</span><span class="value">4,199.07</span></div>
576 <div class="item"><span class="label">占比:</span><span class="value">1.00</span></div> 586 <div class="item"><span class="label">占比:</span><span class="value">1.00</span></div>
577 <div class="item"><span class="label">新客业绩:</span><span class="value">7,679.50</span></div> 587 <div class="item"><span class="label">新客业绩:</span><span class="value">7,679.50</span></div>
578 <div class="item"><span class="label">新客转化率:</span><span class="value">0.46</span></div> 588 <div class="item"><span class="label">新客转化率:</span><span class="value">0.46</span></div>
@@ -587,7 +597,9 @@ @@ -587,7 +597,9 @@
587 <div class="section-title">消耗与项目数据</div> 597 <div class="section-title">消耗与项目数据</div>
588 <div class="grid"> 598 <div class="grid">
589 <div class="item"><span class="label">消耗:</span><span class="value">4,199.07</span></div> 599 <div class="item"><span class="label">消耗:</span><span class="value">4,199.07</span></div>
  600 + <div class="item"><span class="label">日均消耗:</span><span class="value">155.52</span></div>
590 <div class="item"><span class="label">项目数:</span><span class="value">89.50</span></div> 601 <div class="item"><span class="label">项目数:</span><span class="value">89.50</span></div>
  602 + <div class="item"><span class="label">日均项目数:</span><span class="value">3.31</span></div>
591 <div class="item"><span class="label">到店人头:</span><span class="value">53</span></div> 603 <div class="item"><span class="label">到店人头:</span><span class="value">53</span></div>
592 </div> 604 </div>
593 </div> 605 </div>
@@ -609,9 +621,9 @@ @@ -609,9 +621,9 @@
609 <div class="item"><span class="label">基础业绩提成:</span><span class="value">642.09</span></div> 621 <div class="item"><span class="label">基础业绩提成:</span><span class="value">642.09</span></div>
610 <div class="item"><span class="label">合作业绩提成:</span><span class="value">45.25</span></div> 622 <div class="item"><span class="label">合作业绩提成:</span><span class="value">45.25</span></div>
611 <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div> 623 <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div>
612 - <div class="item"><span class="label">新客业绩提成:</span><span class="value">1,151.93</span></div> 624 + <div class="item"><span class="label">新客业绩提成:</span><span class="value">1,094.33</span></div>
613 <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div> 625 <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div>
614 - <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">1,839.27</span></div> 626 + <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">1,781.67</span></div>
615 </div> 627 </div>
616 </div> 628 </div>
617 629
@@ -630,7 +642,7 @@ @@ -630,7 +642,7 @@
630 <div class="total-section"> 642 <div class="total-section">
631 <div class="total-row final"> 643 <div class="total-row final">
632 <span>实发工资</span> 644 <span>实发工资</span>
633 - <span>4,953.27</span> 645 + <span>4,895.67</span>
634 </div> 646 </div>
635 </div> 647 </div>
636 </div> 648 </div>
@@ -663,13 +675,13 @@ @@ -663,13 +675,13 @@
663 <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div> 675 <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div>
664 </div> 676 </div>
665 <div class="calc-item"> 677 <div class="calc-item">
666 - <div class="calc-label">6. 新客转化率提成 (1,151.93)</div>  
667 - <div class="calc-formula">7,679.50 × 15% = 1,151.93</div>  
668 - <div class="calc-note">新客业绩 × 转化率提成比例(46% → 15%)</div> 678 + <div class="calc-label">6. 新客转化率提成 (1,094.33)</div>
  679 + <div class="calc-formula">7,679.50 × 15% × 0.95 = 1,094.33</div>
  680 + <div class="calc-note">新客业绩 × 转化率提成比例(46% → 15%) × 0.95</div>
669 </div> 681 </div>
670 <div class="calc-item"> 682 <div class="calc-item">
671 - <div class="calc-label">7. 实发工资 (4,953.27)</div>  
672 - <div class="calc-formula">2,000.00 + 1,839.27 + 1,114.00 = 4,953.27</div> 683 + <div class="calc-label">7. 实发工资 (4,895.67)</div>
  684 + <div class="calc-formula">2,000.00 + 1,781.67 + 1,114.00 = 4,895.67</div>
673 <div class="calc-note">底薪 + 提成合计 + 手工费</div> 685 <div class="calc-note">底薪 + 提成合计 + 手工费</div>
674 </div> 686 </div>
675 </div> 687 </div>
@@ -710,6 +722,7 @@ @@ -710,6 +722,7 @@
710 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div> 722 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div>
711 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div> 723 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div>
712 <div class="item"><span class="label">队伍业绩:</span><span class="value">5,373.70</span></div> 724 <div class="item"><span class="label">队伍业绩:</span><span class="value">5,373.70</span></div>
  725 + <div class="item"><span class="label">队伍总消耗:</span><span class="value">10,102.27</span></div>
713 <div class="item"><span class="label">占比:</span><span class="value">1.00</span></div> 726 <div class="item"><span class="label">占比:</span><span class="value">1.00</span></div>
714 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div> 727 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div>
715 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div> 728 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div>
@@ -724,7 +737,9 @@ @@ -724,7 +737,9 @@
724 <div class="section-title">消耗与项目数据</div> 737 <div class="section-title">消耗与项目数据</div>
725 <div class="grid"> 738 <div class="grid">
726 <div class="item"><span class="label">消耗:</span><span class="value">10,102.27</span></div> 739 <div class="item"><span class="label">消耗:</span><span class="value">10,102.27</span></div>
  740 + <div class="item"><span class="label">日均消耗:</span><span class="value">531.70</span></div>
727 <div class="item"><span class="label">项目数:</span><span class="value">72.00</span></div> 741 <div class="item"><span class="label">项目数:</span><span class="value">72.00</span></div>
  742 + <div class="item"><span class="label">日均项目数:</span><span class="value">3.79</span></div>
728 <div class="item"><span class="label">到店人头:</span><span class="value">59</span></div> 743 <div class="item"><span class="label">到店人头:</span><span class="value">59</span></div>
729 </div> 744 </div>
730 </div> 745 </div>
@@ -776,8 +791,8 @@ @@ -776,8 +791,8 @@
776 <div class="calc-title">计算过程说明</div> 791 <div class="calc-title">计算过程说明</div>
777 <div class="calc-item"> 792 <div class="calc-item">
778 <div class="calc-label">1. 提成资格判定</div> 793 <div class="calc-label">1. 提成资格判定</div>
779 - <div class="calc-formula">出勤19天 < 21天 → 无提成资格</div>  
780 - <div class="calc-note">出勤不足21天,所有提成归零</div> 794 + <div class="calc-formula">出勤19天 < 20天 → 无提成资格</div>
  795 + <div class="calc-note">出勤不足20天,所有提成归零</div>
781 </div> 796 </div>
782 <div class="calc-item"> 797 <div class="calc-item">
783 <div class="calc-label">2. 实发工资 (2,880.00)</div> 798 <div class="calc-label">2. 实发工资 (2,880.00)</div>
@@ -789,5 +804,6 @@ @@ -789,5 +804,6 @@
789 </div> 804 </div>
790 805
791 </div> 806 </div>
  807 + </div>
792 </body> 808 </body>
793 </html> 809 </html>
794 \ No newline at end of file 810 \ No newline at end of file
店助主任工资计算规则梳理.md 0 → 100644
  1 +# 店助主任工资计算规则梳理
  2 +
  3 +## 📋 概述
  4 +
  5 +店助主任工资由以下几个部分组成:
  6 +1. **底薪**:从工资配置表(`lq_gz`)获取,字段为 `dzzrdx`
  7 +2. **提成**:根据门店业绩与门店生命线的比例计算(阶梯提成模式)
  8 +3. **阶段奖励**:根据进店消耗人数是否达到阶段目标(同店助)
  9 +4. **固定奖励**:手机管理费
  10 +
  11 +---
  12 +
  13 +# 第一部分:计算规则
  14 +
  15 +## 💰 工资组成规则
  16 +
  17 +### 1. 底薪规则
  18 +
  19 +**计算规则**:从工资配置表(`lq_gz`)获取,字段为 `dzzrdx`
  20 +
  21 +**重要说明**:
  22 +- 底薪从配置表获取,如果未设置,应使用默认值或报错提示
  23 +- 底薪是固定值,不随门店分类变化
  24 +
  25 +---
  26 +
  27 +### 2. 提成规则
  28 +
  29 +**计算公式**:根据门店业绩与门店生命线的比例确定提成比例,使用阶梯提成模式计算
  30 +
  31 +#### 提成比例规则
  32 +
  33 +| 门店业绩范围 | 提成计算方式 |
  34 +|------------|------------|
  35 +| 门店业绩 < 门店生命线 × 70% | 0%(无提成) |
  36 +| 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% | 0.4%(全部业绩按0.4%计算) |
  37 +| 门店业绩 ≥ 门店生命线 × 100% | **阶梯提成**:<br>- 超过生命线部分:1%<br>- 剩余部分(≤生命线):0.6% |
  38 +
  39 +#### 提成计算示例
  40 +
  41 +**示例1:业绩未达到70%**
  42 +- 门店生命线 = 100,000元
  43 +- 门店业绩 = 60,000元
  44 +- 计算:60,000 < 100,000 × 70% = 70,000
  45 +- 提成金额 = 0元(无提成)
  46 +
  47 +**示例2:业绩在70%-100%之间**
  48 +- 门店生命线 = 100,000元
  49 +- 门店业绩 = 85,000元
  50 +- 计算:70,000 ≤ 85,000 < 100,000
  51 +- 提成金额 = 85,000 × 0.4% = 340元
  52 +
  53 +**示例3:业绩超过100%(阶梯提成)**
  54 +- 门店生命线 = 100,000元
  55 +- 门店业绩 = 150,000元
  56 +- 计算过程:
  57 + 1. 业绩(150,000)> 生命线(100,000)
  58 + 2. ≤生命线部分:100,000 × 0.6% = 600元
  59 + 3. >生命线部分:(150,000 - 100,000) × 1% = 50,000 × 1% = 500元
  60 + 4. 总提成 = 600 + 500 = 1,100元
  61 +
  62 +**重要说明**:
  63 +- 门店生命线必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示
  64 +- 当业绩超过生命线时,必须使用阶梯提成模式,不能使用单一提成比例
  65 +- 提成按门店业绩的百分比计算
  66 +
  67 +---
  68 +
  69 +### 3. 阶段奖励规则
  70 +
  71 +**考核指标**:进店消耗人数(有消费金额的,按门店按月去重客户数)
  72 +
  73 +#### 奖励规则
  74 +
  75 +| 阶段 | 目标人数 | 奖励金额 | 说明 |
  76 +|-----|---------|---------|------|
  77 +| 第一阶段 | 达到目标(如:100人) | 200元/月 | 只要达到第一阶段目标即可获得 |
  78 +| 第二阶段 | 达到目标(如:140人) | 再奖励200元/月 | 在达到第一阶段的基础上,再达到第二阶段目标 |
  79 +| 两个阶段都达到 | - | 总计400元/月 | 第一阶段200元 + 第二阶段200元 |
  80 +
  81 +**奖励说明**:
  82 +- 两个阶段的奖励是累加的,不是互斥的
  83 +- 如果只达到第二阶段但未达到第一阶段,只获得第二阶段奖励(200元)
  84 +- 如果两个阶段都达到,获得总计400元奖励
  85 +
  86 +**统计规则**:
  87 +- 按门店统计(`md` 字段)
  88 +- 只统计有消费金额的记录(消耗金额 > 0)
  89 +- 同一个会员按月去重(`COUNT(DISTINCT hy)`),其中 `hy` 为会员ID
  90 +- 只统计当月有效消耗记录(`F_IsEffective = 1`)
  91 +
  92 +**重要说明**:
  93 +- 阶段目标必须在 `lq_md_target` 表中进行设置,如果未设置,奖励金额为0(不报错)
  94 +- 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人)
  95 +
  96 +---
  97 +
  98 +### 4. 固定奖励规则
  99 +
  100 +**手机管理费**:150元/月
  101 +
  102 +**说明**:
  103 +- 固定金额,无需计算
  104 +- 每月固定发放
  105 +
  106 +---
  107 +
  108 +## 📊 完整计算公式
  109 +
  110 +```
  111 +店助主任月工资 = 底薪 + 提成 + 阶段奖励 + 固定奖励
  112 +
  113 +其中:
  114 +- 底薪 = 从lq_gz.dzzrdx获取
  115 +- 提成 = 根据业绩与生命线比例确定(阶梯提成模式)
  116 +- 阶段奖励 = 根据进店消耗人数是否达到阶段目标(0/200/400元)
  117 +- 固定奖励 = 150元(手机管理费)
  118 +```
  119 +
  120 +---
  121 +
  122 +## ⚠️ 注意事项
  123 +
  124 +1. **门店业绩计算**:
  125 + - 门店业绩 = 开单业绩 - 退卡业绩
  126 + - 只统计当月有效记录(`F_IsEffective = 1`)
  127 +
  128 +2. **进店消耗人数统计**:
  129 + - 必须使用 `COUNT(DISTINCT hy)` 去重(同一个会员按月去重)
  130 + - 只统计有消费金额的记录(消耗金额 > 0)
  131 + - 按门店统计(`md` 字段)
  132 + - 只统计当月有效消耗记录(`F_IsEffective = 1`)
  133 + - 通过关联 `lq_xh_jksyj` 表判断是否有消费金额
  134 +
  135 +3. **阶段目标配置**:
  136 + - 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人)
  137 + - 必须从 `lq_md_target` 表获取对应门店的目标值
  138 + - **重要**:如果 `lq_md_target` 表中未设置阶段目标,奖励金额为0(不报错)
  139 +
  140 +4. **提成比例判断**:
  141 + - 严格按照门店业绩与门店生命线的比例判断
  142 + - 注意边界值:70% 和 100%
  143 + - **重要**:当业绩超过生命线时,必须使用阶梯提成模式(超过部分1%,剩余部分0.6%)
  144 +
  145 +5. **数据一致性**:
  146 + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致
  147 + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致
  148 +
  149 +6. **数据校验要求**:
  150 + - 门店分类(`F_StoreCategory`)必须设置,不允许为NULL,未设置应报错
  151 + - 门店生命线(`F_StoreLifeline`)如果未设置,提成金额为0(不报错)
  152 + - 阶段目标(`F_AssistantHeadcountTargetStage1`、`F_AssistantHeadcountTargetStage2`)如果未设置,奖励金额为0(不报错)
  153 +
  154 +7. **在店天数比例计算**:
  155 + - 如果店助主任中间离职或请假,提成和奖励需要按在店天数比例计算
  156 + - 计算公式:
  157 + - 店助主任提成 = 门店总提成 ÷ 当月天数 × 在店天数
  158 + - 店助主任奖励 = 门店总奖励 ÷ 当月天数 × 在店天数
  159 +
  160 +---
  161 +
  162 +# 第二部分:数据来源说明
  163 +
  164 +## 📊 数据表说明
  165 +
  166 +### 1. `lq_gz` - 工资配置表
  167 +
  168 +**用途**:获取店助主任底薪
  169 +
  170 +**关键字段**:
  171 +- `dzzrdx`:店助主任底薪
  172 +
  173 +**查询逻辑**:
  174 +- 从配置表获取底薪值
  175 +- 如果未设置,应使用默认值或报错提示
  176 +
  177 +---
  178 +
  179 +### 2. `lq_mdxx` - 门店信息表
  180 +
  181 +**用途**:获取门店分类、门店名称等信息
  182 +
  183 +**关键字段**:
  184 +- `F_Id`:门店ID(主键)
  185 +- `dm`:门店名称
  186 +- `F_StoreCategory`:门店分类(1=A类,2=B类,3=C类)
  187 +- `F_StoreType`:门店类型(200平/旗舰店)
  188 +
  189 +**查询逻辑**:
  190 +- 通过门店ID关联获取门店信息
  191 +- 门店分类必须设置,不允许为NULL
  192 +
  193 +---
  194 +
  195 +### 3. `lq_md_target` - 门店目标表
  196 +
  197 +**用途**:获取门店生命线和阶段目标
  198 +
  199 +**关键字段**:
  200 +- `F_StoreId`:门店ID
  201 +- `F_Month`:月份(YYYYMM格式)
  202 +- `F_StoreLifeline`:门店生命线
  203 +- `F_AssistantHeadcountTargetStage1`:店助人头目标数阶段一
  204 +- `F_AssistantHeadcountTargetStage2`:店助人头目标数阶段二
  205 +
  206 +**查询逻辑**:
  207 +- 通过门店ID和月份关联获取目标数据
  208 +- 门店生命线如果未设置,提成金额为0
  209 +- 阶段目标如果未设置,奖励金额为0
  210 +
  211 +---
  212 +
  213 +### 4. `lq_kd_kdjlb` - 开单记录表
  214 +
  215 +**用途**:计算门店开单业绩
  216 +
  217 +**关键字段**:
  218 +- `Djmd`:登记门店(门店ID)
  219 +- `Kdrq`:开单日期
  220 +- `Sfyj`:实付业绩
  221 +- `F_IsEffective`:是否有效(只统计有效记录)
  222 +
  223 +**查询逻辑**:
  224 +- 按门店、月份统计开单业绩总和
  225 +- 只统计有效记录(`F_IsEffective = 1`)
  226 +
  227 +---
  228 +
  229 +### 5. `lq_hytk_hytk` - 退卡信息表
  230 +
  231 +**用途**:计算门店退卡业绩
  232 +
  233 +**关键字段**:
  234 +- `md`:门店编号(门店ID)
  235 +- `tksj`:退卡时间
  236 +- `F_ActualRefundAmount`:实退金额(优先使用)
  237 +- `tkje`:退卡金额(如果实退金额为空则使用)
  238 +- `F_IsEffective`:是否有效(只统计有效记录)
  239 +
  240 +**查询逻辑**:
  241 +- 按门店、月份统计退卡金额总和
  242 +- 优先使用 `F_ActualRefundAmount`,如果为空则使用 `tkje`
  243 +- 只统计有效记录(`F_IsEffective = 1`)
  244 +
  245 +---
  246 +
  247 +### 6. `lq_xh_hyhk` - 会员耗卡表
  248 +
  249 +**用途**:统计进店消耗人数
  250 +
  251 +**关键字段**:
  252 +- `F_Id`:耗卡编号(主键)
  253 +- `md`:门店ID
  254 +- `hy`:会员ID(用于去重统计)
  255 +- `hksj`:耗卡时间
  256 +- `F_IsEffective`:是否有效(只统计有效记录)
  257 +
  258 +**查询逻辑**:
  259 +- 按门店、月份统计去重会员数
  260 +- 只统计有效记录(`F_IsEffective = 1`)
  261 +- 必须关联 `lq_xh_jksyj` 表判断是否有消费金额
  262 +
  263 +---
  264 +
  265 +### 7. `lq_xh_jksyj` - 耗卡健康师业绩表
  266 +
  267 +**用途**:判断是否有消费金额
  268 +
  269 +**关键字段**:
  270 +- `glkdbh`:关联耗卡记录ID(关联 `lq_xh_hyhk.F_Id`)
  271 +- `jksyj`:健康师业绩(消耗金额)
  272 +- `F_IsEffective`:是否有效(只统计有效记录)
  273 +
  274 +**查询逻辑**:
  275 +- 用于判断耗卡记录是否有消费金额(`jksyj > 0`)
  276 +- 只统计有效记录(`F_IsEffective = 1`)
  277 +
  278 +---
  279 +
  280 +### 8. `lq_attendance_summary` - 考勤汇总表
  281 +
  282 +**用途**:获取在店天数
  283 +
  284 +**关键字段**:
  285 +- `F_UserId`:用户ID(员工ID)
  286 +- `F_Year`:年份
  287 +- `F_Month`:月份
  288 +- `F_WorkDays`:出勤天数(在店天数)
  289 +- `F_LeaveDays`:请假天数
  290 +- `F_IsEffective`:是否有效(只统计有效记录)
  291 +
  292 +**查询逻辑**:
  293 +- 通过员工ID、年份、月份获取考勤数据
  294 +- 用于计算按在店天数比例计算的提成和奖励
  295 +
  296 +---
  297 +
  298 +## 📝 数据查询逻辑
  299 +
  300 +### 1. 门店业绩计算
  301 +
  302 +**计算公式**:门店业绩 = 开单业绩 - 退卡业绩
  303 +
  304 +**查询逻辑**:
  305 +```sql
  306 +-- 1. 计算门店开单业绩
  307 +SELECT COALESCE(SUM(sfyj), 0) as BillingPerformance
  308 +FROM lq_kd_kdjlb
  309 +WHERE Djmd = @StoreId
  310 + AND DATE_FORMAT(Kdrq, '%Y%m') = @Month
  311 + AND F_IsEffective = 1
  312 +
  313 +-- 2. 计算门店退卡业绩
  314 +SELECT COALESCE(SUM(COALESCE(F_ActualRefundAmount, tkje, 0)), 0) as RefundPerformance
  315 +FROM lq_hytk_hytk
  316 +WHERE md = @StoreId
  317 + AND DATE_FORMAT(tksj, '%Y%m') = @Month
  318 + AND F_IsEffective = 1
  319 +
  320 +-- 3. 计算门店实际业绩
  321 +门店业绩 = 开单业绩 - 退卡业绩
  322 +```
  323 +
  324 +---
  325 +
  326 +### 2. 进店消耗人数统计
  327 +
  328 +**统计规则**:有消费金额的,按门店按月去重客户数
  329 +
  330 +**查询逻辑**:
  331 +```sql
  332 +-- 统计门店当月进店消耗人数(有消费金额的,按门店按月去重客户数)
  333 +SELECT COUNT(DISTINCT hy) as HeadCount
  334 +FROM lq_xh_hyhk hk
  335 +WHERE hk.md = @StoreId
  336 + AND DATE_FORMAT(hk.hksj, '%Y%m') = @Month
  337 + AND hk.F_IsEffective = 1
  338 + AND EXISTS (
  339 + -- 确保有消费金额(通过关联消耗业绩表判断)
  340 + SELECT 1
  341 + FROM lq_xh_jksyj jksyj
  342 + WHERE jksyj.glkdbh = hk.F_Id
  343 + AND jksyj.F_IsEffective = 1
  344 + AND jksyj.jksyj > 0
  345 + )
  346 +```
  347 +
  348 +**说明**:
  349 +- `md`:门店ID,按门店统计
  350 +- `hy`:会员ID,用于去重统计(同一个会员按月去重)
  351 +- `hksj`:耗卡时间,用于按月过滤
  352 +- `F_IsEffective = 1`:只统计有效记录
  353 +- **重要**:只统计有消费金额的记录(消耗金额 > 0),通过关联 `lq_xh_jksyj` 表判断是否有消费金额
  354 +
  355 +---
  356 +
  357 +### 3. 门店分类获取
  358 +
  359 +**查询逻辑**:
  360 +```sql
  361 +SELECT
  362 + F_Id as StoreId,
  363 + dm as StoreName,
  364 + F_StoreCategory as StoreCategory,
  365 + F_StoreType as StoreType
  366 +FROM lq_mdxx
  367 +WHERE F_Id = @StoreId
  368 +```
  369 +
  370 +**说明**:
  371 +- `F_StoreCategory`:门店分类(1=A类,2=B类,3=C类)
  372 +- 门店分类必须设置,不允许为NULL
  373 +
  374 +---
  375 +
  376 +### 4. 门店生命线和阶段目标获取
  377 +
  378 +**查询逻辑**:
  379 +```sql
  380 +SELECT
  381 + F_StoreLifeline as StoreLifeline,
  382 + F_AssistantHeadcountTargetStage1 as Stage1Target,
  383 + F_AssistantHeadcountTargetStage2 as Stage2Target
  384 +FROM lq_md_target
  385 +WHERE F_StoreId = @StoreId
  386 + AND F_Month = @Month
  387 +```
  388 +
  389 +**说明**:
  390 +- `F_StoreLifeline`:门店生命线,用于计算提成
  391 +- `F_AssistantHeadcountTargetStage1`:第一阶段目标人数
  392 +- `F_AssistantHeadcountTargetStage2`:第二阶段目标人数
  393 +- 如果门店生命线未设置,提成金额为0
  394 +- 如果阶段目标未设置,奖励金额为0
  395 +
  396 +---
  397 +
  398 +### 5. 店助主任底薪获取
  399 +
  400 +**查询逻辑**:
  401 +```sql
  402 +SELECT dzzrdx as BaseSalary
  403 +FROM lq_gz
  404 +LIMIT 1
  405 +```
  406 +
  407 +**说明**:
  408 +- `dzzrdx`:店助主任底薪
  409 +- 从配置表获取,如果未设置,应使用默认值或报错提示
  410 +
  411 +---
  412 +
  413 +## 🔄 店助主任与店助的主要区别
  414 +
  415 +### 1. 底薪规则
  416 +
  417 +| 岗位 | 底薪来源 |
  418 +|-----|---------|
  419 +| 店助 | 根据门店分类确定(A类3000,B类3100,C类3200) |
  420 +| 店助主任 | 从配置表 `lq_gz.dzzrdx` 获取 |
  421 +
  422 +### 2. 提成规则
  423 +
  424 +| 岗位 | 业绩 ≥ 100%时的提成计算 |
  425 +|-----|---------------------|
  426 +| 店助 | 全部业绩按 0.6% 计算 |
  427 +| 店助主任 | **阶梯提成**:<br>- 超过生命线部分:1%<br>- 剩余部分(≤生命线):0.6% |
  428 +
  429 +**示例对比**:
  430 +- 门店生命线 = 100,000元
  431 +- 门店业绩 = 150,000元
  432 +
  433 +**店助提成**:
  434 +- 150,000 × 0.6% = 900元
  435 +
  436 +**店助主任提成**:
  437 +- ≤生命线部分:100,000 × 0.6% = 600元
  438 +- >生命线部分:(150,000 - 100,000) × 1% = 500元
  439 +- 总提成 = 600 + 500 = 1,100元
  440 +
  441 +### 3. 阶段奖励规则
  442 +
  443 +**相同**:两个岗位的阶段奖励规则完全一致
  444 +
  445 +---
  446 +
  447 +## 📅 更新记录
  448 +
  449 +- 2025-01-XX:初始版本,根据项目文档梳理店助主任工资计算规则
  450 +
店助工资表字段设计.md 0 → 100644
  1 +# 店助工资表字段设计
  2 +
  3 +## 📋 表名建议
  4 +`lq_assistant_salary_statistics`(店助工资统计表)
  5 +
  6 +---
  7 +
  8 +## 🔍 字段分类说明
  9 +
  10 +根据店助工资计算规则,参考健康师工资表结构,店助工资表需要包含以下字段:
  11 +
  12 +---
  13 +
  14 +## 一、基础信息字段
  15 +
  16 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  17 +|--------|-----------|------|------|------|
  18 +| 主键ID | F_Id | VARCHAR(50) | 主键,使用YitIdHelper生成 | ✅ |
  19 +| 门店ID | F_StoreId | VARCHAR(50) | 关联门店信息表 | ✅ |
  20 +| 门店名称 | F_StoreName | VARCHAR(200) | 门店名称(冗余字段,便于查询) | ✅ |
  21 +| 核算岗位 | F_Position | VARCHAR(50) | 固定为"店助"或"店助主任" | ✅ |
  22 +| 员工姓名 | F_EmployeeName | VARCHAR(100) | 员工姓名 | ✅ |
  23 +| 员工ID | F_EmployeeId | VARCHAR(50) | 关联BASE_USER表 | ✅ |
  24 +| 统计月份 | F_StatisticsMonth | VARCHAR(20) | 格式:YYYYMM,如:202501 | ✅ |
  25 +| 门店类型 | F_StoreType | INT | 门店类型(200平/旗舰店) | ❌ |
  26 +| 门店类别 | F_StoreCategory | INT | 门店分类(1=A类,2=B类,3=C类) | ✅ |
  27 +| 是否新店 | F_IsNewStore | VARCHAR(10) | 是/否 | ❌ |
  28 +| 新店保护阶段 | F_NewStoreProtectionStage | INT | 新店保护阶段(0/1/2) | ❌ |
  29 +
  30 +---
  31 +
  32 +## 二、业绩相关字段
  33 +
  34 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  35 +|--------|-----------|------|------|------|
  36 +| 门店总业绩 | F_StoreTotalPerformance | DECIMAL(18,2) | 门店开单业绩 - 门店退卡业绩 | ✅ |
  37 +| 门店开单业绩 | F_StoreBillingPerformance | DECIMAL(18,2) | 门店开单业绩总和 | ✅ |
  38 +| 门店退卡业绩 | F_StoreRefundPerformance | DECIMAL(18,2) | 门店退卡业绩总和 | ✅ |
  39 +| 门店生命线 | F_StoreLifeline | DECIMAL(18,2) | 门店生命线(从lq_md_target获取) | ✅ |
  40 +| 业绩完成率 | F_PerformanceCompletionRate | DECIMAL(18,4) | 门店业绩 / 门店生命线 | ✅ |
  41 +
  42 +---
  43 +
  44 +## 三、提成相关字段
  45 +
  46 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  47 +|--------|-----------|------|------|------|
  48 +| 提成比例 | F_CommissionRate | DECIMAL(18,4) | 根据业绩与生命线比例确定(0%/0.4%/0.6%) | ✅ |
  49 +| 提成金额 | F_CommissionAmount | DECIMAL(18,2) | 门店业绩 × 提成比例 | ✅ |
  50 +
  51 +---
  52 +
  53 +## 四、阶段奖励相关字段
  54 +
  55 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  56 +|--------|-----------|------|------|------|
  57 +| 进店消耗人数 | F_HeadCount | INT | 有消费金额的,按门店按月去重客户数 | ✅ |
  58 +| 第一阶段目标人数 | F_Stage1TargetHeadCount | INT | 从lq_md_target获取 | ✅ |
  59 +| 第二阶段目标人数 | F_Stage2TargetHeadCount | INT | 从lq_md_target获取 | ✅ |
  60 +| 是否达到第一阶段 | F_ReachedStage1 | VARCHAR(10) | 是/否 | ✅ |
  61 +| 是否达到第二阶段 | F_ReachedStage2 | VARCHAR(10) | 是/否 | ✅ |
  62 +| 阶段奖励金额 | F_StageRewardAmount | DECIMAL(18,2) | 根据阶段达成情况计算(0/200/400元) | ✅ |
  63 +| 第一阶段奖励 | F_Stage1Reward | DECIMAL(18,2) | 第一阶段奖励金额(0或200元) | ✅ |
  64 +| 第二阶段奖励 | F_Stage2Reward | DECIMAL(18,2) | 第二阶段奖励金额(0或200元) | ✅ |
  65 +
  66 +---
  67 +
  68 +## 五、底薪相关字段
  69 +
  70 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  71 +|--------|-----------|------|------|------|
  72 +| 门店分类 | F_StoreCategory | INT | 门店分类(1=A类,2=B类,3=C类) | ✅ |
  73 +| 底薪金额 | F_BaseSalary | DECIMAL(18,2) | 根据门店分类确定(A类3000,B类3100,C类3200) | ✅ |
  74 +
  75 +---
  76 +
  77 +## 六、固定奖励字段
  78 +
  79 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  80 +|--------|-----------|------|------|------|
  81 +| 手机管理费 | F_PhoneManagementFee | DECIMAL(18,2) | 固定150元/月 | ✅ |
  82 +
  83 +---
  84 +
  85 +## 七、考勤相关字段
  86 +
  87 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  88 +|--------|-----------|------|------|------|
  89 +| 在店天数 | F_WorkingDays | INT | 在店工作天数 | ✅ |
  90 +| 请假天数 | F_LeaveDays | INT | 请假天数 | ✅ |
  91 +
  92 +---
  93 +
  94 +## 八、工资计算字段
  95 +
  96 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  97 +|--------|-----------|------|------|------|
  98 +| 应发工资 | F_GrossSalary | DECIMAL(18,2) | 底薪 + 提成 + 阶段奖励 + 固定奖励 | ✅ |
  99 +| 实发工资 | F_ActualSalary | DECIMAL(18,2) | 应发工资 - 扣款合计 + 补贴合计 + 奖金 | ✅ |
  100 +
  101 +---
  102 +
  103 +## 九、扣款相关字段
  104 +
  105 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  106 +|--------|-----------|------|------|------|
  107 +| 缺卡扣款 | F_MissingCard | DECIMAL(18,2) | 缺卡扣款金额 | ✅ |
  108 +| 迟到扣款 | F_LateArrival | DECIMAL(18,2) | 迟到扣款金额 | ✅ |
  109 +| 请假扣款 | F_LeaveDeduction | DECIMAL(18,2) | 请假扣款金额 | ✅ |
  110 +| 扣社保 | F_SocialInsuranceDeduction | DECIMAL(18,2) | 社保扣款金额 | ✅ |
  111 +| 扣除奖励 | F_RewardDeduction | DECIMAL(18,2) | 扣除奖励金额 | ✅ |
  112 +| 扣住宿费 | F_AccommodationDeduction | DECIMAL(18,2) | 住宿费扣款金额 | ✅ |
  113 +| 扣学习期费用 | F_StudyPeriodDeduction | DECIMAL(18,2) | 学习期费用扣款 | ✅ |
  114 +| 扣工作服费用 | F_WorkClothesDeduction | DECIMAL(18,2) | 工作服费用扣款 | ✅ |
  115 +| 扣款合计 | F_TotalDeduction | DECIMAL(18,2) | 所有扣款金额总和 | ✅ |
  116 +
  117 +---
  118 +
  119 +## 十、补贴相关字段
  120 +
  121 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  122 +|--------|-----------|------|------|------|
  123 +| 当月培训补贴 | F_MonthlyTrainingSubsidy | DECIMAL(18,2) | 当月培训补贴 | ✅ |
  124 +| 当月交通补贴 | F_MonthlyTransportSubsidy | DECIMAL(18,2) | 当月交通补贴 | ✅ |
  125 +| 上月培训补贴 | F_LastMonthTrainingSubsidy | DECIMAL(18,2) | 上月培训补贴 | ✅ |
  126 +| 上月交通补贴 | F_LastMonthTransportSubsidy | DECIMAL(18,2) | 上月交通补贴 | ✅ |
  127 +| 补贴合计 | F_TotalSubsidy | DECIMAL(18,2) | 所有补贴金额总和 | ✅ |
  128 +
  129 +---
  130 +
  131 +## 十一、奖金相关字段
  132 +
  133 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  134 +|--------|-----------|------|------|------|
  135 +| 发奖金 | F_Bonus | DECIMAL(18,2) | 奖金金额 | ✅ |
  136 +| 退手机押金 | F_ReturnPhoneDeposit | DECIMAL(18,2) | 退手机押金金额 | ✅ |
  137 +| 退住宿押金 | F_ReturnAccommodationDeposit | DECIMAL(18,2) | 退住宿押金金额 | ✅ |
  138 +
  139 +---
  140 +
  141 +## 十二、支付相关字段
  142 +
  143 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  144 +|--------|-----------|------|------|------|
  145 +| 当月是否发放 | F_MonthlyPaymentStatus | VARCHAR(20) | 已发放/未发放/部分发放 | ✅ |
  146 +| 支付金额 | F_PaidAmount | DECIMAL(18,2) | 已支付金额 | ✅ |
  147 +| 待支付金额 | F_PendingAmount | DECIMAL(18,2) | 待支付金额 | ✅ |
  148 +| 补发上月 | F_LastMonthSupplement | DECIMAL(18,2) | 补发上月金额 | ✅ |
  149 +| 当月支付总额 | F_MonthlyTotalPayment | DECIMAL(18,2) | 当月支付总额 | ✅ |
  150 +
  151 +---
  152 +
  153 +## 十三、系统字段
  154 +
  155 +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 |
  156 +|--------|-----------|------|------|------|
  157 +| 是否锁定 | F_IsLocked | INT | 0=未锁定,1=已锁定 | ✅ |
  158 +| 创建时间 | F_CreateTime | DATETIME | 创建时间 | ✅ |
  159 +| 更新时间 | F_UpdateTime | DATETIME | 更新时间 | ✅ |
  160 +| 创建人 | F_CreateUser | VARCHAR(50) | 创建人ID | ❌ |
  161 +| 更新人 | F_UpdateUser | VARCHAR(50) | 更新人ID | ❌ |
  162 +
  163 +---
  164 +
  165 +## 📊 字段统计
  166 +
  167 +- **总字段数**:约 60+ 个字段
  168 +- **必填字段**:约 50+ 个(包含奖金、补贴、支付相关字段)
  169 +- **可选字段**:约 10 个(主要是系统字段中的创建人、更新人等)
  170 +
  171 +---
  172 +
  173 +## 🔑 索引建议
  174 +
  175 +1. **主键索引**:`F_Id`(PRIMARY KEY)
  176 +2. **唯一索引**:`F_EmployeeId + F_StatisticsMonth`(确保同一员工同一月份只有一条记录)
  177 +3. **普通索引**:
  178 + - `F_StoreId`(按门店查询)
  179 + - `F_StatisticsMonth`(按月份查询)
  180 + - `F_EmployeeId`(按员工查询)
  181 + - `F_StoreCategory`(按门店分类查询)
  182 +
  183 +---
  184 +
  185 +## 📝 字段说明
  186 +
  187 +### 1. 业绩完成率计算
  188 +```
  189 +业绩完成率 = 门店业绩 / 门店生命线 × 100%
  190 +```
  191 +
  192 +### 2. 提成比例判断
  193 +```
  194 +if (门店业绩 < 门店生命线 × 70%)
  195 + 提成比例 = 0%
  196 +else if (门店业绩 < 门店生命线 × 100%)
  197 + 提成比例 = 0.4%
  198 +else
  199 + 提成比例 = 0.6%
  200 +```
  201 +
  202 +### 3. 阶段奖励计算
  203 +```
  204 +if (进店消耗人数 >= 第二阶段目标)
  205 + 阶段奖励 = 400元(第一阶段200 + 第二阶段200)
  206 +else if (进店消耗人数 >= 第一阶段目标)
  207 + 阶段奖励 = 200元(第一阶段200)
  208 +else
  209 + 阶段奖励 = 0元
  210 +```
  211 +
  212 +### 4. 应发工资计算
  213 +```
  214 +应发工资 = 底薪 + 提成金额 + 阶段奖励金额 + 手机管理费
  215 +```
  216 +
  217 +### 5. 实发工资计算
  218 +```
  219 +实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金
  220 +```
  221 +
  222 +---
  223 +
  224 +## ⚠️ 注意事项
  225 +
  226 +1. **数据校验**:
  227 + - 门店分类必须设置,不允许为NULL
  228 + - 门店生命线必须设置,未设置应报错
  229 + - 阶段目标必须设置,未设置应报错
  230 +
  231 +2. **数据一致性**:
  232 + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致
  233 + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致
  234 +
  235 +3. **字段命名规范**:
  236 + - 所有字段使用 `F_` 前缀
  237 + - 金额字段使用 `DECIMAL(18,2)` 类型
  238 + - 日期字段使用 `DATETIME` 类型
  239 + - 月份字段使用 `VARCHAR(20)` 类型,格式为 YYYYMM
  240 +
  241 +4. **与健康师工资表的差异**:
  242 + - 店助工资表不需要:个人业绩、战队业绩、新客业绩、升单业绩等个人业绩相关字段
  243 + - 店助工资表需要:门店业绩、门店生命线、进店消耗人数、阶段奖励等门店相关字段
  244 + - 店助工资表不需要:顾问提成、门店T区提成等健康师特有字段
  245 + - 店助工资表需要:手机管理费固定奖励字段
  246 +
  247 +---
  248 +
  249 +## 🔗 相关表关联
  250 +
  251 +1. **BASE_USER**:通过 `F_EmployeeId` 关联员工信息
  252 +2. **lq_mdxx**:通过 `F_StoreId` 关联门店信息,获取门店分类
  253 +3. **lq_md_target**:通过 `F_StoreId + F_StatisticsMonth` 关联门店目标,获取门店生命线和阶段目标
  254 +4. **lq_kd_kdjlb**:用于计算门店开单业绩
  255 +5. **lq_hytk_hytk**:用于计算门店退卡业绩
  256 +6. **lq_xh_hyhk**:用于统计进店消耗人数
  257 +7. **lq_xh_jksyj**:用于判断是否有消费金额
  258 +
  259 +---
  260 +
  261 +## 📅 更新记录
  262 +
  263 +- 2025-01-XX:初始版本,根据健康师工资表结构和店助工资计算规则梳理店助工资表字段
  264 +
店助工资计算规则梳理.md 0 → 100644
  1 +# 店助工资计算规则梳理
  2 +
  3 +## 📋 概述
  4 +
  5 +店助工资由以下几个部分组成:
  6 +1. **底薪**:根据门店分类(A、B、C类)按规则计算
  7 +2. **提成**:根据门店业绩与门店生命线的比例计算
  8 +3. **阶段奖励**:根据进店消耗人数是否达到阶段目标
  9 +4. **固定奖励**:手机管理费
  10 +
  11 +---
  12 +
  13 +# 第一部分:计算规则
  14 +
  15 +## 💰 工资组成规则
  16 +
  17 +### 1. 底薪规则
  18 +
  19 +**计算规则**:根据门店分类(A、B、C类)确定底薪金额
  20 +
  21 +| 门店分类 | 底薪金额 |
  22 +|---------|---------|
  23 +| A类门店 | 3000元 |
  24 +| B类门店 | 3100元 |
  25 +| C类门店 | 3200元 |
  26 +
  27 +**重要说明**:
  28 +- 门店分类必须设置,系统不允许设置为NULL
  29 +- 如果门店分类未设置,应在计算工资前进行校验并提示错误
  30 +
  31 +---
  32 +
  33 +### 2. 提成规则
  34 +
  35 +**计算公式**:根据门店业绩与门店生命线的比例确定提成比例,然后按门店业绩的百分比计算提成
  36 +
  37 +#### 提成比例规则
  38 +
  39 +| 门店业绩范围 | 提成比例 |
  40 +|------------|---------|
  41 +| 门店业绩 < 门店生命线 × 70% | 0%(无提成) |
  42 +| 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% | 0.4% |
  43 +| 门店业绩 ≥ 门店生命线 × 100% | 0.6% |
  44 +
  45 +#### 提成计算示例
  46 +
  47 +假设:
  48 +- 门店生命线 = 100,000元
  49 +- 门店业绩 = 85,000元
  50 +
  51 +计算过程:
  52 +1. 判断比例:85,000 / 100,000 = 85%
  53 +2. 判断区间:70% ≤ 85% < 100%
  54 +3. 提成比例:0.4%
  55 +4. 提成金额:85,000 × 0.4% = 340元
  56 +
  57 +**重要说明**:
  58 +- 门店生命线必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示
  59 +- 提成按门店业绩的百分比计算
  60 +
  61 +---
  62 +
  63 +### 3. 阶段奖励规则
  64 +
  65 +**考核指标**:进店消耗人数(有消费金额的,按门店按月去重客户数)
  66 +
  67 +#### 奖励规则
  68 +
  69 +| 阶段 | 目标人数 | 奖励金额 | 说明 |
  70 +|-----|---------|---------|------|
  71 +| 第一阶段 | 达到目标(如:100人) | 200元/月 | 只要达到第一阶段目标即可获得 |
  72 +| 第二阶段 | 达到目标(如:140人) | 再奖励200元/月 | 在达到第一阶段的基础上,再达到第二阶段目标 |
  73 +| 两个阶段都达到 | - | 总计400元/月 | 第一阶段200元 + 第二阶段200元 |
  74 +
  75 +**奖励说明**:
  76 +- 两个阶段的奖励是累加的,不是互斥的
  77 +- 如果只达到第二阶段但未达到第一阶段,只获得第二阶段奖励(200元)
  78 +- 如果两个阶段都达到,获得总计400元奖励
  79 +
  80 +**统计规则**:
  81 +- 按门店统计(`md` 字段)
  82 +- 只统计有消费金额的记录(消耗金额 > 0)
  83 +- 同一个会员按月去重(`COUNT(DISTINCT hy)`),其中 `hy` 为会员ID
  84 +- 只统计当月有效消耗记录(`F_IsEffective = 1`)
  85 +
  86 +**重要说明**:
  87 +- 阶段目标必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示
  88 +- 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人)
  89 +
  90 +---
  91 +
  92 +### 4. 固定奖励规则
  93 +
  94 +**手机管理费**:150元/月
  95 +
  96 +**说明**:
  97 +- 固定金额,无需计算
  98 +- 每月固定发放
  99 +
  100 +---
  101 +
  102 +## 📊 完整计算公式
  103 +
  104 +```
  105 +店助月工资 = 底薪 + 提成 + 阶段奖励 + 固定奖励
  106 +
  107 +其中:
  108 +- 底薪 = 根据门店分类确定(A类3000元,B类3100元,C类3200元)
  109 +- 提成 = 门店业绩 × 提成比例(根据业绩与生命线比例确定)
  110 +- 阶段奖励 = 根据进店消耗人数是否达到阶段目标(0/200/400元)
  111 +- 固定奖励 = 150元(手机管理费)
  112 +```
  113 +
  114 +---
  115 +
  116 +## ⚠️ 注意事项
  117 +
  118 +1. **门店业绩计算**:
  119 + - 门店业绩 = 开单业绩 - 退卡业绩
  120 + - 只统计当月有效记录(`F_IsEffective = 1`)
  121 +
  122 +2. **进店消耗人数统计**:
  123 + - 必须使用 `COUNT(DISTINCT hy)` 去重(同一个会员按月去重)
  124 + - 只统计有消费金额的记录(消耗金额 > 0)
  125 + - 按门店统计(`md` 字段)
  126 + - 只统计当月有效消耗记录(`F_IsEffective = 1`)
  127 + - 通过关联 `lq_xh_jksyj` 表判断是否有消费金额
  128 +
  129 +3. **阶段目标配置**:
  130 + - 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人)
  131 + - 必须从 `lq_md_target` 表获取对应门店的目标值
  132 + - **重要**:如果 `lq_md_target` 表中未设置阶段目标,系统应报错提示
  133 +
  134 +4. **提成比例判断**:
  135 + - 严格按照门店业绩与门店生命线的比例判断
  136 + - 注意边界值:70% 和 100%
  137 + - 提成按门店业绩的百分比计算
  138 +
  139 +5. **数据一致性**:
  140 + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致
  141 + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致
  142 +
  143 +6. **数据校验要求**:
  144 + - 门店分类(`F_StoreCategory`)必须设置,不允许为NULL,未设置应报错
  145 + - 门店生命线(`F_StoreLifeline`)必须设置,未设置应报错
  146 + - 阶段目标(`F_AssistantHeadcountTargetStage1`、`F_AssistantHeadcountTargetStage2`)必须设置,未设置应报错
  147 +
  148 +---
  149 +
  150 +# 第二部分:数据来源说明
  151 +
  152 +## 🔍 核心数据表
  153 +
  154 +### 1. `lq_mdxx` - 门店信息表
  155 +
  156 +**用途**:获取门店分类,用于确定店助底薪
  157 +
  158 +**关键字段**:
  159 +- `F_Id`:门店ID(主键)
  160 +- `F_StoreCategory`:门店分类
  161 + - `1` = A类门店
  162 + - `2` = B类门店
  163 + - `3` = C类门店
  164 +
  165 +**查询条件**:门店ID(`F_Id`)
  166 +
  167 +**查询示例**:
  168 +```sql
  169 +SELECT F_StoreCategory FROM lq_mdxx WHERE F_Id = @StoreId
  170 +```
  171 +
  172 +**代码实现**:
  173 +- 实体类:`LqMdxxEntity.StoreCategory`(对应数据库字段 `F_StoreCategory`)
  174 +- 枚举类:`StoreCategoryEnum`(定义A、B、C类门店)
  175 +- 在工资计算时,已从门店信息中获取 `StoreCategory` 字段(见 `LqSalaryService.cs` 第345行)
  176 +
  177 +---
  178 +
  179 +### 2. `lq_md_target` - 门店目标表
  180 +
  181 +**用途**:获取门店生命线和阶段目标
  182 +
  183 +**关键字段**:
  184 +- `F_StoreId`:门店ID
  185 +- `F_Month`:月份(YYYYMM格式)
  186 +- `F_StoreLifeline`:门店生命线(必须设置,未设置则报错)
  187 +- `F_AssistantHeadcountTargetStage1`:店助第一阶段目标人数(必须设置,未设置则报错)
  188 +- `F_AssistantHeadcountTargetStage2`:店助第二阶段目标人数(必须设置,未设置则报错)
  189 +
  190 +**查询条件**:门店ID(`F_StoreId`)+ 月份(`F_Month`,YYYYMM格式)
  191 +
  192 +**查询示例**:
  193 +```sql
  194 +SELECT
  195 + F_StoreLifeline,
  196 + F_AssistantHeadcountTargetStage1,
  197 + F_AssistantHeadcountTargetStage2
  198 +FROM lq_md_target
  199 +WHERE F_StoreId = @StoreId AND F_Month = @Month
  200 +```
  201 +
  202 +**重要说明**:所有目标字段都必须设置,如果未设置,系统应报错提示
  203 +
  204 +---
  205 +
  206 +### 3. `lq_kd_kdjlb` - 开单记录表
  207 +
  208 +**用途**:计算门店开单业绩
  209 +
  210 +**关键字段**:
  211 +- `sfyj`:实付业绩(用于计算门店开单业绩总和)
  212 +- `Kdrq`:开单日期(用于按月过滤)
  213 +- `F_IsEffective`:是否有效(只统计有效记录)
  214 +
  215 +**查询逻辑**:
  216 +- 按门店、月份汇总 `sfyj` 字段
  217 +- 只统计有效记录(`F_IsEffective = 1`)
  218 +
  219 +---
  220 +
  221 +### 4. `lq_hytk_hytk` - 退卡记录表
  222 +
  223 +**用途**:计算门店退卡业绩
  224 +
  225 +**关键字段**:
  226 +- `F_ActualRefundAmount`:实际退卡金额(用于计算门店退卡业绩总和)
  227 +- `tksj`:退卡时间(用于按月过滤)
  228 +- `md`:门店ID
  229 +- `F_IsEffective`:是否有效(只统计有效记录)
  230 +
  231 +**查询逻辑**:
  232 +- 按门店、月份汇总 `F_ActualRefundAmount` 字段
  233 +- 只统计有效记录(`F_IsEffective = 1`)
  234 +
  235 +---
  236 +
  237 +### 5. `lq_xh_hyhk` - 耗卡记录表
  238 +
  239 +**用途**:统计进店消耗人数
  240 +
  241 +**关键字段**:
  242 +- `F_Id`:耗卡记录ID(主键)
  243 +- `hy`:会员ID(用于去重统计)
  244 +- `md`:门店ID(按门店统计)
  245 +- `hksj`:耗卡时间(用于按月过滤)
  246 +- `F_IsEffective`:是否有效(只统计有效记录)
  247 +
  248 +**查询逻辑**:
  249 +- 按门店、月份统计去重会员数
  250 +- 只统计有效记录(`F_IsEffective = 1`)
  251 +- 只统计有消费金额的记录(通过关联 `lq_xh_jksyj` 表判断)
  252 +
  253 +---
  254 +
  255 +### 6. `lq_xh_jksyj` - 耗卡健康师业绩表
  256 +
  257 +**用途**:判断是否有消费金额
  258 +
  259 +**关键字段**:
  260 +- `glkdbh`:关联耗卡记录ID(关联 `lq_xh_hyhk.F_Id`)
  261 +- `jksyj`:健康师业绩(消耗金额)
  262 +- `F_IsEffective`:是否有效(只统计有效记录)
  263 +
  264 +**查询逻辑**:
  265 +- 用于判断耗卡记录是否有消费金额(`jksyj > 0`)
  266 +- 只统计有效记录(`F_IsEffective = 1`)
  267 +
  268 +---
  269 +
  270 +## 📝 数据查询逻辑
  271 +
  272 +### 1. 门店业绩计算
  273 +
  274 +**计算公式**:门店业绩 = 开单业绩 - 退卡业绩
  275 +
  276 +**查询逻辑**:
  277 +```sql
  278 +-- 1. 计算门店开单业绩
  279 +SELECT COALESCE(SUM(sfyj), 0) as BillingPerformance
  280 +FROM lq_kd_kdjlb
  281 +WHERE Djmd = @StoreId
  282 + AND DATE_FORMAT(Kdrq, '%Y%m') = @Month
  283 + AND F_IsEffective = 1
  284 +
  285 +-- 2. 计算门店退卡业绩
  286 +SELECT COALESCE(SUM(F_ActualRefundAmount), 0) as RefundPerformance
  287 +FROM lq_hytk_hytk
  288 +WHERE md = @StoreId
  289 + AND DATE_FORMAT(tksj, '%Y%m') = @Month
  290 + AND F_IsEffective = 1
  291 +
  292 +-- 3. 计算门店实际业绩
  293 +门店业绩 = 开单业绩 - 退卡业绩
  294 +```
  295 +
  296 +---
  297 +
  298 +### 2. 进店消耗人数统计
  299 +
  300 +**统计规则**:有消费金额的,按门店按月去重客户数
  301 +
  302 +**查询逻辑**:
  303 +```sql
  304 +-- 统计门店当月进店消耗人数(有消费金额的,按门店按月去重客户数)
  305 +SELECT COUNT(DISTINCT hy) as HeadCount
  306 +FROM lq_xh_hyhk hk
  307 +WHERE hk.md = @StoreId
  308 + AND DATE_FORMAT(hk.hksj, '%Y%m') = @Month
  309 + AND hk.F_IsEffective = 1
  310 + AND EXISTS (
  311 + -- 确保有消费金额(通过关联消耗业绩表判断)
  312 + SELECT 1
  313 + FROM lq_xh_jksyj jksyj
  314 + WHERE jksyj.glkdbh = hk.F_Id
  315 + AND jksyj.F_IsEffective = 1
  316 + AND jksyj.jksyj > 0
  317 + )
  318 +```
  319 +
  320 +**说明**:
  321 +- `md`:门店ID,按门店统计
  322 +- `hy`:会员ID,用于去重统计(同一个会员按月去重)
  323 +- `hksj`:耗卡时间,用于按月过滤
  324 +- `F_IsEffective = 1`:只统计有效记录
  325 +- **重要**:只统计有消费金额的记录(消耗金额 > 0),通过关联 `lq_xh_jksyj` 表判断是否有消费金额
  326 +
  327 +---
  328 +
  329 +### 3. 门店分类获取
  330 +
  331 +**查询逻辑**:
  332 +```sql
  333 +-- 获取门店分类
  334 +SELECT F_StoreCategory FROM lq_mdxx WHERE F_Id = @StoreId
  335 +
  336 +-- 根据门店分类确定底薪
  337 +-- F_StoreCategory = 1 → 底薪 = 3000元
  338 +-- F_StoreCategory = 2 → 底薪 = 3100元
  339 +-- F_StoreCategory = 3 → 底薪 = 3200元
  340 +```
  341 +
  342 +**代码实现**:
  343 +```csharp
  344 +// 从门店信息中获取
  345 +var store = await _db.Queryable<LqMdxxEntity>()
  346 + .Where(x => x.Id == storeId)
  347 + .FirstAsync();
  348 +
  349 +int? storeCategory = store.StoreCategory; // 1=A类, 2=B类, 3=C类
  350 +
  351 +// 根据门店分类确定店助底薪
  352 +decimal baseSalary = storeCategory switch
  353 +{
  354 + 1 => 3000m, // A类门店
  355 + 2 => 3100m, // B类门店
  356 + 3 => 3200m, // C类门店
  357 + _ => throw new Exception($"门店分类未设置或无效:门店ID={storeId}")
  358 +};
  359 +```
  360 +
  361 +---
  362 +
  363 +## 🔗 相关代码位置
  364 +
  365 +- **门店信息查询**:`LqMdxxService.cs`
  366 +- **门店分类枚举**:`StoreCategoryEnum.cs`(`netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/`)
  367 +- **门店实体类**:`LqMdxxEntity.cs`(`F_StoreCategory` 字段)
  368 +- **工资计算服务**:`LqSalaryService.cs`(第345行获取 `StoreCategory`)
  369 +- **门店目标查询**:`LqMdTargetService.cs`
  370 +- **门店业绩统计**:`LqStatisticsService.cs` / `LqDailyReportService.cs`
  371 +- **进店消耗人数统计**:`LqStatisticsService.cs` / `LqDailyReportService.cs`
  372 +
  373 +---
  374 +
  375 +## 📅 更新记录
  376 +
  377 +- 2025-01-XX:初始版本,梳理店助工资计算规则
  378 +- 2025-01-XX:更新底薪规则,明确按A、B、C类门店分类确定底薪(A类3000元,B类3100元,C类3200元)
  379 +- 2025-01-XX:明确A、B、C类门店来源:`lq_mdxx` 表的 `F_StoreCategory` 字段(枚举值:1=A类,2=B类,3=C类)
  380 +- 2025-01-XX:明确数据校验要求:门店分类必须设置,`lq_md_target` 表的目标字段必须设置,未设置应报错
  381 +- 2025-01-XX:明确进店消耗人数统计规则:有消费金额的,按门店按月去重,同一个会员按月去重
  382 +- 2025-01-XX:明确提成计算:按门店业绩的百分比计算
  383 +- 2025-01-XX:重新组织文档结构,分为规则部分和数据来源说明部分
项目信息-薪酬规则与名词解释.md
@@ -68,7 +68,6 @@ @@ -68,7 +68,6 @@
68 68
69 ### 通用规则 69 ### 通用规则
70 70
71 -1. **单人业绩提成门槛**:单人业绩 ≤ 6000元,无提成  
72 2. **金三角(战队)标准**:组员当月考勤天数 ≥ 20天,则算战队组成成功,可按战队提成计算 71 2. **金三角(战队)标准**:组员当月考勤天数 ≥ 20天,则算战队组成成功,可按战队提成计算
73 72
74 ### 健康师薪酬规则 73 ### 健康师薪酬规则
@@ -129,6 +128,11 @@ @@ -129,6 +128,11 @@
129 128
130 ### 店助考核规则 129 ### 店助考核规则
131 130
  131 +店助底薪规则
  132 +A类门店:3000 元
  133 +B类门店:3100 元
  134 +C类门店:3200 元
  135 +
132 #### 考核阶段 136 #### 考核阶段
133 每个门店均有两个阶段的考核(如:川师) 137 每个门店均有两个阶段的考核(如:川师)
134 - 第一阶段:100人(进店消耗人数) 138 - 第一阶段:100人(进店消耗人数)
@@ -141,8 +145,8 @@ @@ -141,8 +145,8 @@
141 145
142 #### 提成计算 146 #### 提成计算
143 - 当月门店业绩 < 门店生命线×70% → 无提成 147 - 当月门店业绩 < 门店生命线×70% → 无提成
144 -- 门店生命线×70% ≤ 当月门店业绩 < 门店生命线×100% → 0.4%  
145 -- 当月门店业绩 > 门店生命线 → 0.6% 148 +- 门店生命线×70% ≤ 当月门店业绩 ≤门店生命线×100% → 0.4%
  149 +- 当月门店业绩 > 门店生命线*100%→ 0.6%
146 150
147 #### 固定奖励 151 #### 固定奖励
148 - 手机管理费:150元 152 - 手机管理费:150元