Commit 2beedbc2329147f7443ed6d31dca3938d8efdec9
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元 | ... | ... |