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 12 /// 申请编号
13 13 /// </summary>
14 14 public string id { get; set; }
15   -
  15 +
16 16 /// <summary>
17 17 /// 申请人编号
18 18 /// </summary>
19 19 public string applicationUserId { get; set; }
20   -
  20 +
21 21 /// <summary>
22 22 /// 申请人姓名
23 23 /// </summary>
24 24 public string applicationUserName { get; set; }
25   -
  25 +
26 26 /// <summary>
27 27 /// 申请门店
28 28 /// </summary>
29 29 public string applicationStoreId { get; set; }
30   -
  30 +
31 31 /// <summary>
32 32 /// 申请时间
33 33 /// </summary>
34 34 public DateTime? applicationTime { get; set; }
35   -
  35 +
36 36 /// <summary>
37 37 /// 总金额
38 38 /// </summary>
39 39 public string amount { get; set; }
40   -
  40 +
41 41 /// <summary>
42 42 /// 审批人
43 43 /// </summary>
44 44 public string approveUser { get; set; }
45   -
  45 +
46 46 /// <summary>
47 47 /// 审批结果
48 48 /// </summary>
49 49 public string approveStatus { get; set; }
50   -
  50 +
51 51 /// <summary>
52 52 /// 审批时间
53 53 /// </summary>
54 54 public DateTime? approveTime { get; set; }
55   -
  55 +
56 56 /// <summary>
57 57 /// 关联购买编号
58 58 /// </summary>
59 59 public string purchaseRecordsId { get; set; }
60   -
  60 +
61 61 /// <summary>
62 62 /// 选中的购买记录ID列表(用于更新购买记录的审批单编号)
63 63 /// </summary>
64 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 56 /// 关联购买编号
57 57 /// </summary>
58 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 181 /// 升单提点
182 182 /// </summary>
183 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 16 /// </summary>
17 17 [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
18 18 public string Id { get; set; }
19   -
  19 +
20 20 /// <summary>
21 21 /// 申请人编号
22 22 /// </summary>
23   - [SugarColumn(ColumnName = "F_ApplicationUserId")]
  23 + [SugarColumn(ColumnName = "F_ApplicationUserId")]
24 24 public string ApplicationUserId { get; set; }
25   -
  25 +
26 26 /// <summary>
27 27 /// 申请人姓名
28 28 /// </summary>
29   - [SugarColumn(ColumnName = "F_ApplicationUserName")]
  29 + [SugarColumn(ColumnName = "F_ApplicationUserName")]
30 30 public string ApplicationUserName { get; set; }
31   -
  31 +
32 32 /// <summary>
33 33 /// 申请门店
34 34 /// </summary>
35   - [SugarColumn(ColumnName = "F_ApplicationStoreId")]
  35 + [SugarColumn(ColumnName = "F_ApplicationStoreId")]
36 36 public string ApplicationStoreId { get; set; }
37   -
  37 +
38 38 /// <summary>
39 39 /// 申请时间
40 40 /// </summary>
41   - [SugarColumn(ColumnName = "F_ApplicationTime")]
  41 + [SugarColumn(ColumnName = "F_ApplicationTime")]
42 42 public DateTime? ApplicationTime { get; set; }
43   -
  43 +
44 44 /// <summary>
45 45 /// 总金额
46 46 /// </summary>
47   - [SugarColumn(ColumnName = "F_Amount")]
  47 + [SugarColumn(ColumnName = "F_Amount")]
48 48 public string Amount { get; set; }
49   -
  49 +
50 50 /// <summary>
51 51 /// 审批人
52 52 /// </summary>
53   - [SugarColumn(ColumnName = "F_ApproveUser")]
  53 + [SugarColumn(ColumnName = "F_ApproveUser")]
54 54 public string ApproveUser { get; set; }
55   -
  55 +
56 56 /// <summary>
57 57 /// 审批结果
58 58 /// </summary>
59   - [SugarColumn(ColumnName = "F_ApproveStatus")]
  59 + [SugarColumn(ColumnName = "F_ApproveStatus")]
60 60 public string ApproveStatus { get; set; }
61   -
  61 +
62 62 /// <summary>
63 63 /// 审批时间
64 64 /// </summary>
65   - [SugarColumn(ColumnName = "F_ApproveTime")]
  65 + [SugarColumn(ColumnName = "F_ApproveTime")]
66 66 public DateTime? ApproveTime { get; set; }
67   -
  67 +
68 68 /// <summary>
69 69 /// 关联购买编号
70 70 /// </summary>
71   - [SugarColumn(ColumnName = "F_PurchaseRecordsId")]
  71 + [SugarColumn(ColumnName = "F_PurchaseRecordsId")]
72 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 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 520 /// </summary>
521 521 [SugarColumn(ColumnName = "F_StoreCategory")]
522 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 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 130 output.upgradeLifeBeauty = entity.UpgradeLifeBeauty;
131 131 output.upgradeTechBeauty = entity.UpgradeTechBeauty;
132 132 output.upgradeMedicalBeauty = entity.UpgradeMedicalBeauty;
  133 + output.fileUrl = entity.F_FIleUrl;
133 134 if (!string.IsNullOrEmpty(entity.AppointmentId))
134 135 {
135 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 111 LaundrySupplierId = input.LaundrySupplierId,
112 112 Quantity = input.Quantity,
113 113 LaundryPrice = supplier.LaundryPrice, // 记录历史价格
114   - TotalPrice = 0, // 送出时总费用为0
  114 + TotalPrice = input.Quantity * supplier.LaundryPrice, // 送出时总费用为数量 * 单价
115 115 Remark = input.Remark,
116 116 IsEffective = StatusEnum.有效.GetHashCode(),
117 117 CreateUser = _userManager.UserId,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
... ... @@ -15,6 +15,9 @@ using System.Linq;
15 15 using System.Threading.Tasks;
16 16 using NCC.Extend.Entitys;
17 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 21 using Yitter.IdGenerator;
19 22 using NCC.Common.Helper;
20 23 using NCC.JsonSerialization;
... ... @@ -49,16 +52,85 @@ namespace NCC.Extend.LqReimbursementApplication
49 52 }
50 53  
51 54 /// <summary>
52   - /// 获取报销申请表
  55 + /// 获取报销申请表详情(包含表单和流程信息)
53 56 /// </summary>
54   - /// <param name="id">参数</param>
  57 + /// <param name="id">申请编号</param>
55 58 /// <returns></returns>
56 59 [HttpGet("{id}")]
57 60 public async Task<dynamic> GetInfo(string id)
58 61 {
59 62 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id);
  63 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  64 +
60 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 136 /// <summary>
... ... @@ -76,7 +148,7 @@ namespace NCC.Extend.LqReimbursementApplication
76 148 List<string> queryApproveTime = input.approveTime != null ? input.approveTime.Split(',').ToObeject<List<string>>() : null;
77 149 DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null;
78 150 DateTime? endApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.Last()) : null;
79   - var data = await _db.Queryable<LqReimbursementApplicationEntity>()
  151 + var query = _db.Queryable<LqReimbursementApplicationEntity>()
80 152 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
81 153 .WhereIF(!string.IsNullOrEmpty(input.applicationUserId), p => p.ApplicationUserId.Contains(input.applicationUserId))
82 154 .WhereIF(!string.IsNullOrEmpty(input.applicationUserName), p => p.ApplicationUserName.Contains(input.applicationUserName))
... ... @@ -85,24 +157,60 @@ namespace NCC.Extend.LqReimbursementApplication
85 157 .WhereIF(queryApplicationTime != null, p => p.ApplicationTime <= new DateTime(endApplicationTime.ToDate().Year, endApplicationTime.ToDate().Month, endApplicationTime.ToDate().Day, 23, 59, 59))
86 158 .WhereIF(!string.IsNullOrEmpty(input.amount), p => p.Amount.Contains(input.amount))
87 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 161 // .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0))
90 162 //.WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59))
91 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 216 /// <summary>
... ... @@ -122,11 +230,99 @@ namespace NCC.Extend.LqReimbursementApplication
122 230 //开启事务
123 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 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 326 if (input.selectedPurchaseRecordIds != null && input.selectedPurchaseRecordIds.Count > 0)
131 327 {
132 328 // 先更新ApplicationId
... ... @@ -370,107 +566,582 @@ namespace NCC.Extend.LqReimbursementApplication
370 566 }
371 567  
372 568 /// <summary>
373   - /// 通过审批
  569 + /// 提交审批(进入第一个节点)
374 570 /// </summary>
375 571 /// <param name="id">申请编号</param>
376 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 576 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id);
382 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 590 try
385 591 {
386   - //开启事务
387 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 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 632 _db.CommitTran();
415 633 }
416 634 catch (Exception)
417 635 {
418   - //回滚事务
419 636 _db.RollbackTran();
420 637 throw;
421 638 }
422 639 }
423 640  
424 641 /// <summary>
425   - /// 拒绝审批
  642 + /// 审批操作(通过/不通过/退回)
426 643 /// </summary>
427 644 /// <param name="id">申请编号</param>
  645 + /// <param name="result">审批结果:通过/不通过/退回</param>
  646 + /// <param name="opinion">审批意见</param>
428 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 651 var userInfo = await _userManager.GetUserInfo();
433 652 var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id);
434 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 694 try
437 695 {
438   - //开启事务
439 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 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 860 _db.CommitTran();
467 861 }
468 862 catch (Exception)
469 863 {
470   - //回滚事务
471 864 _db.RollbackTran();
472 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 47 /// <param name="input">查询参数</param>
48 48 /// <returns>健康师工资额外计算分页列表</returns>
49 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 57 if (input.Year.HasValue)
... ... @@ -76,7 +77,7 @@ namespace NCC.Extend
76 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 82 Id = ec.Id,
82 83 EmployeeId = ec.EmployeeId,
... ... @@ -94,9 +95,11 @@ namespace NCC.Extend
94 95 OtherPerformanceAdd = ec.OtherPerformanceAdd,
95 96 OtherPerformanceSubtract = ec.OtherPerformanceSubtract
96 97 })
  98 + .MergeTable()
  99 + .OrderByIF(!string.IsNullOrEmpty(input.sidx), input.sidx + " " + input.sort)
97 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 105 /// <summary>
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
... ... @@ -30,9 +30,9 @@ using System.Threading.Tasks;
30 30 namespace NCC.Extend
31 31 {
32 32 /// <summary>
33   - /// 薪酬服务
  33 + /// 健康师薪酬服务
34 34 /// </summary>
35   - [ApiDescriptionSettings(Tag = "薪酬服务", Name = "LqSalary", Order = 300)]
  35 + [ApiDescriptionSettings(Tag = "健康师薪酬服务", Name = "LqSalary", Order = 300)]
36 36 [Route("api/Extend/[controller]")]
37 37 public class LqSalaryService : IDynamicApiController, ITransient
38 38 {
... ... @@ -52,7 +52,7 @@ namespace NCC.Extend
52 52 /// <param name="input">查询参数</param>
53 53 /// <returns>健康师工资分页列表</returns>
54 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 57 var monthStr = $"{input.Year}{input.Month:D2}";
58 58  
... ... @@ -83,40 +83,93 @@ namespace NCC.Extend
83 83 var list = await query.Select(x => new HealthCoachSalaryOutput
84 84 {
85 85 Id = x.Id,
  86 + StoreId = x.StoreId,
86 87 StoreName = x.StoreName,
  88 + EmployeeId = x.EmployeeId,
87 89 EmployeeName = x.EmployeeName,
88 90 Position = x.Position,
  91 + GoldTriangleId = x.GoldTriangleId,
89 92 GoldTriangleTeam = x.GoldTriangleTeam,
90 93 TotalPerformance = x.TotalPerformance,
91 94 BasePerformance = x.BasePerformance,
92 95 CooperationPerformance = x.CooperationPerformance,
  96 + BaseRewardPerformance = x.BaseRewardPerformance,
  97 + CooperationRewardPerformance = x.CooperationRewardPerformance,
  98 + ActualBasePerformance = x.ActualBasePerformance,
  99 + ActualCooperationPerformance = x.ActualCooperationPerformance,
93 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 114 Consumption = x.Consumption,
95 115 ProjectCount = x.ProjectCount,
96 116 CustomerCount = x.CustomerCount,
97 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 124 TotalCommission = x.TotalCommission,
  125 + HealthCoachBaseSalary = x.HealthCoachBaseSalary,
100 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 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 150 TotalDeduction = x.TotalDeduction,
  151 + Bonus = x.Bonus,
  152 + ReturnPhoneDeposit = x.ReturnPhoneDeposit,
  153 + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
103 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 161 IsLocked = x.IsLocked,
  162 + CreateTime = x.CreateTime,
  163 + CreateUser = x.CreateUser,
105 164 UpdateTime = x.UpdateTime,
  165 + UpdateUser = x.UpdateUser,
106 166 IsNewStore = x.IsNewStore,
107 167 NewStoreProtectionStage = x.NewStoreProtectionStage,
108 168 StoreType = x.StoreType,
109 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 174 .ToPagedListAsync(input.currentPage, input.pageSize);
122 175  
... ... @@ -360,8 +413,8 @@ namespace NCC.Extend
360 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 419 // 2.1.1 填充额外计算数据
367 420 if (extraCalculationDict.ContainsKey(empId))
... ... @@ -419,6 +472,19 @@ namespace NCC.Extend
419 472 salary.WorkingDays = myAtt?.WorkDays ?? 0;
420 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 488 // 2.4 到店人头
423 489 var myHeadcount = headcountList.FirstOrDefault(x => x.PersonId == empId);
424 490 salary.CustomerCount = myHeadcount?.Count ?? 0;
... ... @@ -458,7 +524,7 @@ namespace NCC.Extend
458 524 }
459 525  
460 526 // 3. 处理战队逻辑 (考勤规则)
461   - // 规则:若出勤天数 < 21天,则该健康师不计入战队,按单人计算。
  527 + // 规则:若出勤天数 < 20天,则该健康师不计入战队,按单人计算。
462 528  
463 529 // 按战队分组
464 530 var teamGroups = employeeStats.Values
... ... @@ -473,7 +539,7 @@ namespace NCC.Extend
473 539  
474 540 foreach (var member in group)
475 541 {
476   - if (member.WorkingDays >= 21)
  542 + if (member.WorkingDays >= 20)
477 543 {
478 544 validMembers.Add(member);
479 545 }
... ... @@ -491,13 +557,15 @@ namespace NCC.Extend
491 557 member.Position = "健康师"; // 降级为健康师
492 558 }
493 559  
494   - // 计算有效战队的总业绩
  560 + // 计算有效战队的总业绩和总消耗
495 561 var teamTotalPerformance = validMembers.Sum(x => x.TotalPerformance);
  562 + var teamTotalConsumption = validMembers.Sum(x => x.Consumption);
496 563  
497   - // 更新有效成员的战队业绩
  564 + // 更新有效成员的战队业绩和战队总消耗
498 565 foreach (var member in validMembers)
499 566 {
500 567 member.TeamPerformance = teamTotalPerformance;
  568 + member.TeamTotalConsumption = teamTotalConsumption;
501 569 }
502 570 }
503 571  
... ... @@ -518,11 +586,18 @@ namespace NCC.Extend
518 586 int newStoreStage = salary.NewStoreProtectionStage;
519 587  
520 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 598 // 4.2 提成计算
524   - // 业绩门槛: 个人总业绩 <= 6000 无提成
525   - if (salary.TotalPerformance <= 6000)
  599 + // 业绩门槛: 战队成员个人总业绩 <= 6000 无提成
  600 + if (!string.IsNullOrEmpty(salary.GoldTriangleId) && salary.TotalPerformance <= 6000)
526 601 {
527 602 salary.TotalCommission = 0;
528 603 salary.BasePerformanceCommission = 0;
... ... @@ -564,17 +639,17 @@ namespace NCC.Extend
564 639 {
565 640 if (newStoreStage == 1)
566 641 {
567   - // 第一阶段:计算新客转化率提成
  642 + // 第一阶段:计算新客转化率提成 (需乘以0.95)
568 643 salary.NewCustomerPerformanceCommission = CalculateNewCustomerConversionCommission(
569 644 salary.NewCustomerPerformance,
570   - salary.NewCustomerConversionRate);
  645 + salary.NewCustomerConversionRate) * 0.95m;
571 646 }
572 647 else if (newStoreStage == 2)
573 648 {
574   - // 第二阶段:计算升单人头提成
  649 + // 第二阶段:计算升单人头提成 (需乘以0.95)
575 650 salary.UpgradePerformanceCommission = CalculateUpgradeCustomerCommission(
576 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 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 675 salary.TotalCommission = salary.BasePerformanceCommission
594 676 + salary.CooperationPerformanceCommission
595 677 + salary.ConsultantCommission
596 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 706 /// <summary>
624 707 /// 计算底薪
625 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 730 // 特殊规则:若消耗或项目数中仅一项未达标(0星),底薪按1星(2000元)计算
634 731 // 新店规则:新店底薪最低为1星(2000元),不满足1星按1星算
635 732  
636 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 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 743 int finalStar = Math.Min(starCons, starProj);
647 744  
... ... @@ -713,6 +810,10 @@ namespace NCC.Extend
713 810 // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X%
714 811 // 4. 新店顾问不考核消耗
715 812  
  813 + // 使用传入的 teamMembers 计算总消耗,或者直接使用 TeamTotalConsumption (如果已计算)
  814 + // 但为了保险起见,这里重新计算或使用已有的逻辑
  815 + // 注意:CalculateConsultantCommission 方法签名未变,但逻辑需确认 teamConsumption 来源
  816 + // 在调用此方法前,teamMembers 已经有了 Consumption 数据
716 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 358 [SugarColumn(ColumnName = "F_GW")]
359 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 192 <div class="item"><span class="label">其他业绩加:</span><span class="value">4,000.00</span></div>
193 193 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div>
194 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 196 <div class="item"><span class="label">占比:</span><span class="value">0.18</span></div>
196 197 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div>
197 198 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div>
... ... @@ -206,7 +207,9 @@
206 207 <div class="section-title">消耗与项目数据</div>
207 208 <div class="grid">
208 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 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 213 <div class="item"><span class="label">到店人头:</span><span class="value">57</span></div>
211 214 </div>
212 215 </div>
... ... @@ -237,7 +240,7 @@
237 240 <div class="section">
238 241 <div class="section-title">底薪与补贴</div>
239 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 244 <div class="item"><span class="label">手工费:</span><span class="value">1,583.00</span></div>
242 245 <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div>
243 246 <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div>
... ... @@ -249,7 +252,7 @@
249 252 <div class="total-section">
250 253 <div class="total-row final">
251 254 <span>实发工资</span>
252   - <span>5,050.98</span>
  255 + <span>5,250.98</span>
253 256 </div>
254 257 </div>
255 258 </div>
... ... @@ -284,16 +287,16 @@
284 287 <div class="calc-item">
285 288 <div class="calc-label">6. 顾问提成 (740.39)</div>
286 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 291 </div>
289 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 295 <div class="calc-note">底薪 + 提成合计 + 手工费</div>
293 296 </div>
294 297 </div>
295   -
296 298 </div>
  299 +
297 300  
298 301  
299 302 <!-- 案例: 李芳 -->
... ... @@ -329,6 +332,7 @@
329 332 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div>
330 333 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div>
331 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 336 <div class="item"><span class="label">占比:</span><span class="value">0.38</span></div>
333 337 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div>
334 338 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div>
... ... @@ -343,7 +347,9 @@
343 347 <div class="section-title">消耗与项目数据</div>
344 348 <div class="grid">
345 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 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 353 <div class="item"><span class="label">到店人头:</span><span class="value">50</span></div>
348 354 </div>
349 355 </div>
... ... @@ -418,20 +424,20 @@
418 424 </div>
419 425  
420 426  
421   - <!-- 案例: 罗丹 -->
  427 + <!-- 案例: 刘恬恬 -->
422 428 <div class="salary-row">
423 429 <div class="salary-card">
424 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 432 <span class="record-id">ID: 766260517810472197</span>
427 433 </div>
428 434  
429 435 <div class="section">
430 436 <div class="section-title">基本信息</div>
431 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 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 441 <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div>
436 442 <div class="item"><span class="label">岗位:</span><span class="value">健康师</span></div>
437 443 <div class="item"><span class="label">金三角战队:</span><span class="value">精英队 (3人)</span></div>
... ... @@ -444,29 +450,32 @@
444 450 <div class="section-title">业绩数据</div>
445 451 <div class="grid">
446 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 455 <div class="item"><span class="label">基础奖励业绩:</span><span class="value">0.00</span></div>
450 456 <div class="item"><span class="label">合作奖励业绩:</span><span class="value">0.00</span></div>
451 457 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div>
452 458 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div>
453 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 461 <div class="item"><span class="label">占比:</span><span class="value">0.44</span></div>
455 462 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div>
456 463 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div>
457 464 <div class="item"><span class="label">升单业绩:</span><span class="value">0.00</span></div>
458 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 468 </div>
462 469 </div>
463 470  
464 471 <div class="section">
465 472 <div class="section-title">消耗与项目数据</div>
466 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 479 </div>
471 480 </div>
472 481  
... ... @@ -484,12 +493,12 @@
484 493 <div class="section-title">提成计算</div>
485 494 <div class="grid">
486 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 498 <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div>
490 499 <div class="item"><span class="label">新客业绩提成:</span><span class="value">0.00</span></div>
491 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 502 </div>
494 503 </div>
495 504  
... ... @@ -497,7 +506,7 @@
497 506 <div class="section-title">底薪与补贴</div>
498 507 <div class="grid">
499 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 510 <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div>
502 511 <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div>
503 512 <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div>
... ... @@ -508,7 +517,7 @@
508 517 <div class="total-section">
509 518 <div class="total-row final">
510 519 <span>实发工资</span>
511   - <span>4,956.47</span>
  520 + <span>5,028.45</span>
512 521 </div>
513 522 </div>
514 523 </div>
... ... @@ -521,18 +530,18 @@
521 530 <div class="calc-note">根据提成点表查询得出</div>
522 531 </div>
523 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 535 <div class="calc-note">实际基础业绩 × 95% × 提成点</div>
527 536 </div>
528 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 540 <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div>
532 541 </div>
533 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 545 <div class="calc-note">底薪 + 提成合计 + 手工费</div>
537 546 </div>
538 547 </div>
... ... @@ -573,6 +582,7 @@
573 582 <div class="item"><span class="label">其他业绩加:</span><span class="value">6,189.21</span></div>
574 583 <div class="item"><span class="label">其他业绩减:</span><span class="value">162.07</span></div>
575 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 586 <div class="item"><span class="label">占比:</span><span class="value">1.00</span></div>
577 587 <div class="item"><span class="label">新客业绩:</span><span class="value">7,679.50</span></div>
578 588 <div class="item"><span class="label">新客转化率:</span><span class="value">0.46</span></div>
... ... @@ -587,7 +597,9 @@
587 597 <div class="section-title">消耗与项目数据</div>
588 598 <div class="grid">
589 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 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 603 <div class="item"><span class="label">到店人头:</span><span class="value">53</span></div>
592 604 </div>
593 605 </div>
... ... @@ -609,9 +621,9 @@
609 621 <div class="item"><span class="label">基础业绩提成:</span><span class="value">642.09</span></div>
610 622 <div class="item"><span class="label">合作业绩提成:</span><span class="value">45.25</span></div>
611 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 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 627 </div>
616 628 </div>
617 629  
... ... @@ -630,7 +642,7 @@
630 642 <div class="total-section">
631 643 <div class="total-row final">
632 644 <span>实发工资</span>
633   - <span>4,953.27</span>
  645 + <span>4,895.67</span>
634 646 </div>
635 647 </div>
636 648 </div>
... ... @@ -663,13 +675,13 @@
663 675 <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div>
664 676 </div>
665 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 681 </div>
670 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 685 <div class="calc-note">底薪 + 提成合计 + 手工费</div>
674 686 </div>
675 687 </div>
... ... @@ -710,6 +722,7 @@
710 722 <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div>
711 723 <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div>
712 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 726 <div class="item"><span class="label">占比:</span><span class="value">1.00</span></div>
714 727 <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div>
715 728 <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div>
... ... @@ -724,7 +737,9 @@
724 737 <div class="section-title">消耗与项目数据</div>
725 738 <div class="grid">
726 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 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 743 <div class="item"><span class="label">到店人头:</span><span class="value">59</span></div>
729 744 </div>
730 745 </div>
... ... @@ -776,8 +791,8 @@
776 791 <div class="calc-title">计算过程说明</div>
777 792 <div class="calc-item">
778 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 796 </div>
782 797 <div class="calc-item">
783 798 <div class="calc-label">2. 实发工资 (2,880.00)</div>
... ... @@ -789,5 +804,6 @@
789 804 </div>
790 805  
791 806 </div>
  807 + </div>
792 808 </body>
793 809 </html>
794 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 68  
69 69 ### 通用规则
70 70  
71   -1. **单人业绩提成门槛**:单人业绩 ≤ 6000元,无提成
72 71 2. **金三角(战队)标准**:组员当月考勤天数 ≥ 20天,则算战队组成成功,可按战队提成计算
73 72  
74 73 ### 健康师薪酬规则
... ... @@ -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 138 - 第一阶段:100人(进店消耗人数)
... ... @@ -141,8 +145,8 @@
141 145  
142 146 #### 提成计算
143 147 - 当月门店业绩 < 门店生命线×70% → 无提成
144   -- 门店生命线×70% ≤ 当月门店业绩 < 门店生命线×100% → 0.4%
145   -- 当月门店业绩 > 门店生命线 → 0.6%
  148 +- 门店生命线×70% ≤ 当月门店业绩 ≤门店生命线×100% → 0.4%
  149 +- 当月门店业绩 > 门店生命线*100%→ 0.6%
146 150  
147 151 #### 固定奖励
148 152 - 手机管理费:150元
... ...