From 2beedbc2329147f7443ed6d31dca3938d8efdec9 Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Sun, 7 Dec 2025 21:26:01 +0800 Subject: [PATCH] 修复健康师工资额外计算服务接口错误 --- generate_salary_html.py | 565 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryInput.cs | 32 ++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryOutput.cs | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryInput.cs | 32 ++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationCrInput.cs | 57 ++++++++++++++++++++++++++++++++++++++++++++++----------- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationListOutput.cs | 15 +++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/LqReimbursementApplicationEntity.cs | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_assistant_salary_statistics/LqAssistantSalaryStatisticsEntity.cs | 369 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_director_salary_statistics/LqDirectorSalaryStatisticsEntity.cs | 393 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node/LqReimbursementApplicationNodeEntity.cs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node_user/LqReimbursementApplicationNodeUserEntity.cs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_approval_record/LqReimbursementApprovalRecordEntity.cs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs | 18 ++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs | 533 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs | 519 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs | 1 + netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs | 2 +- netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs | 809 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------- netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs | 13 ++++++++----- netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------- netcore/src/Modularity/System/NCC.System.Entitys/Entity/Permission/UserEntity.cs | 6 ++++++ sql/创建主任工资统计表.sql | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/创建店助工资统计表.sql | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/创建报销多级审批流程表.sql | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/添加BASE_USER表是否在职字段.sql | 10 ++++++++++ 主任工资计算规则梳理.md | 454 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 健康师工资信息说明.html | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------- 店助主任工资计算规则梳理.md | 450 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 店助工资表字段设计.md | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 店助工资计算规则梳理.md | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 项目信息-薪酬规则与名词解释.md | 10 +++++++--- 33 files changed, 5881 insertions(+), 750 deletions(-) delete mode 100644 generate_salary_html.py create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryInput.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryOutput.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryInput.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_assistant_salary_statistics/LqAssistantSalaryStatisticsEntity.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_director_salary_statistics/LqDirectorSalaryStatisticsEntity.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node/LqReimbursementApplicationNodeEntity.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node_user/LqReimbursementApplicationNodeUserEntity.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_approval_record/LqReimbursementApprovalRecordEntity.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs create mode 100644 sql/创建主任工资统计表.sql create mode 100644 sql/创建店助工资统计表.sql create mode 100644 sql/创建报销多级审批流程表.sql create mode 100644 sql/添加BASE_USER表是否在职字段.sql create mode 100644 主任工资计算规则梳理.md create mode 100644 店助主任工资计算规则梳理.md create mode 100644 店助工资表字段设计.md create mode 100644 店助工资计算规则梳理.md diff --git a/generate_salary_html.py b/generate_salary_html.py deleted file mode 100644 index 34a9519..0000000 --- a/generate_salary_html.py +++ /dev/null @@ -1,565 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -生成健康师工资信息说明HTML - 直接使用数据库数据 -""" - -def generate_html(): - # 数据直接来自数据库查询结果 - 不做任何计算,只展示 - employees = [ - { - 'name': '苟小春', 'id': '766260517894358278', 'emp_id': '15828942309', - 'store': '绿纤468店', 'position': '顾问', 'team': '精英队', 'team_count': 3, - 'is_new': '否', 'stage': 0, - 'total_perf': 16526.90, 'base_perf': 9215.00, 'coop_perf': 7311.90, - 'base_reward': 2000.00, 'coop_reward': 1000.00, - 'actual_base': 11215.00, 'actual_coop': 6311.90, # 数据库存储值 - 'team_perf': 92548.30, 'percentage': 0.18, - 'new_cust_perf': 0.00, 'new_cust_rate': 0.00, - 'upgrade_perf': 0.00, 'upgrade_count': 0, - 'other_add': 4000.00, 'other_subtract': 0.00, - 'consumption': 22650.24, 'project_count': 114, 'customer_count': 57, - 'working_days': 27, 'leave_days': 0, - 'point': 0.05, 'base_comm': 532.71, 'coop_comm': 194.88, - 'consultant_comm': 740.39, 'new_cust_comm': 0.00, 'upgrade_comm': 0.00, - 'total_comm': 1467.98, 'base_salary': 2000, 'handwork': 1583, - 'actual_salary': 5050.98 - }, - { - 'name': '李芳', 'id': '766260517806277893', 'emp_id': '18566028067', - 'store': '绿纤468店', 'position': '健康师', 'team': '精英队', 'team_count': 3, - 'is_new': '否', 'stage': 0, - 'total_perf': 35181.30, 'base_perf': 28635.00, 'coop_perf': 6546.30, - 'base_reward': 0.00, 'coop_reward': 0.00, - 'actual_base': 28635.00, 'actual_coop': 6546.30, # 数据库存储值 - 'team_perf': 92548.30, 'percentage': 0.38, - 'new_cust_perf': 0.00, 'new_cust_rate': 0.00, - 'upgrade_perf': 0.00, 'upgrade_count': 0, - 'other_add': 0.00, 'other_subtract': 0.00, - 'consumption': 18341.43, 'project_count': 96, 'customer_count': 50, - 'working_days': 23, 'leave_days': 7, - 'point': 0.05, 'base_comm': 1360.16, 'coop_comm': 202.12, - 'consultant_comm': 0.00, 'new_cust_comm': 0.00, 'upgrade_comm': 0.00, - 'total_comm': 1562.28, 'base_salary': 2000, 'handwork': 1189, - 'actual_salary': 4751.28 - }, - { - 'name': '罗丹', 'id': '766260517810472197', 'emp_id': '13540428522', - 'store': '绿纤468店', 'position': '健康师', 'team': '精英队', 'team_count': 3, - 'is_new': '否', 'stage': 0, - 'total_perf': 40840.10, 'base_perf': 23190.10, 'coop_perf': 17650.00, - 'base_reward': 0.00, 'coop_reward': 0.00, - 'actual_base': 23190.10, 'actual_coop': 17650.00, # 数据库存储值 - 'team_perf': 92548.30, 'percentage': 0.44, - 'new_cust_perf': 0.00, 'new_cust_rate': 0.00, - 'upgrade_perf': 0.00, 'upgrade_count': 0, - 'other_add': 0.00, 'other_subtract': 0.00, - 'consumption': 28095.53, 'project_count': 119, 'customer_count': 64, - 'working_days': 26, 'leave_days': 4, - 'point': 0.05, 'base_comm': 1101.53, 'coop_comm': 544.94, - 'consultant_comm': 0.00, 'new_cust_comm': 0.00, 'upgrade_comm': 0.00, - 'total_comm': 1646.47, 'base_salary': 2000, 'handwork': 1310, - 'actual_salary': 4956.47 - }, - { - 'name': '何玲', 'id': '766260517860803845', 'emp_id': '17628345607', - 'store': '绿纤金沙店', 'position': '健康师', 'team': '何玲', 'team_count': 1, - 'is_new': '是', 'stage': 1, - 'total_perf': 28235.40, 'base_perf': 26318.60, 'coop_perf': 1916.80, - 'base_reward': 7769.10, 'coop_reward': 84.84, - 'actual_base': 16897.14, 'actual_coop': 1831.96, # 数据库存储值 - 'team_perf': 28235.40, 'percentage': 1.00, - 'new_cust_perf': 7679.50, 'new_cust_rate': 0.46, - 'upgrade_perf': 5771.99, 'upgrade_count': 8, - 'other_add': 6189.21, 'other_subtract': 162.07, - 'consumption': 4199.07, 'project_count': 89.50, 'customer_count': 53, - 'working_days': 27, 'leave_days': 0, - 'point': 0.04, 'base_comm': 642.09, 'coop_comm': 45.25, - 'consultant_comm': 0.00, 'new_cust_comm': 1151.93, 'upgrade_comm': 0.00, - 'total_comm': 1839.27, 'base_salary': 2000, 'handwork': 1114, - 'actual_salary': 4953.27 - }, - { - 'name': '汤倩', 'id': '766260517814667397', 'emp_id': '751340541496526085', - 'store': '绿纤荣华南路店', 'position': '健康师', 'team': '个人', 'team_count': 1, - 'is_new': '否', 'stage': 0, - 'total_perf': 5373.70, 'base_perf': 3085.20, 'coop_perf': 2288.50, - 'base_reward': 0.00, 'coop_reward': 0.00, - 'actual_base': 3085.20, 'actual_coop': 2288.50, # 数据库存储值 - 'team_perf': 5373.70, 'percentage': 1.00, - 'new_cust_perf': 0.00, 'new_cust_rate': 0.00, - 'upgrade_perf': 0.00, 'upgrade_count': 0, - 'other_add': 0.00, 'other_subtract': 0.00, - 'consumption': 10102.27, 'project_count': 72, 'customer_count': 59, - 'working_days': 19, 'leave_days': 0, - 'point': 0.00, 'base_comm': 0.00, 'coop_comm': 0.00, - 'consultant_comm': 0.00, 'new_cust_comm': 0.00, 'upgrade_comm': 0.00, - 'total_comm': 0.00, 'base_salary': 2000, 'handwork': 880, - 'actual_salary': 2880.00 - } - ] - - html_parts = [] - - # HTML头部 - html_parts.append(''' - - - - - 绿纤美业 - 健康师工资信息说明 - - - -
-

健康师工资信息说明 (2025年11月)

-''') - - # 为每个员工生成HTML - for emp in employees: - html_parts.append(generate_employee_card(emp)) - - # HTML尾部 - html_parts.append('''
- -''') - - return '\n'.join(html_parts) - - -def generate_employee_card(emp): - """生成单个员工的工资卡片 - 使用数据库实际值""" - - # 确定卡片颜色 - if emp['is_new'] == '是': - header_color = '#2ecc71' - subtitle = f"(新店第{emp['stage']}阶段)" - elif emp['working_days'] < 21: - header_color = '#f39c12' - subtitle = '(出勤不足)' - elif emp['position'] == '顾问': - header_color = '#007bff' - subtitle = '(顾问)' - else: - header_color = '#007bff' - subtitle = '(健康师)' - - card_html = f''' - -
-
-
-

{emp['name']} {subtitle}

- ID: {emp['id']} -
- -
-
基本信息
-
-
姓名:{emp['name']}
-
门店:{emp['store']}
-
员工ID:{emp['emp_id']}
-
统计月份:202511
-
岗位:{emp['position']}
-
金三角战队:{emp['team']} ({emp['team_count']}人)
-
是否新店:{emp['is_new']}
-
新店保护阶段:{emp['stage']}
-
-
- -
-
业绩数据
-
-
总业绩:{emp['total_perf']:,.2f}
-
基础业绩:{emp['base_perf']:,.2f}
-
合作业绩:{emp['coop_perf']:,.2f}
-
基础奖励业绩:{emp['base_reward']:,.2f}
-
合作奖励业绩:{emp['coop_reward']:,.2f}
-
其他业绩加:{emp['other_add']:,.2f}
-
其他业绩减:{emp['other_subtract']:,.2f}
-
队伍业绩:{emp['team_perf']:,.2f}
-
占比:{emp['percentage']:.2f}
-
新客业绩:{emp['new_cust_perf']:,.2f}
-
新客转化率:{emp['new_cust_rate']:.2f}
-
升单业绩:{emp['upgrade_perf']:,.2f}
-
升单人头数:{emp['upgrade_count']:.0f}
-
实际基础业绩:{emp['actual_base']:,.2f}
-
实际合作业绩:{emp['actual_coop']:,.2f}
-
-
- -
-
消耗与项目数据
-
-
消耗:{emp['consumption']:,.2f}
-
项目数:{emp['project_count']:,.2f}
-
到店人头:{emp['customer_count']:.0f}
-
-
- -
-
考勤数据
-
-
在店天数:{emp['working_days']:.0f}
-
请假天数:{emp['leave_days']:.0f}
-
迟到次数:0.00
-
缺卡次数:0.00
-
-
- -
-
提成计算
-
-
提点:{emp['point']:.2f}
-
基础业绩提成:{emp['base_comm']:,.2f}
-
合作业绩提成:{emp['coop_comm']:,.2f}
-
顾问提成:{emp['consultant_comm']:,.2f}
-
新客业绩提成:{emp['new_cust_comm']:,.2f}
-
升单业绩提成:{emp['upgrade_comm']:,.2f}
-
提成合计:{emp['total_comm']:,.2f}
-
-
- -
-
底薪与补贴
-
-
健康师底薪:{emp['base_salary']:,.2f}
-
手工费:{emp['handwork']:,.2f}
-
额外手工费:0.00
-
车补:0.00
-
少休费:0.00
-
全勤奖:0.00
-
-
- -
-
- 实发工资 - {emp['actual_salary']:,.2f} -
-
-
- - {generate_calculation_details(emp)} -
-''' - return card_html - - -def generate_calculation_details(emp): - """生成计算过程说明 - 基于数据库实际值""" - - details_html = '
\n' - details_html += '
计算过程说明
\n' - - step_num = 1 - - # 提成点计算说明 - if emp['point'] > 0: - point_rule = get_commission_point_rule(emp['team_count'], emp['team_perf']) - details_html += f'''
-
{step_num}. 提成点 ({emp['point']:.0%})
-
战队人数({emp['team_count']}人) + 战队业绩({emp['team_perf']:,.2f}) → {point_rule}
-
根据提成点表查询得出
-
-''' - step_num += 1 - else: - details_html += f'''
-
{step_num}. 提成资格判定
-
出勤{emp['working_days']:.0f}天 < 21天 → 无提成资格
-
出勤不足21天,所有提成归零
-
-''' - step_num += 1 - - # 实际业绩计算说明 - if emp['base_reward'] > 0 or emp['other_add'] > 0 or emp['other_subtract'] > 0 or emp['new_cust_perf'] > 0: - details_html += f'''
-
{step_num}. 实际基础业绩计算
-
{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}
-
基础业绩 - 基础奖励业绩 + 其他业绩加 - 其他业绩减 - 新客业绩
-
-''' - step_num += 1 - - if emp['coop_reward'] > 0: - details_html += f'''
-
{step_num}. 实际合作业绩计算
-
{emp['coop_perf']:,.2f} - {emp['coop_reward']:,.2f} = {emp['actual_coop']:,.2f}
-
合作业绩 - 合作奖励业绩
-
-''' - step_num += 1 - - # 基础业绩提成 - if emp['base_comm'] > 0: - details_html += f'''
-
{step_num}. 基础业绩提成 ({emp['base_comm']:,.2f})
-
{emp['actual_base']:,.2f} × 0.95 × {emp['point']:.0%} = {emp['base_comm']:,.2f}
-
实际基础业绩 × 95% × 提成点
-
-''' - step_num += 1 - - # 合作业绩提成 - if emp['coop_comm'] > 0: - details_html += f'''
-
{step_num}. 合作业绩提成 ({emp['coop_comm']:,.2f})
-
{emp['actual_coop']:,.2f} × 0.95 × 0.65 × {emp['point']:.0%} = {emp['coop_comm']:,.2f}
-
实际合作业绩 × 95% × 65% × 提成点
-
-''' - step_num += 1 - - # 顾问提成 - if emp['consultant_comm'] > 0: - consultant_rule = get_consultant_commission_rule(emp['team_perf'], emp['consumption'], emp['is_new']) - details_html += f'''
-
{step_num}. 顾问提成 ({emp['consultant_comm']:,.2f})
-
{emp['team_perf']:,.2f} × 0.8% = {emp['consultant_comm']:,.2f}
-
{consultant_rule}
-
-''' - step_num += 1 - - # 新客转化率提成 - if emp['new_cust_comm'] > 0: - new_cust_rate = get_new_customer_commission_rate(emp['new_cust_rate']) - details_html += f'''
-
{step_num}. 新客转化率提成 ({emp['new_cust_comm']:,.2f})
-
{emp['new_cust_perf']:,.2f} × {new_cust_rate:.0%} = {emp['new_cust_comm']:,.2f}
-
新客业绩 × 转化率提成比例({emp['new_cust_rate']:.0%} → {new_cust_rate:.0%})
-
-''' - step_num += 1 - - # 实发工资 - details_html += f'''
-
{step_num}. 实发工资 ({emp['actual_salary']:,.2f})
-
{emp['base_salary']:,.2f} + {emp['total_comm']:,.2f} + {emp['handwork']:,.2f} = {emp['actual_salary']:,.2f}
-
底薪 + 提成合计 + 手工费
-
-''' - - details_html += '
\n' - return details_html - - -def get_commission_point_rule(team_count, team_perf): - """获取提成点规则说明""" - if team_count >= 3: - if team_perf >= 150000: - return "查表得7% (3人以上,业绩≥15万)" - elif team_perf >= 120000: - return "查表得6% (3人以上,业绩≥12万)" - elif team_perf >= 90000: - return "查表得5% (3人以上,业绩≥9万)" - elif team_perf >= 60000: - return "查表得4% (3人以上,业绩≥6万)" - elif team_perf >= 30000: - return "查表得3% (3人以上,业绩≥3万)" - elif team_count == 2: - if team_perf >= 80000: - return "查表得6% (2人,业绩≥8万)" - elif team_perf >= 60000: - return "查表得5% (2人,业绩≥6万)" - elif team_perf >= 40000: - return "查表得4% (2人,业绩≥4万)" - elif team_perf >= 20000: - return "查表得3% (2人,业绩≥2万)" - else: # 1人 - if team_perf >= 60000: - return "查表得6% (1人,业绩≥6万)" - elif team_perf >= 40000: - return "查表得5% (1人,业绩≥4万)" - elif team_perf >= 20000: - return "查表得4% (1人,业绩≥2万)" - elif team_perf >= 10000: - return "查表得3% (1人,业绩≥1万)" - return "查表得0% (未达标)" - - -def get_consultant_commission_rule(team_perf, consumption, is_new): - """获取顾问提成规则说明""" - if team_perf >= 60000: - if is_new == '是' or consumption >= 60000: - return f"高级顾问: 战队业绩≥6万 且 消耗≥6万(或新店) → 提成0.8%" - if team_perf >= 40000: - if is_new == '是' or consumption >= 40000: - return f"普通顾问: 战队业绩≥4万 且 消耗≥4万(或新店) → 提成0.3%" - return "未达顾问提成标准" - - -def get_new_customer_commission_rate(conversion_rate): - """获取新客转化率提成比例""" - if conversion_rate >= 0.5: - return 0.20 - elif conversion_rate >= 0.45: - return 0.15 - elif conversion_rate >= 0.35: - return 0.10 - elif conversion_rate > 0: - return 0.06 - return 0 - - -if __name__ == '__main__': - html_content = generate_html() - - # 写入文件 - with open('健康师工资信息说明.html', 'w', encoding='utf-8') as f: - f.write(html_content) - - print("HTML文件已生成: 健康师工资信息说明.html") - print("\n数据验证:") - print("- 苟小春: 基础业绩=9,215.00, 基础奖励=2,000.00, 实际基础业绩=11,215.00 ✓") - print("- 苟小春: 合作业绩=7,311.90, 合作奖励=1,000.00, 实际合作业绩=6,311.90 ✓") diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryInput.cs new file mode 100644 index 0000000..2fbcadb --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryInput.cs @@ -0,0 +1,32 @@ +using NCC.Common.Filter; +using System; + +namespace NCC.Extend.Entitys.Dto.LqAssistantSalary +{ + /// + /// 店助工资查询参数 + /// + public class AssistantSalaryInput : PageInputBase + { + /// + /// 年份 + /// + public int Year { get; set; } + + /// + /// 月份 + /// + public int Month { get; set; } + + /// + /// 门店ID(可选,用于筛选特定门店) + /// + public string StoreId { get; set; } + + /// + /// 员工姓名/账号(可选,用于模糊搜索) + /// + public string Keyword { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryOutput.cs new file mode 100644 index 0000000..ab75df1 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryOutput.cs @@ -0,0 +1,216 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqAssistantSalary +{ + /// + /// 店助工资输出 + /// + public class AssistantSalaryOutput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 员工姓名 + /// + public string EmployeeName { get; set; } + + /// + /// 岗位 + /// + public string Position { get; set; } + + /// + /// 门店总业绩 + /// + public decimal StoreTotalPerformance { get; set; } + + /// + /// 门店开单业绩 + /// + public decimal StoreBillingPerformance { get; set; } + + /// + /// 门店退卡业绩 + /// + public decimal StoreRefundPerformance { get; set; } + + /// + /// 门店生命线 + /// + public decimal StoreLifeline { get; set; } + + /// + /// 业绩完成率 + /// + public decimal PerformanceCompletionRate { get; set; } + + /// + /// 提成比例 + /// + public decimal CommissionRate { get; set; } + + /// + /// 提成金额 + /// + public decimal CommissionAmount { get; set; } + + /// + /// 进店消耗人数 + /// + public int HeadCount { get; set; } + + /// + /// 第一阶段目标人数 + /// + public int Stage1TargetHeadCount { get; set; } + + /// + /// 第二阶段目标人数 + /// + public int Stage2TargetHeadCount { get; set; } + + /// + /// 是否达到第一阶段 + /// + public string ReachedStage1 { get; set; } + + /// + /// 是否达到第二阶段 + /// + public string ReachedStage2 { get; set; } + + /// + /// 阶段奖励金额 + /// + public decimal StageRewardAmount { get; set; } + + /// + /// 第一阶段奖励 + /// + public decimal Stage1Reward { get; set; } + + /// + /// 第二阶段奖励 + /// + public decimal Stage2Reward { get; set; } + + /// + /// 底薪金额 + /// + public decimal BaseSalary { get; set; } + + /// + /// 手机管理费 + /// + public decimal PhoneManagementFee { get; set; } + + /// + /// 在店天数 + /// + public int WorkingDays { get; set; } + + /// + /// 请假天数 + /// + public int LeaveDays { get; set; } + + /// + /// 应发工资 + /// + public decimal GrossSalary { get; set; } + + /// + /// 实发工资 + /// + public decimal ActualSalary { get; set; } + + /// + /// 扣款合计 + /// + public decimal TotalDeduction { get; set; } + + /// + /// 补贴合计 + /// + public decimal TotalSubsidy { get; set; } + + /// + /// 发奖金 + /// + public decimal Bonus { get; set; } + + /// + /// 退手机押金 + /// + public decimal ReturnPhoneDeposit { get; set; } + + /// + /// 退住宿押金 + /// + public decimal ReturnAccommodationDeposit { get; set; } + + /// + /// 当月是否发放 + /// + public string MonthlyPaymentStatus { get; set; } + + /// + /// 支付金额 + /// + public decimal PaidAmount { get; set; } + + /// + /// 待支付金额 + /// + public decimal PendingAmount { get; set; } + + /// + /// 补发上月 + /// + public decimal LastMonthSupplement { get; set; } + + /// + /// 当月支付总额 + /// + public decimal MonthlyTotalPayment { get; set; } + + /// + /// 是否锁定 + /// + public int IsLocked { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } + + /// + /// 门店类型 + /// + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + public int? StoreCategory { get; set; } + + /// + /// 是否新店 + /// + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + public int NewStoreProtectionStage { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryInput.cs new file mode 100644 index 0000000..e463c86 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryInput.cs @@ -0,0 +1,32 @@ +using NCC.Common.Filter; +using System; + +namespace NCC.Extend.Entitys.Dto.LqDirectorSalary +{ + /// + /// 主任工资查询参数 + /// + public class DirectorSalaryInput : PageInputBase + { + /// + /// 年份 + /// + public int Year { get; set; } + + /// + /// 月份 + /// + public int Month { get; set; } + + /// + /// 门店ID(可选,用于筛选特定门店) + /// + public string StoreId { get; set; } + + /// + /// 员工姓名/账号(可选,用于模糊搜索) + /// + public string Keyword { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs new file mode 100644 index 0000000..a6a8ca1 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs @@ -0,0 +1,236 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqDirectorSalary +{ + /// + /// 主任工资输出 + /// + public class DirectorSalaryOutput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 员工姓名 + /// + public string EmployeeName { get; set; } + + /// + /// 岗位 + /// + public string Position { get; set; } + + /// + /// 门店总业绩 + /// + public decimal StoreTotalPerformance { get; set; } + + /// + /// 门店开单业绩 + /// + public decimal StoreBillingPerformance { get; set; } + + /// + /// 门店退卡业绩 + /// + public decimal StoreRefundPerformance { get; set; } + + /// + /// 门店生命线 + /// + public decimal StoreLifeline { get; set; } + + /// + /// 业绩完成率 + /// + public decimal PerformanceCompletionRate { get; set; } + + /// + /// 业绩是否达标 + /// + public string PerformanceReached { get; set; } + + /// + /// 人头是否达标 + /// + public string HeadCountReached { get; set; } + + /// + /// 消耗是否达标 + /// + public string ConsumeReached { get; set; } + + /// + /// 考核扣款金额 + /// + public decimal AssessmentDeduction { get; set; } + + /// + /// 未达标指标数量 + /// + public int UnreachedIndicatorCount { get; set; } + + /// + /// 进店消耗人数 + /// + public int HeadCount { get; set; } + + /// + /// 目标人头数 + /// + public decimal TargetHeadCount { get; set; } + + /// + /// 门店消耗金额 + /// + public decimal StoreConsume { get; set; } + + /// + /// 目标消耗金额 + /// + public decimal TargetConsume { get; set; } + + /// + /// ≤生命线部分提成比例 + /// + public decimal CommissionRateBelowLifeline { get; set; } + + /// + /// >生命线部分提成比例 + /// + public decimal CommissionRateAboveLifeline { get; set; } + + /// + /// ≤生命线部分提成金额 + /// + public decimal CommissionAmountBelowLifeline { get; set; } + + /// + /// >生命线部分提成金额 + /// + public decimal CommissionAmountAboveLifeline { get; set; } + + /// + /// 提成总金额 + /// + public decimal TotalCommissionAmount { get; set; } + + /// + /// 底薪金额 + /// + public decimal BaseSalary { get; set; } + + /// + /// 实际底薪 + /// + public decimal ActualBaseSalary { get; set; } + + /// + /// 在店天数 + /// + public int WorkingDays { get; set; } + + /// + /// 请假天数 + /// + public int LeaveDays { get; set; } + + /// + /// 应发工资 + /// + public decimal GrossSalary { get; set; } + + /// + /// 实发工资 + /// + public decimal ActualSalary { get; set; } + + /// + /// 扣款合计 + /// + public decimal TotalDeduction { get; set; } + + /// + /// 补贴合计 + /// + public decimal TotalSubsidy { get; set; } + + /// + /// 发奖金 + /// + public decimal Bonus { get; set; } + + /// + /// 退手机押金 + /// + public decimal ReturnPhoneDeposit { get; set; } + + /// + /// 退住宿押金 + /// + public decimal ReturnAccommodationDeposit { get; set; } + + /// + /// 当月是否发放 + /// + public string MonthlyPaymentStatus { get; set; } + + /// + /// 支付金额 + /// + public decimal PaidAmount { get; set; } + + /// + /// 待支付金额 + /// + public decimal PendingAmount { get; set; } + + /// + /// 补发上月 + /// + public decimal LastMonthSupplement { get; set; } + + /// + /// 当月支付总额 + /// + public decimal MonthlyTotalPayment { get; set; } + + /// + /// 是否锁定 + /// + public int IsLocked { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } + + /// + /// 门店类型 + /// + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + public int? StoreCategory { get; set; } + + /// + /// 是否新店 + /// + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + public int NewStoreProtectionStage { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationCrInput.cs index ca53669..ef85e65 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationCrInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationCrInput.cs @@ -12,56 +12,91 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication /// 申请编号 /// public string id { get; set; } - + /// /// 申请人编号 /// public string applicationUserId { get; set; } - + /// /// 申请人姓名 /// public string applicationUserName { get; set; } - + /// /// 申请门店 /// public string applicationStoreId { get; set; } - + /// /// 申请时间 /// public DateTime? applicationTime { get; set; } - + /// /// 总金额 /// public string amount { get; set; } - + /// /// 审批人 /// public string approveUser { get; set; } - + /// /// 审批结果 /// public string approveStatus { get; set; } - + /// /// 审批时间 /// public DateTime? approveTime { get; set; } - + /// /// 关联购买编号 /// public string purchaseRecordsId { get; set; } - + /// /// 选中的购买记录ID列表(用于更新购买记录的审批单编号) /// public List selectedPurchaseRecordIds { get; set; } - + + /// + /// 节点配置列表(3-5个节点) + /// + public List nodes { get; set; } + } + + /// + /// 审批节点配置 + /// + public class ApprovalNodeConfig + { + /// + /// 节点顺序(1, 2, 3, 4, 5) + /// + public int nodeOrder { get; set; } + + /// + /// 节点名称(如:部门经理审批、财务审批等) + /// + public string nodeName { get; set; } + + /// + /// 审批类型(会签/或签) + /// + public string approvalType { get; set; } + + /// + /// 审批人ID列表(可多选) + /// + public List approverIds { get; set; } + + /// + /// 审批人姓名列表(用于显示) + /// + public List approverNames { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationListOutput.cs index ccc6243..2d190ad 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationListOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationListOutput.cs @@ -56,6 +56,21 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication /// 关联购买编号 /// public string purchaseRecordsId { get; set; } + + /// + /// 当前审批人(多个用逗号分隔) + /// + public string currentApprovers { get; set; } + + /// + /// 当前节点顺序 + /// + public int? currentNodeOrder { get; set; } + + /// + /// 节点总数 + /// + public int? nodeCount { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs index 97799c9..aadbbbb 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs @@ -181,5 +181,270 @@ namespace NCC.Extend.Entitys.Dto.LqSalary /// 升单提点 /// public decimal UpgradePoint { get; set; } + + /// + /// 基础奖励业绩 + /// + public decimal BaseRewardPerformance { get; set; } + + /// + /// 合作奖励业绩 + /// + public decimal CooperationRewardPerformance { get; set; } + + /// + /// 日均消耗 + /// + public decimal DailyAverageConsumption { get; set; } + + /// + /// 日均项目数 + /// + public decimal DailyAverageProjectCount { get; set; } + + /// + /// 战队总消耗 + /// + public decimal TeamTotalConsumption { get; set; } + + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 员工ID + /// + public string EmployeeId { get; set; } + + /// + /// 金三角ID + /// + public string GoldTriangleId { get; set; } + + /// + /// 门店总业绩 + /// + public decimal StoreTotalPerformance { get; set; } + + /// + /// 队伍业绩 + /// + public decimal TeamPerformance { get; set; } + + /// + /// 占比 + /// + public decimal Percentage { get; set; } + + /// + /// 其他业绩加 + /// + public decimal OtherPerformanceAdd { get; set; } + + /// + /// 其他业绩减 + /// + public decimal OtherPerformanceSubtract { get; set; } + + /// + /// 请假天数 + /// + public decimal LeaveDays { get; set; } + + /// + /// 提点 + /// + public decimal CommissionPoint { get; set; } + + /// + /// 基础业绩提成 + /// + public decimal BasePerformanceCommission { get; set; } + + /// + /// 合作业绩提成 + /// + public decimal CooperationPerformanceCommission { get; set; } + + /// + /// 顾问提成 + /// + public decimal ConsultantCommission { get; set; } + + /// + /// 门店T区提成 + /// + public decimal StoreTZoneCommission { get; set; } + + /// + /// 额外手工费 + /// + public decimal OutherHandworkFee { get; set; } + + /// + /// 车补 + /// + public decimal TransportationAllowance { get; set; } + + /// + /// 少休费 + /// + public decimal LessRest { get; set; } + + /// + /// 全勤奖 + /// + public decimal FullAttendance { get; set; } + + /// + /// 核算应发工资 + /// + public decimal CalculatedGrossSalary { get; set; } + + /// + /// 保底工资 + /// + public decimal GuaranteedSalary { get; set; } + + /// + /// 保底请假扣款 + /// + public decimal GuaranteedLeaveDeduction { get; set; } + + /// + /// 保底底薪 + /// + public decimal GuaranteedBaseSalary { get; set; } + + /// + /// 保底补差 + /// + public decimal GuaranteedSupplement { get; set; } + + /// + /// 最终应发工资 + /// + public decimal FinalGrossSalary { get; set; } + + /// + /// 当月培训补贴 + /// + public decimal MonthlyTrainingSubsidy { get; set; } + + /// + /// 当月交通补贴 + /// + public decimal MonthlyTransportSubsidy { get; set; } + + /// + /// 上月培训补贴 + /// + public decimal LastMonthTrainingSubsidy { get; set; } + + /// + /// 上月交通补贴 + /// + public decimal LastMonthTransportSubsidy { get; set; } + + /// + /// 缺卡扣款 + /// + public decimal MissingCard { get; set; } + + /// + /// 迟到扣款 + /// + public decimal LateArrival { get; set; } + + /// + /// 请假扣款 + /// + public decimal LeaveDeduction { get; set; } + + /// + /// 扣社保 + /// + public decimal SocialInsuranceDeduction { get; set; } + + /// + /// 扣除奖励 + /// + public decimal RewardDeduction { get; set; } + + /// + /// 扣住宿费 + /// + public decimal AccommodationDeduction { get; set; } + + /// + /// 扣学习期费用 + /// + public decimal StudyPeriodDeduction { get; set; } + + /// + /// 扣工作服费用 + /// + public decimal WorkClothesDeduction { get; set; } + + /// + /// 发奖金 + /// + public decimal Bonus { get; set; } + + /// + /// 退手机押金 + /// + public decimal ReturnPhoneDeposit { get; set; } + + /// + /// 退住宿押金 + /// + public decimal ReturnAccommodationDeposit { get; set; } + + /// + /// 当月是否发放 + /// + public string MonthlyPaymentStatus { get; set; } + + /// + /// 支付金额 + /// + public decimal PaidAmount { get; set; } + + /// + /// 待支付金额 + /// + public decimal PendingAmount { get; set; } + + /// + /// 补发上月 + /// + public decimal LastMonthSupplement { get; set; } + + /// + /// 当月支付总额 + /// + public decimal MonthlyTotalPayment { get; set; } + + /// + /// 统计月份(YYYYMM) + /// + public string StatisticsMonth { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } + + /// + /// 创建人 + /// + public string CreateUser { get; set; } + + /// + /// 更新人 + /// + public string UpdateUser { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/LqReimbursementApplicationEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/LqReimbursementApplicationEntity.cs index 201fcac..a220086 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/LqReimbursementApplicationEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/LqReimbursementApplicationEntity.cs @@ -16,60 +16,96 @@ namespace NCC.Extend.Entitys /// [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] public string Id { get; set; } - + /// /// 申请人编号 /// - [SugarColumn(ColumnName = "F_ApplicationUserId")] + [SugarColumn(ColumnName = "F_ApplicationUserId")] public string ApplicationUserId { get; set; } - + /// /// 申请人姓名 /// - [SugarColumn(ColumnName = "F_ApplicationUserName")] + [SugarColumn(ColumnName = "F_ApplicationUserName")] public string ApplicationUserName { get; set; } - + /// /// 申请门店 /// - [SugarColumn(ColumnName = "F_ApplicationStoreId")] + [SugarColumn(ColumnName = "F_ApplicationStoreId")] public string ApplicationStoreId { get; set; } - + /// /// 申请时间 /// - [SugarColumn(ColumnName = "F_ApplicationTime")] + [SugarColumn(ColumnName = "F_ApplicationTime")] public DateTime? ApplicationTime { get; set; } - + /// /// 总金额 /// - [SugarColumn(ColumnName = "F_Amount")] + [SugarColumn(ColumnName = "F_Amount")] public string Amount { get; set; } - + /// /// 审批人 /// - [SugarColumn(ColumnName = "F_ApproveUser")] + [SugarColumn(ColumnName = "F_ApproveUser")] public string ApproveUser { get; set; } - + /// /// 审批结果 /// - [SugarColumn(ColumnName = "F_ApproveStatus")] + [SugarColumn(ColumnName = "F_ApproveStatus")] public string ApproveStatus { get; set; } - + /// /// 审批时间 /// - [SugarColumn(ColumnName = "F_ApproveTime")] + [SugarColumn(ColumnName = "F_ApproveTime")] public DateTime? ApproveTime { get; set; } - + /// /// 关联购买编号 /// - [SugarColumn(ColumnName = "F_PurchaseRecordsId")] + [SugarColumn(ColumnName = "F_PurchaseRecordsId")] public string PurchaseRecordsId { get; set; } - + + /// + /// 节点数量(3,4,5) + /// + [SugarColumn(ColumnName = "F_NodeCount")] + public int? NodeCount { get; set; } + + /// + /// 当前审批节点(0-待审批,1-N节点,N+1-已完成) + /// + [SugarColumn(ColumnName = "F_CurrentNodeOrder")] + public int? CurrentNodeOrder { get; set; } + + /// + /// 当前节点ID + /// + [SugarColumn(ColumnName = "F_CurrentNodeId")] + public string CurrentNodeId { get; set; } + + /// + /// 审批状态(待审批/审批中/已通过/未通过/已退回) + /// + [SugarColumn(ColumnName = "F_ApprovalStatus")] + public string ApprovalStatus { get; set; } + + /// + /// 退回节点 + /// + [SugarColumn(ColumnName = "F_ReturnedNodeOrder")] + public int? ReturnedNodeOrder { get; set; } + + /// + /// 退回原因 + /// + [SugarColumn(ColumnName = "F_ReturnedReason")] + public string ReturnedReason { get; set; } + } } \ No newline at end of file diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_assistant_salary_statistics/LqAssistantSalaryStatisticsEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_assistant_salary_statistics/LqAssistantSalaryStatisticsEntity.cs new file mode 100644 index 0000000..d1aacdc --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_assistant_salary_statistics/LqAssistantSalaryStatisticsEntity.cs @@ -0,0 +1,369 @@ +using System; +using NCC.Common.Const; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_assistant_salary_statistics +{ + /// + /// 店助工资统计表 + /// + [SugarTable("lq_assistant_salary_statistics")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqAssistantSalaryStatisticsEntity + { + /// + /// 主键ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 门店ID + /// + [SugarColumn(ColumnName = "F_StoreId")] + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + [SugarColumn(ColumnName = "F_StoreName")] + public string StoreName { get; set; } + + /// + /// 核算岗位 + /// + [SugarColumn(ColumnName = "F_Position")] + public string Position { get; set; } + + /// + /// 员工姓名 + /// + [SugarColumn(ColumnName = "F_EmployeeName")] + public string EmployeeName { get; set; } + + /// + /// 员工ID + /// + [SugarColumn(ColumnName = "F_EmployeeId")] + public string EmployeeId { get; set; } + + /// + /// 统计月份(YYYYMM) + /// + [SugarColumn(ColumnName = "F_StatisticsMonth")] + public string StatisticsMonth { get; set; } + + /// + /// 门店类型 + /// + [SugarColumn(ColumnName = "F_StoreType")] + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + [SugarColumn(ColumnName = "F_StoreCategory")] + public int? StoreCategory { get; set; } + + /// + /// 是否新店 + /// + [SugarColumn(ColumnName = "F_IsNewStore")] + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")] + public int NewStoreProtectionStage { get; set; } + + /// + /// 门店总业绩 + /// + [SugarColumn(ColumnName = "F_StoreTotalPerformance")] + public decimal StoreTotalPerformance { get; set; } + + /// + /// 门店开单业绩 + /// + [SugarColumn(ColumnName = "F_StoreBillingPerformance")] + public decimal StoreBillingPerformance { get; set; } + + /// + /// 门店退卡业绩 + /// + [SugarColumn(ColumnName = "F_StoreRefundPerformance")] + public decimal StoreRefundPerformance { get; set; } + + /// + /// 门店生命线 + /// + [SugarColumn(ColumnName = "F_StoreLifeline")] + public decimal StoreLifeline { get; set; } + + /// + /// 业绩完成率 + /// + [SugarColumn(ColumnName = "F_PerformanceCompletionRate")] + public decimal PerformanceCompletionRate { get; set; } + + /// + /// 提成比例 + /// + [SugarColumn(ColumnName = "F_CommissionRate")] + public decimal CommissionRate { get; set; } + + /// + /// 提成金额 + /// + [SugarColumn(ColumnName = "F_CommissionAmount")] + public decimal CommissionAmount { get; set; } + + /// + /// 进店消耗人数 + /// + [SugarColumn(ColumnName = "F_HeadCount")] + public int HeadCount { get; set; } + + /// + /// 第一阶段目标人数 + /// + [SugarColumn(ColumnName = "F_Stage1TargetHeadCount")] + public int Stage1TargetHeadCount { get; set; } + + /// + /// 第二阶段目标人数 + /// + [SugarColumn(ColumnName = "F_Stage2TargetHeadCount")] + public int Stage2TargetHeadCount { get; set; } + + /// + /// 是否达到第一阶段 + /// + [SugarColumn(ColumnName = "F_ReachedStage1")] + public string ReachedStage1 { get; set; } + + /// + /// 是否达到第二阶段 + /// + [SugarColumn(ColumnName = "F_ReachedStage2")] + public string ReachedStage2 { get; set; } + + /// + /// 阶段奖励金额 + /// + [SugarColumn(ColumnName = "F_StageRewardAmount")] + public decimal StageRewardAmount { get; set; } + + /// + /// 第一阶段奖励 + /// + [SugarColumn(ColumnName = "F_Stage1Reward")] + public decimal Stage1Reward { get; set; } + + /// + /// 第二阶段奖励 + /// + [SugarColumn(ColumnName = "F_Stage2Reward")] + public decimal Stage2Reward { get; set; } + + /// + /// 底薪金额 + /// + [SugarColumn(ColumnName = "F_BaseSalary")] + public decimal BaseSalary { get; set; } + + /// + /// 手机管理费 + /// + [SugarColumn(ColumnName = "F_PhoneManagementFee")] + public decimal PhoneManagementFee { get; set; } + + /// + /// 在店天数 + /// + [SugarColumn(ColumnName = "F_WorkingDays")] + public int WorkingDays { get; set; } + + /// + /// 请假天数 + /// + [SugarColumn(ColumnName = "F_LeaveDays")] + public int LeaveDays { get; set; } + + /// + /// 应发工资 + /// + [SugarColumn(ColumnName = "F_GrossSalary")] + public decimal GrossSalary { get; set; } + + /// + /// 实发工资 + /// + [SugarColumn(ColumnName = "F_ActualSalary")] + public decimal ActualSalary { get; set; } + + /// + /// 缺卡扣款 + /// + [SugarColumn(ColumnName = "F_MissingCard")] + public decimal MissingCard { get; set; } + + /// + /// 迟到扣款 + /// + [SugarColumn(ColumnName = "F_LateArrival")] + public decimal LateArrival { get; set; } + + /// + /// 请假扣款 + /// + [SugarColumn(ColumnName = "F_LeaveDeduction")] + public decimal LeaveDeduction { get; set; } + + /// + /// 扣社保 + /// + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")] + public decimal SocialInsuranceDeduction { get; set; } + + /// + /// 扣除奖励 + /// + [SugarColumn(ColumnName = "F_RewardDeduction")] + public decimal RewardDeduction { get; set; } + + /// + /// 扣住宿费 + /// + [SugarColumn(ColumnName = "F_AccommodationDeduction")] + public decimal AccommodationDeduction { get; set; } + + /// + /// 扣学习期费用 + /// + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")] + public decimal StudyPeriodDeduction { get; set; } + + /// + /// 扣工作服费用 + /// + [SugarColumn(ColumnName = "F_WorkClothesDeduction")] + public decimal WorkClothesDeduction { get; set; } + + /// + /// 扣款合计 + /// + [SugarColumn(ColumnName = "F_TotalDeduction")] + public decimal TotalDeduction { get; set; } + + /// + /// 当月培训补贴 + /// + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")] + public decimal MonthlyTrainingSubsidy { get; set; } + + /// + /// 当月交通补贴 + /// + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")] + public decimal MonthlyTransportSubsidy { get; set; } + + /// + /// 上月培训补贴 + /// + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")] + public decimal LastMonthTrainingSubsidy { get; set; } + + /// + /// 上月交通补贴 + /// + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")] + public decimal LastMonthTransportSubsidy { get; set; } + + /// + /// 补贴合计 + /// + [SugarColumn(ColumnName = "F_TotalSubsidy")] + public decimal TotalSubsidy { get; set; } + + /// + /// 发奖金 + /// + [SugarColumn(ColumnName = "F_Bonus")] + public decimal Bonus { get; set; } + + /// + /// 退手机押金 + /// + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")] + public decimal ReturnPhoneDeposit { get; set; } + + /// + /// 退住宿押金 + /// + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")] + public decimal ReturnAccommodationDeposit { get; set; } + + /// + /// 当月是否发放 + /// + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")] + public string MonthlyPaymentStatus { get; set; } + + /// + /// 支付金额 + /// + [SugarColumn(ColumnName = "F_PaidAmount")] + public decimal PaidAmount { get; set; } + + /// + /// 待支付金额 + /// + [SugarColumn(ColumnName = "F_PendingAmount")] + public decimal PendingAmount { get; set; } + + /// + /// 补发上月 + /// + [SugarColumn(ColumnName = "F_LastMonthSupplement")] + public decimal LastMonthSupplement { get; set; } + + /// + /// 当月支付总额 + /// + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")] + public decimal MonthlyTotalPayment { get; set; } + + /// + /// 是否锁定(0未锁定,1已锁定) + /// + [SugarColumn(ColumnName = "F_IsLocked")] + public int IsLocked { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime CreateTime { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(ColumnName = "F_UpdateTime")] + public DateTime UpdateTime { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "F_CreateUser")] + public string CreateUser { get; set; } + + /// + /// 更新人 + /// + [SugarColumn(ColumnName = "F_UpdateUser")] + public string UpdateUser { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_director_salary_statistics/LqDirectorSalaryStatisticsEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_director_salary_statistics/LqDirectorSalaryStatisticsEntity.cs new file mode 100644 index 0000000..3f48dab --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_director_salary_statistics/LqDirectorSalaryStatisticsEntity.cs @@ -0,0 +1,393 @@ +using System; +using NCC.Common.Const; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_director_salary_statistics +{ + /// + /// 主任工资统计表 + /// + [SugarTable("lq_director_salary_statistics")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqDirectorSalaryStatisticsEntity + { + /// + /// 主键ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 门店ID + /// + [SugarColumn(ColumnName = "F_StoreId")] + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + [SugarColumn(ColumnName = "F_StoreName")] + public string StoreName { get; set; } + + /// + /// 核算岗位 + /// + [SugarColumn(ColumnName = "F_Position")] + public string Position { get; set; } + + /// + /// 员工姓名 + /// + [SugarColumn(ColumnName = "F_EmployeeName")] + public string EmployeeName { get; set; } + + /// + /// 员工ID + /// + [SugarColumn(ColumnName = "F_EmployeeId")] + public string EmployeeId { get; set; } + + /// + /// 统计月份(YYYYMM) + /// + [SugarColumn(ColumnName = "F_StatisticsMonth")] + public string StatisticsMonth { get; set; } + + /// + /// 门店类型 + /// + [SugarColumn(ColumnName = "F_StoreType")] + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + [SugarColumn(ColumnName = "F_StoreCategory")] + public int? StoreCategory { get; set; } + + /// + /// 是否新店 + /// + [SugarColumn(ColumnName = "F_IsNewStore")] + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")] + public int NewStoreProtectionStage { get; set; } + + /// + /// 门店总业绩 + /// + [SugarColumn(ColumnName = "F_StoreTotalPerformance")] + public decimal StoreTotalPerformance { get; set; } + + /// + /// 门店开单业绩 + /// + [SugarColumn(ColumnName = "F_StoreBillingPerformance")] + public decimal StoreBillingPerformance { get; set; } + + /// + /// 门店退卡业绩 + /// + [SugarColumn(ColumnName = "F_StoreRefundPerformance")] + public decimal StoreRefundPerformance { get; set; } + + /// + /// 门店生命线 + /// + [SugarColumn(ColumnName = "F_StoreLifeline")] + public decimal StoreLifeline { get; set; } + + /// + /// 业绩完成率 + /// + [SugarColumn(ColumnName = "F_PerformanceCompletionRate")] + public decimal PerformanceCompletionRate { get; set; } + + /// + /// 业绩是否达标 + /// + [SugarColumn(ColumnName = "F_PerformanceReached")] + public string PerformanceReached { get; set; } + + /// + /// 人头是否达标 + /// + [SugarColumn(ColumnName = "F_HeadCountReached")] + public string HeadCountReached { get; set; } + + /// + /// 消耗是否达标 + /// + [SugarColumn(ColumnName = "F_ConsumeReached")] + public string ConsumeReached { get; set; } + + /// + /// 考核扣款金额 + /// + [SugarColumn(ColumnName = "F_AssessmentDeduction")] + public decimal AssessmentDeduction { get; set; } + + /// + /// 未达标指标数量 + /// + [SugarColumn(ColumnName = "F_UnreachedIndicatorCount")] + public int UnreachedIndicatorCount { get; set; } + + /// + /// 进店消耗人数 + /// + [SugarColumn(ColumnName = "F_HeadCount")] + public int HeadCount { get; set; } + + /// + /// 目标人头数 + /// + [SugarColumn(ColumnName = "F_TargetHeadCount")] + public decimal TargetHeadCount { get; set; } + + /// + /// 门店消耗金额 + /// + [SugarColumn(ColumnName = "F_StoreConsume")] + public decimal StoreConsume { get; set; } + + /// + /// 目标消耗金额 + /// + [SugarColumn(ColumnName = "F_TargetConsume")] + public decimal TargetConsume { get; set; } + + /// + /// ≤生命线部分提成比例 + /// + [SugarColumn(ColumnName = "F_CommissionRateBelowLifeline")] + public decimal CommissionRateBelowLifeline { get; set; } + + /// + /// >生命线部分提成比例 + /// + [SugarColumn(ColumnName = "F_CommissionRateAboveLifeline")] + public decimal CommissionRateAboveLifeline { get; set; } + + /// + /// ≤生命线部分提成金额 + /// + [SugarColumn(ColumnName = "F_CommissionAmountBelowLifeline")] + public decimal CommissionAmountBelowLifeline { get; set; } + + /// + /// >生命线部分提成金额 + /// + [SugarColumn(ColumnName = "F_CommissionAmountAboveLifeline")] + public decimal CommissionAmountAboveLifeline { get; set; } + + /// + /// 提成总金额 + /// + [SugarColumn(ColumnName = "F_TotalCommissionAmount")] + public decimal TotalCommissionAmount { get; set; } + + /// + /// 底薪金额 + /// + [SugarColumn(ColumnName = "F_BaseSalary")] + public decimal BaseSalary { get; set; } + + /// + /// 实际底薪 + /// + [SugarColumn(ColumnName = "F_ActualBaseSalary")] + public decimal ActualBaseSalary { get; set; } + + /// + /// 在店天数 + /// + [SugarColumn(ColumnName = "F_WorkingDays")] + public int WorkingDays { get; set; } + + /// + /// 请假天数 + /// + [SugarColumn(ColumnName = "F_LeaveDays")] + public int LeaveDays { get; set; } + + /// + /// 应发工资 + /// + [SugarColumn(ColumnName = "F_GrossSalary")] + public decimal GrossSalary { get; set; } + + /// + /// 实发工资 + /// + [SugarColumn(ColumnName = "F_ActualSalary")] + public decimal ActualSalary { get; set; } + + /// + /// 缺卡扣款 + /// + [SugarColumn(ColumnName = "F_MissingCard")] + public decimal MissingCard { get; set; } + + /// + /// 迟到扣款 + /// + [SugarColumn(ColumnName = "F_LateArrival")] + public decimal LateArrival { get; set; } + + /// + /// 请假扣款 + /// + [SugarColumn(ColumnName = "F_LeaveDeduction")] + public decimal LeaveDeduction { get; set; } + + /// + /// 扣社保 + /// + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")] + public decimal SocialInsuranceDeduction { get; set; } + + /// + /// 扣除奖励 + /// + [SugarColumn(ColumnName = "F_RewardDeduction")] + public decimal RewardDeduction { get; set; } + + /// + /// 扣住宿费 + /// + [SugarColumn(ColumnName = "F_AccommodationDeduction")] + public decimal AccommodationDeduction { get; set; } + + /// + /// 扣学习期费用 + /// + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")] + public decimal StudyPeriodDeduction { get; set; } + + /// + /// 扣工作服费用 + /// + [SugarColumn(ColumnName = "F_WorkClothesDeduction")] + public decimal WorkClothesDeduction { get; set; } + + /// + /// 扣款合计 + /// + [SugarColumn(ColumnName = "F_TotalDeduction")] + public decimal TotalDeduction { get; set; } + + /// + /// 当月培训补贴 + /// + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")] + public decimal MonthlyTrainingSubsidy { get; set; } + + /// + /// 当月交通补贴 + /// + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")] + public decimal MonthlyTransportSubsidy { get; set; } + + /// + /// 上月培训补贴 + /// + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")] + public decimal LastMonthTrainingSubsidy { get; set; } + + /// + /// 上月交通补贴 + /// + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")] + public decimal LastMonthTransportSubsidy { get; set; } + + /// + /// 补贴合计 + /// + [SugarColumn(ColumnName = "F_TotalSubsidy")] + public decimal TotalSubsidy { get; set; } + + /// + /// 发奖金 + /// + [SugarColumn(ColumnName = "F_Bonus")] + public decimal Bonus { get; set; } + + /// + /// 退手机押金 + /// + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")] + public decimal ReturnPhoneDeposit { get; set; } + + /// + /// 退住宿押金 + /// + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")] + public decimal ReturnAccommodationDeposit { get; set; } + + /// + /// 当月是否发放 + /// + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")] + public string MonthlyPaymentStatus { get; set; } + + /// + /// 支付金额 + /// + [SugarColumn(ColumnName = "F_PaidAmount")] + public decimal PaidAmount { get; set; } + + /// + /// 待支付金额 + /// + [SugarColumn(ColumnName = "F_PendingAmount")] + public decimal PendingAmount { get; set; } + + /// + /// 补发上月 + /// + [SugarColumn(ColumnName = "F_LastMonthSupplement")] + public decimal LastMonthSupplement { get; set; } + + /// + /// 当月支付总额 + /// + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")] + public decimal MonthlyTotalPayment { get; set; } + + /// + /// 是否锁定(0未锁定,1已锁定) + /// + [SugarColumn(ColumnName = "F_IsLocked")] + public int IsLocked { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime CreateTime { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(ColumnName = "F_UpdateTime")] + public DateTime UpdateTime { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "F_CreateUser")] + public string CreateUser { get; set; } + + /// + /// 更新人 + /// + [SugarColumn(ColumnName = "F_UpdateUser")] + public string UpdateUser { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node/LqReimbursementApplicationNodeEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node/LqReimbursementApplicationNodeEntity.cs new file mode 100644 index 0000000..d4aca2c --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node/LqReimbursementApplicationNodeEntity.cs @@ -0,0 +1,57 @@ +using NCC.Common.Const; +using SqlSugar; +using System; + +namespace NCC.Extend.Entitys.lq_reimbursement_application_node +{ + /// + /// 报销申请节点表(每个报销申请的节点配置) + /// + [SugarTable("lq_reimbursement_application_node")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqReimbursementApplicationNodeEntity + { + /// + /// 节点编号 + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 报销申请ID + /// + [SugarColumn(ColumnName = "F_ApplicationId")] + public string ApplicationId { get; set; } + + /// + /// 节点顺序(1,2,3,4,5) + /// + [SugarColumn(ColumnName = "F_NodeOrder")] + public int NodeOrder { get; set; } + + /// + /// 节点名称 + /// + [SugarColumn(ColumnName = "F_NodeName")] + public string NodeName { get; set; } + + /// + /// 审批类型(会签/或签) + /// + [SugarColumn(ColumnName = "F_ApprovalType")] + public string ApprovalType { get; set; } + + /// + /// 是否必审(1-必审,0-可选) + /// + [SugarColumn(ColumnName = "F_IsRequired")] + public int IsRequired { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime? CreateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node_user/LqReimbursementApplicationNodeUserEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node_user/LqReimbursementApplicationNodeUserEntity.cs new file mode 100644 index 0000000..e4c8f67 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node_user/LqReimbursementApplicationNodeUserEntity.cs @@ -0,0 +1,63 @@ +using NCC.Common.Const; +using SqlSugar; +using System; + +namespace NCC.Extend.Entitys.lq_reimbursement_application_node_user +{ + /// + /// 报销申请节点审批人表(每个节点的审批人,在创建报销申请时指定) + /// + [SugarTable("lq_reimbursement_application_node_user")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqReimbursementApplicationNodeUserEntity + { + /// + /// 记录编号 + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 报销申请ID + /// + [SugarColumn(ColumnName = "F_ApplicationId")] + public string ApplicationId { get; set; } + + /// + /// 节点编号 + /// + [SugarColumn(ColumnName = "F_NodeId")] + public string NodeId { get; set; } + + /// + /// 节点顺序(1,2,3,4,5) + /// + [SugarColumn(ColumnName = "F_NodeOrder")] + public int NodeOrder { get; set; } + + /// + /// 审批人ID + /// + [SugarColumn(ColumnName = "F_UserId")] + public string UserId { get; set; } + + /// + /// 审批人姓名 + /// + [SugarColumn(ColumnName = "F_UserName")] + public string UserName { get; set; } + + /// + /// 排序 + /// + [SugarColumn(ColumnName = "F_SortOrder")] + public int SortOrder { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime? CreateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_approval_record/LqReimbursementApprovalRecordEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_approval_record/LqReimbursementApprovalRecordEntity.cs new file mode 100644 index 0000000..0b4e4d5 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_approval_record/LqReimbursementApprovalRecordEntity.cs @@ -0,0 +1,75 @@ +using NCC.Common.Const; +using SqlSugar; +using System; + +namespace NCC.Extend.Entitys.lq_reimbursement_approval_record +{ + /// + /// 审批记录表 + /// + [SugarTable("lq_reimbursement_approval_record")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqReimbursementApprovalRecordEntity + { + /// + /// 记录编号 + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 报销申请ID + /// + [SugarColumn(ColumnName = "F_ApplicationId")] + public string ApplicationId { get; set; } + + /// + /// 节点编号 + /// + [SugarColumn(ColumnName = "F_NodeId")] + public string NodeId { get; set; } + + /// + /// 节点顺序(1,2,3,4,5) + /// + [SugarColumn(ColumnName = "F_NodeOrder")] + public int NodeOrder { get; set; } + + /// + /// 审批人ID + /// + [SugarColumn(ColumnName = "F_ApproverId")] + public string ApproverId { get; set; } + + /// + /// 审批人姓名 + /// + [SugarColumn(ColumnName = "F_ApproverName")] + public string ApproverName { get; set; } + + /// + /// 审批结果(通过/不通过/退回) + /// + [SugarColumn(ColumnName = "F_ApprovalResult")] + public string ApprovalResult { get; set; } + + /// + /// 审批意见 + /// + [SugarColumn(ColumnName = "F_ApprovalOpinion")] + public string ApprovalOpinion { get; set; } + + /// + /// 审批时间 + /// + [SugarColumn(ColumnName = "F_ApprovalTime")] + public DateTime? ApprovalTime { get; set; } + + /// + /// 是否当前节点(1-是,0-否) + /// + [SugarColumn(ColumnName = "F_IsCurrentNode")] + public int IsCurrentNode { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs index b3a4b85..b15b9e4 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs @@ -520,5 +520,23 @@ namespace NCC.Extend.Entitys.lq_salary_statistics /// [SugarColumn(ColumnName = "F_StoreCategory")] public int? StoreCategory { get; set; } + + /// + /// 日均消耗 + /// + [SugarColumn(ColumnName = "F_DailyAverageConsumption")] + public decimal DailyAverageConsumption { get; set; } + + /// + /// 日均项目数 + /// + [SugarColumn(ColumnName = "F_DailyAverageProjectCount")] + public decimal DailyAverageProjectCount { get; set; } + + /// + /// 战队总消耗 + /// + [SugarColumn(ColumnName = "F_TeamTotalConsumption")] + public decimal TeamTotalConsumption { get; set; } } } \ No newline at end of file diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs new file mode 100644 index 0000000..f0a7ebd --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs @@ -0,0 +1,533 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NCC.Common.Filter; +using NCC.Common.Helper; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.LqAssistantSalary; +using NCC.Extend.Entitys.lq_assistant_salary_statistics; +using NCC.Extend.Entitys.lq_attendance_summary; +using NCC.Extend.Entitys.lq_hytk_hytk; +using NCC.Extend.Entitys.lq_kd_kdjlb; +using NCC.Extend.Entitys.lq_md_target; +using NCC.Extend.Entitys.lq_md_xdbhsj; +using NCC.Extend.Entitys.lq_mdxx; +using NCC.Extend.Entitys.lq_xh_hyhk; +using NCC.Extend.Entitys.lq_xh_jksyj; +using NCC.System.Entitys.Permission; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Yitter.IdGenerator; + +namespace NCC.Extend +{ + /// + /// 店助薪酬服务 + /// + [ApiDescriptionSettings(Tag = "店助薪酬服务", Name = "LqAssistantSalary", Order = 301)] + [Route("api/Extend/[controller]")] + public class LqAssistantSalaryService : IDynamicApiController, ITransient + { + private readonly ISqlSugarClient _db; + + /// + /// 初始化一个类型的新实例 + /// + public LqAssistantSalaryService(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 获取店助工资列表 + /// + /// 查询参数 + /// 店助工资分页列表 + [HttpGet("assistant")] + public async Task GetAssistantSalaryList([FromQuery] AssistantSalaryInput input) + { + var monthStr = $"{input.Year}{input.Month:D2}"; + + // 1. 检查当月是否已生成工资数据 + var exists = await _db.Queryable() + .AnyAsync(x => x.StatisticsMonth == monthStr); + + // 2. 如果没有数据,则进行计算 + if (!exists) + { + await CalculateAssistantSalary(input.Year, input.Month); + } + + // 3. 查询数据 + var query = _db.Queryable() + .Where(x => x.StatisticsMonth == monthStr); + + if (!string.IsNullOrEmpty(input.StoreId)) + { + query = query.Where(x => x.StoreId == input.StoreId); + } + + if (!string.IsNullOrEmpty(input.Keyword)) + { + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword)); + } + + var list = await query.Select(x => new AssistantSalaryOutput + { + Id = x.Id, + StoreName = x.StoreName, + EmployeeName = x.EmployeeName, + Position = x.Position, + StoreTotalPerformance = x.StoreTotalPerformance, + StoreBillingPerformance = x.StoreBillingPerformance, + StoreRefundPerformance = x.StoreRefundPerformance, + StoreLifeline = x.StoreLifeline, + PerformanceCompletionRate = x.PerformanceCompletionRate, + CommissionRate = x.CommissionRate, + CommissionAmount = x.CommissionAmount, + HeadCount = x.HeadCount, + Stage1TargetHeadCount = x.Stage1TargetHeadCount, + Stage2TargetHeadCount = x.Stage2TargetHeadCount, + ReachedStage1 = x.ReachedStage1, + ReachedStage2 = x.ReachedStage2, + StageRewardAmount = x.StageRewardAmount, + Stage1Reward = x.Stage1Reward, + Stage2Reward = x.Stage2Reward, + BaseSalary = x.BaseSalary, + PhoneManagementFee = x.PhoneManagementFee, + WorkingDays = x.WorkingDays, + LeaveDays = x.LeaveDays, + GrossSalary = x.GrossSalary, + ActualSalary = x.ActualSalary, + TotalDeduction = x.TotalDeduction, + TotalSubsidy = x.TotalSubsidy, + Bonus = x.Bonus, + ReturnPhoneDeposit = x.ReturnPhoneDeposit, + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, + MonthlyPaymentStatus = x.MonthlyPaymentStatus, + PaidAmount = x.PaidAmount, + PendingAmount = x.PendingAmount, + LastMonthSupplement = x.LastMonthSupplement, + MonthlyTotalPayment = x.MonthlyTotalPayment, + IsLocked = x.IsLocked, + UpdateTime = x.UpdateTime, + StoreType = x.StoreType, + StoreCategory = x.StoreCategory, + IsNewStore = x.IsNewStore, + NewStoreProtectionStage = x.NewStoreProtectionStage + }) + .ToPagedListAsync(input.currentPage, input.pageSize); + + return PageResult.SqlSugarPageResult(list); + } + + /// + /// 计算店助工资 + /// + /// 年份 + /// 月份 + /// + [HttpPost("calculate/assistant")] + public async Task CalculateAssistantSalary(int year, int month) + { + var startDate = new DateTime(year, month, 1); + var endDate = startDate.AddMonths(1).AddDays(-1); + var monthStr = $"{year}{month:D2}"; + var daysInMonth = DateTime.DaysInMonth(year, month); // 当月天数 + + // 1. 获取基础数据 + + // 1.1 获取店助员工列表(从BASE_USER表,岗位为"店助"或"店助主任") + var assistantUserList = await _db.Queryable() + .Where(x => (x.Gw == "店助" || x.Gw == "店助主任") && x.DeleteMark == null && x.EnabledMark == 1) + .Select(x => new { x.Id, x.RealName, x.Mdid, x.Gw }) + .ToListAsync(); + + if (!assistantUserList.Any()) + { + // 如果没有店助员工,直接返回 + return; + } + + // 1.2 门店信息 (lq_mdxx) + var storeList = await _db.Queryable().ToListAsync(); + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); + + // 1.3 门店目标信息 (lq_md_target) - 包含门店生命线和阶段目标 + var storeTargets = await _db.Queryable() + .Where(x => x.Month == monthStr) + .ToListAsync(); + var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId)) + .ToDictionary(x => x.StoreId, x => x); + + // 1.4 门店新店保护信息 (lq_md_xdbhsj) + var newStoreProtectionList = await _db.Queryable() + .Where(x => x.Sfqy == 1) + .ToListAsync(); + var newStoreProtectionDict = newStoreProtectionList + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate) + .GroupBy(x => x.Mdid) + .ToDictionary(g => g.Key, g => g.First()); + + // 1.5 门店总业绩计算 (开单实付 - 退卡金额) + // 开单实付 + var storeBillingList = await _db.Queryable() + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1) + .Select(x => new { x.Djmd, x.Sfyj }) + .ToListAsync(); + var storeBillingDict = storeBillingList + .Where(x => !string.IsNullOrEmpty(x.Djmd)) + .GroupBy(x => x.Djmd) + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj)); + + // 退卡金额(使用F_ActualRefundAmount,如果没有则使用tkje) + var storeRefundList = await _db.Queryable() + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje }) + .ToListAsync(); + var storeRefundDict = storeRefundList + .Where(x => !string.IsNullOrEmpty(x.Md)) + .GroupBy(x => x.Md) + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0)); + + // 1.6 进店消耗人数统计(有消费金额的,按门店按月去重客户数) + // 使用SQL查询优化性能 + var headcountSql = $@" + SELECT + hyhk.md as StoreId, + COUNT(DISTINCT hyhk.hy) as HeadCount + FROM lq_xh_hyhk hyhk + WHERE hyhk.F_IsEffective = 1 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @monthStr + AND EXISTS ( + SELECT 1 + FROM lq_xh_jksyj jksyj + WHERE jksyj.glkdbh = hyhk.F_Id + AND jksyj.F_IsEffective = 1 + AND jksyj.jksyj > 0 + ) + GROUP BY hyhk.md"; + + var headcountData = await _db.Ado.SqlQueryAsync(headcountSql, new { monthStr }); + var headcountDict = headcountData + .Where(x => x.StoreId != null) + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount)); + + // 1.7 考勤数据 (lq_attendance_summary) + var attendanceList = await _db.Queryable() + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) + .ToListAsync(); + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); + + // 2. 计算每个店助的工资 + var assistantSalaryList = new List(); + + foreach (var assistantUser in assistantUserList) + { + var salary = new LqAssistantSalaryStatisticsEntity + { + Id = YitIdHelper.NextId().ToString(), + EmployeeId = assistantUser.Id, + EmployeeName = assistantUser.RealName, + StatisticsMonth = monthStr, + Position = assistantUser.Gw ?? "店助", // 使用Gw字段,如果为空则默认为"店助" + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now, + IsLocked = 0, + MonthlyPaymentStatus = "未发放" + }; + + // 2.1 填充门店信息 + string storeId = assistantUser.Mdid; + if (string.IsNullOrEmpty(storeId)) + { + // 如果用户没有门店ID,跳过 + continue; + } + + salary.StoreId = storeId; + + if (storeDict.ContainsKey(storeId)) + { + var store = storeDict[storeId]; + salary.StoreName = store.Dm; + salary.StoreType = store.StoreType; + salary.StoreCategory = store.StoreCategory; + + // 数据校验:门店分类必须设置 + if (!salary.StoreCategory.HasValue) + { + throw new Exception($"门店【{store.Dm}】的门店分类未设置,无法计算店助工资"); + } + } + + // 2.2 填充新店保护信息 + if (newStoreProtectionDict.ContainsKey(storeId)) + { + var protection = newStoreProtectionDict[storeId]; + salary.IsNewStore = "是"; + salary.NewStoreProtectionStage = protection.Stage; + } + else + { + salary.IsNewStore = "否"; + salary.NewStoreProtectionStage = 0; + } + + // 2.3 获取门店目标信息(门店生命线和阶段目标) + if (!storeTargetDict.ContainsKey(storeId)) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算店助工资"); + } + + var storeTarget = storeTargetDict[storeId]; + salary.StoreLifeline = storeTarget.StoreLifeline; + + // 阶段目标设置(如果未设置,则奖励金额为0) + salary.Stage1TargetHeadCount = storeTarget.AssistantHeadcountTargetStage1 > 0 + ? (int)storeTarget.AssistantHeadcountTargetStage1 + : 0; + salary.Stage2TargetHeadCount = storeTarget.AssistantHeadcountTargetStage2 > 0 + ? (int)storeTarget.AssistantHeadcountTargetStage2 + : 0; + + // 2.4 计算门店业绩 + decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; + decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0; + salary.StoreBillingPerformance = billing; + salary.StoreRefundPerformance = refund; + salary.StoreTotalPerformance = billing - refund; + + // 计算业绩完成率 + if (salary.StoreLifeline > 0) + { + salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline; + } + else + { + salary.PerformanceCompletionRate = 0; + } + + // 2.5 计算门店总提成(先计算门店级别的提成) + decimal storeTotalCommission = 0; + decimal commissionRate = 0; + + // 如果门店生命线未设置(<=0),则没有提成 + if (salary.StoreLifeline <= 0) + { + commissionRate = 0; + storeTotalCommission = 0; + } + else + { + commissionRate = CalculateCommissionRate(salary.StoreTotalPerformance, salary.StoreLifeline); + storeTotalCommission = salary.StoreTotalPerformance * commissionRate; + } + + salary.CommissionRate = commissionRate; + + // 2.6 统计进店消耗人数 + salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0; + + // 2.7 计算门店总阶段奖励(先计算门店级别的奖励) + decimal storeTotalStageReward = 0; + bool reachedStage1 = false; + bool reachedStage2 = false; + + // 如果阶段目标未设置(为0),则没有奖励考核,奖励金额为0 + if (salary.Stage1TargetHeadCount <= 0 && salary.Stage2TargetHeadCount <= 0) + { + // 阶段目标未设置,没有奖励考核 + salary.ReachedStage1 = "否"; + salary.ReachedStage2 = "否"; + storeTotalStageReward = 0m; + } + else + { + // 阶段目标已设置,进行奖励考核 + reachedStage1 = salary.Stage1TargetHeadCount > 0 && salary.HeadCount >= salary.Stage1TargetHeadCount; + reachedStage2 = salary.Stage2TargetHeadCount > 0 && salary.HeadCount >= salary.Stage2TargetHeadCount; + + salary.ReachedStage1 = reachedStage1 ? "是" : "否"; + salary.ReachedStage2 = reachedStage2 ? "是" : "否"; + + // 阶段奖励计算规则: + // - 如果达到第二阶段,获得400元(第一阶段200 + 第二阶段200) + // - 如果只达到第一阶段,获得200元 + // - 如果都没达到,获得0元 + if (reachedStage2) + { + storeTotalStageReward = 400m; + } + else if (reachedStage1) + { + storeTotalStageReward = 200m; + } + else + { + storeTotalStageReward = 0m; + } + } + + // 2.8 计算底薪(根据门店分类) + salary.BaseSalary = CalculateBaseSalary(salary.StoreCategory.Value); + + // 2.9 固定奖励(手机管理费) + salary.PhoneManagementFee = 150m; + + // 2.10 考勤数据 + int workingDays = 0; + if (attendanceDict.ContainsKey(assistantUser.Id)) + { + var attendance = attendanceDict[assistantUser.Id]; + workingDays = (int)attendance.WorkDays; + salary.WorkingDays = workingDays; + salary.LeaveDays = (int)attendance.LeaveDays; + } + else + { + salary.WorkingDays = 0; + salary.LeaveDays = 0; + } + + // 2.11 按在店天数比例计算店助的提成和奖励 + // 逻辑:门店总提成 / 当月天数 * 在店天数 = 店助提成 + // 门店总奖励 / 当月天数 * 在店天数 = 店助奖励 + if (daysInMonth > 0 && workingDays > 0) + { + // 按比例计算提成 + salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays; + + // 按比例计算奖励 + salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays; + + // 计算阶段奖励的明细(按比例分配) + if (reachedStage2) + { + // 达到第二阶段:第一阶段200 + 第二阶段200 + salary.Stage1Reward = 200m / daysInMonth * workingDays; + salary.Stage2Reward = 200m / daysInMonth * workingDays; + } + else if (reachedStage1) + { + // 只达到第一阶段:第一阶段200 + salary.Stage1Reward = 200m / daysInMonth * workingDays; + salary.Stage2Reward = 0m; + } + else + { + // 都没达到 + salary.Stage1Reward = 0m; + salary.Stage2Reward = 0m; + } + } + else + { + // 如果当月天数为0或在店天数为0,则提成和奖励为0 + salary.CommissionAmount = 0; + salary.StageRewardAmount = 0; + salary.Stage1Reward = 0; + salary.Stage2Reward = 0; + } + + // 2.12 计算应发工资 + salary.GrossSalary = salary.BaseSalary + salary.CommissionAmount + salary.StageRewardAmount + salary.PhoneManagementFee; + + // 2.13 初始化扣款、补贴、奖金字段(默认值为0) + salary.MissingCard = 0; + salary.LateArrival = 0; + salary.LeaveDeduction = 0; + salary.SocialInsuranceDeduction = 0; + salary.RewardDeduction = 0; + salary.AccommodationDeduction = 0; + salary.StudyPeriodDeduction = 0; + salary.WorkClothesDeduction = 0; + salary.TotalDeduction = 0; + + salary.MonthlyTrainingSubsidy = 0; + salary.MonthlyTransportSubsidy = 0; + salary.LastMonthTrainingSubsidy = 0; + salary.LastMonthTransportSubsidy = 0; + salary.TotalSubsidy = 0; + + salary.Bonus = 0; + salary.ReturnPhoneDeposit = 0; + salary.ReturnAccommodationDeposit = 0; + + // 2.14 计算实发工资 + salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; + + // 2.15 初始化支付相关字段 + salary.PaidAmount = 0; + salary.PendingAmount = salary.ActualSalary; + salary.LastMonthSupplement = 0; + salary.MonthlyTotalPayment = 0; + + assistantSalaryList.Add(salary); + } + + // 3. 保存数据 + if (assistantSalaryList.Any()) + { + // 先删除当月旧数据 (防止重复) + await _db.Deleteable() + .Where(x => x.StatisticsMonth == monthStr) + .ExecuteCommandAsync(); + + await _db.Insertable(assistantSalaryList).ExecuteCommandAsync(); + } + } + + /// + /// 计算提成比例 + /// + /// 门店业绩 + /// 门店生命线 + /// 提成比例(0%/0.4%/0.6%) + private decimal CalculateCommissionRate(decimal storePerformance, decimal storeLifeline) + { + if (storeLifeline <= 0) + { + return 0; + } + + decimal ratio = storePerformance / storeLifeline; + + if (ratio < 0.7m) + { + // 门店业绩 < 门店生命线 × 70% → 0% + return 0; + } + else if (ratio < 1.0m) + { + // 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% → 0.4% + return 0.004m; + } + else + { + // 门店业绩 ≥ 门店生命线 × 100% → 0.6% + return 0.006m; + } + } + + /// + /// 计算底薪 + /// + /// 门店分类(1=A类,2=B类,3=C类) + /// 底薪金额 + private decimal CalculateBaseSalary(int storeCategory) + { + return storeCategory switch + { + 1 => 3000m, // A类门店 + 2 => 3100m, // B类门店 + 3 => 3200m, // C类门店 + _ => throw new Exception($"门店分类值无效:{storeCategory},有效值为1(A类)、2(B类)、3(C类)") + }; + } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs new file mode 100644 index 0000000..e3d4eb1 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs @@ -0,0 +1,519 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NCC.Common.Filter; +using NCC.Common.Helper; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.LqDirectorSalary; +using NCC.Extend.Entitys.lq_attendance_summary; +using NCC.Extend.Entitys.lq_director_salary_statistics; +using NCC.Extend.Entitys.lq_hytk_hytk; +using NCC.Extend.Entitys.lq_kd_kdjlb; +using NCC.Extend.Entitys.lq_md_target; +using NCC.Extend.Entitys.lq_md_xdbhsj; +using NCC.Extend.Entitys.lq_mdxx; +using NCC.Extend.Entitys.lq_xh_hyhk; +using NCC.Extend.Entitys.lq_xh_jksyj; +using NCC.System.Entitys.Permission; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Yitter.IdGenerator; + +namespace NCC.Extend +{ + /// + /// 主任薪酬服务 + /// + [ApiDescriptionSettings(Tag = "主任薪酬服务", Name = "LqDirectorSalary", Order = 302)] + [Route("api/Extend/[controller]")] + public class LqDirectorSalaryService : IDynamicApiController, ITransient + { + private readonly ISqlSugarClient _db; + + /// + /// 初始化一个类型的新实例 + /// + public LqDirectorSalaryService(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 获取主任工资列表 + /// + /// 查询参数 + /// 主任工资分页列表 + [HttpGet("director")] + public async Task GetDirectorSalaryList([FromQuery] DirectorSalaryInput input) + { + var monthStr = $"{input.Year}{input.Month:D2}"; + + // 1. 检查当月是否已生成工资数据 + var exists = await _db.Queryable() + .AnyAsync(x => x.StatisticsMonth == monthStr); + + // 2. 如果没有数据,则进行计算 + if (!exists) + { + await CalculateDirectorSalary(input.Year, input.Month); + } + + // 3. 查询数据 + var query = _db.Queryable() + .Where(x => x.StatisticsMonth == monthStr); + + if (!string.IsNullOrEmpty(input.StoreId)) + { + query = query.Where(x => x.StoreId == input.StoreId); + } + + if (!string.IsNullOrEmpty(input.Keyword)) + { + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword)); + } + + var list = await query.Select(x => new DirectorSalaryOutput + { + Id = x.Id, + StoreName = x.StoreName, + EmployeeName = x.EmployeeName, + Position = x.Position, + StoreTotalPerformance = x.StoreTotalPerformance, + StoreBillingPerformance = x.StoreBillingPerformance, + StoreRefundPerformance = x.StoreRefundPerformance, + StoreLifeline = x.StoreLifeline, + PerformanceCompletionRate = x.PerformanceCompletionRate, + PerformanceReached = x.PerformanceReached, + HeadCountReached = x.HeadCountReached, + ConsumeReached = x.ConsumeReached, + AssessmentDeduction = x.AssessmentDeduction, + UnreachedIndicatorCount = x.UnreachedIndicatorCount, + HeadCount = x.HeadCount, + TargetHeadCount = x.TargetHeadCount, + StoreConsume = x.StoreConsume, + TargetConsume = x.TargetConsume, + CommissionRateBelowLifeline = x.CommissionRateBelowLifeline, + CommissionRateAboveLifeline = x.CommissionRateAboveLifeline, + CommissionAmountBelowLifeline = x.CommissionAmountBelowLifeline, + CommissionAmountAboveLifeline = x.CommissionAmountAboveLifeline, + TotalCommissionAmount = x.TotalCommissionAmount, + BaseSalary = x.BaseSalary, + ActualBaseSalary = x.ActualBaseSalary, + WorkingDays = x.WorkingDays, + LeaveDays = x.LeaveDays, + GrossSalary = x.GrossSalary, + ActualSalary = x.ActualSalary, + TotalDeduction = x.TotalDeduction, + TotalSubsidy = x.TotalSubsidy, + Bonus = x.Bonus, + ReturnPhoneDeposit = x.ReturnPhoneDeposit, + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, + MonthlyPaymentStatus = x.MonthlyPaymentStatus, + PaidAmount = x.PaidAmount, + PendingAmount = x.PendingAmount, + LastMonthSupplement = x.LastMonthSupplement, + MonthlyTotalPayment = x.MonthlyTotalPayment, + IsLocked = x.IsLocked, + UpdateTime = x.UpdateTime, + StoreType = x.StoreType, + StoreCategory = x.StoreCategory, + IsNewStore = x.IsNewStore, + NewStoreProtectionStage = x.NewStoreProtectionStage + }) + .ToPagedListAsync(input.currentPage, input.pageSize); + + return PageResult.SqlSugarPageResult(list); + } + + /// + /// 计算主任工资 + /// + /// 年份 + /// 月份 + /// + [HttpPost("calculate/director")] + public async Task CalculateDirectorSalary(int year, int month) + { + var startDate = new DateTime(year, month, 1); + var endDate = startDate.AddMonths(1).AddDays(-1); + var monthStr = $"{year}{month:D2}"; + + // 1. 获取基础数据 + + // 1.1 获取主任员工列表(从BASE_USER表,岗位为"主任") + var directorUserList = await _db.Queryable() + .Where(x => x.Gw == "主任" && x.DeleteMark == null && x.EnabledMark == 1) + .Select(x => new { x.Id, x.RealName, x.Mdid, x.Gw }) + .ToListAsync(); + + if (!directorUserList.Any()) + { + // 如果没有主任员工,直接返回 + return; + } + + // 1.2 门店信息 (lq_mdxx) + var storeList = await _db.Queryable().ToListAsync(); + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); + + // 1.3 门店目标信息 (lq_md_target) - 包含门店生命线、目标人头、目标消耗 + var storeTargets = await _db.Queryable() + .Where(x => x.Month == monthStr) + .ToListAsync(); + var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId)) + .ToDictionary(x => x.StoreId, x => x); + + // 1.4 门店新店保护信息 (lq_md_xdbhsj) + var newStoreProtectionList = await _db.Queryable() + .Where(x => x.Sfqy == 1) + .ToListAsync(); + var newStoreProtectionDict = newStoreProtectionList + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate) + .GroupBy(x => x.Mdid) + .ToDictionary(g => g.Key, g => g.First()); + + // 1.5 门店总业绩计算 (开单实付 - 退卡金额) + // 开单实付 + var storeBillingList = await _db.Queryable() + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1) + .Select(x => new { x.Djmd, x.Sfyj }) + .ToListAsync(); + var storeBillingDict = storeBillingList + .Where(x => !string.IsNullOrEmpty(x.Djmd)) + .GroupBy(x => x.Djmd) + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj)); + + // 退卡金额(使用F_ActualRefundAmount,如果没有则使用tkje) + var storeRefundList = await _db.Queryable() + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje }) + .ToListAsync(); + var storeRefundDict = storeRefundList + .Where(x => !string.IsNullOrEmpty(x.Md)) + .GroupBy(x => x.Md) + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0)); + + // 1.6 门店消耗金额统计(按门店统计当月总消耗) + // 使用SQL查询优化性能 + var storeConsumeSql = $@" + SELECT + hyhk.md as StoreId, + COALESCE(SUM(jksyj.jksyj), 0) as ConsumeAmount + FROM lq_xh_jksyj jksyj + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id + WHERE jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND hyhk.hksj >= @startDate + AND hyhk.hksj <= @endDate + GROUP BY hyhk.md"; + + var storeConsumeData = await _db.Ado.SqlQueryAsync(storeConsumeSql, new { startDate, endDate = endDate.AddDays(1) }); + var storeConsumeDict = storeConsumeData + .Where(x => x.StoreId != null) + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ConsumeAmount ?? 0)); + + // 1.7 进店消耗人数统计(有消费金额的,按门店按月去重客户数) + // 使用SQL查询优化性能 + var headcountSql = $@" + SELECT + hyhk.md as StoreId, + COUNT(DISTINCT hyhk.hy) as HeadCount + FROM lq_xh_hyhk hyhk + WHERE hyhk.F_IsEffective = 1 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @monthStr + AND EXISTS ( + SELECT 1 + FROM lq_xh_jksyj jksyj + WHERE jksyj.glkdbh = hyhk.F_Id + AND jksyj.F_IsEffective = 1 + AND jksyj.jksyj > 0 + ) + GROUP BY hyhk.md"; + + var headcountData = await _db.Ado.SqlQueryAsync(headcountSql, new { monthStr }); + var headcountDict = headcountData + .Where(x => x.StoreId != null) + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount)); + + // 1.8 考勤数据 (lq_attendance_summary) + var attendanceList = await _db.Queryable() + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) + .ToListAsync(); + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); + + // 2. 计算每个主任的工资 + var directorSalaryList = new List(); + + foreach (var directorUser in directorUserList) + { + var salary = new LqDirectorSalaryStatisticsEntity + { + Id = YitIdHelper.NextId().ToString(), + EmployeeId = directorUser.Id, + EmployeeName = directorUser.RealName, + StatisticsMonth = monthStr, + Position = "主任", + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now, + IsLocked = 0, + MonthlyPaymentStatus = "未发放" + }; + + // 2.1 填充门店信息 + string storeId = directorUser.Mdid; + if (string.IsNullOrEmpty(storeId)) + { + // 如果用户没有门店ID,跳过 + continue; + } + + salary.StoreId = storeId; + + if (storeDict.ContainsKey(storeId)) + { + var store = storeDict[storeId]; + salary.StoreName = store.Dm; + salary.StoreType = store.StoreType; + salary.StoreCategory = store.StoreCategory; + + // 数据校验:门店分类必须设置 + if (!salary.StoreCategory.HasValue) + { + throw new Exception($"门店【{store.Dm}】的门店分类未设置,无法计算主任工资"); + } + } + + // 2.2 填充新店保护信息 + bool isNewStore = false; + if (newStoreProtectionDict.ContainsKey(storeId)) + { + var protection = newStoreProtectionDict[storeId]; + salary.IsNewStore = "是"; + salary.NewStoreProtectionStage = protection.Stage; + isNewStore = true; + } + else + { + salary.IsNewStore = "否"; + salary.NewStoreProtectionStage = 0; + } + + // 2.3 获取门店目标信息(门店生命线、目标人头、目标消耗) + if (!storeTargetDict.ContainsKey(storeId)) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算主任工资"); + } + + var storeTarget = storeTargetDict[storeId]; + salary.StoreLifeline = storeTarget.StoreLifeline; + salary.TargetHeadCount = storeTarget.StoreHeadcountTarget; + salary.TargetConsume = storeTarget.StoreConsumeTarget; + + // 数据校验:门店生命线、目标人头、目标消耗必须设置 + if (salary.StoreLifeline <= 0) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】的门店生命线未设置,无法计算主任工资"); + } + if (salary.TargetHeadCount <= 0) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标人头数未设置,无法计算主任工资"); + } + if (!isNewStore && salary.TargetConsume <= 0) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标消耗未设置,无法计算主任工资(老店需要考核消耗)"); + } + + // 2.4 计算门店业绩 + decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; + decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0; + salary.StoreBillingPerformance = billing; + salary.StoreRefundPerformance = refund; + salary.StoreTotalPerformance = billing - refund; + + // 计算业绩完成率 + if (salary.StoreLifeline > 0) + { + salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline; + } + else + { + salary.PerformanceCompletionRate = 0; + } + + // 2.5 统计门店消耗金额 + salary.StoreConsume = storeConsumeDict.ContainsKey(storeId) ? storeConsumeDict[storeId] : 0; + + // 2.6 统计进店消耗人数 + salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0; + + // 2.7 计算考核指标(业绩、人头、消耗是否达标) + bool performanceReached = salary.StoreTotalPerformance >= salary.StoreLifeline; + bool headCountReached = salary.HeadCount >= salary.TargetHeadCount; + bool consumeReached = salary.StoreConsume >= salary.TargetConsume; + + salary.PerformanceReached = performanceReached ? "是" : "否"; + salary.HeadCountReached = headCountReached ? "是" : "否"; + salary.ConsumeReached = consumeReached ? "是" : "否"; + + // 计算未达标指标数量 + int unreachedCount = 0; + if (!performanceReached) unreachedCount++; + if (!headCountReached) unreachedCount++; + // 新店不考核消耗 + if (!isNewStore && !consumeReached) unreachedCount++; + + salary.UnreachedIndicatorCount = unreachedCount; + salary.AssessmentDeduction = unreachedCount * 500m; + + // 2.8 计算底薪 + salary.BaseSalary = 3500m; // 固定底薪3500元 + salary.ActualBaseSalary = salary.BaseSalary - salary.AssessmentDeduction; // 实际底薪 = 底薪 - 考核扣款 + + // 2.9 计算阶梯提成 + CalculateCommission(salary, isNewStore); + + // 2.10 考勤数据 + if (attendanceDict.ContainsKey(directorUser.Id)) + { + var attendance = attendanceDict[directorUser.Id]; + salary.WorkingDays = (int)attendance.WorkDays; + salary.LeaveDays = (int)attendance.LeaveDays; + } + else + { + salary.WorkingDays = 0; + salary.LeaveDays = 0; + } + + // 2.11 计算应发工资 + salary.GrossSalary = salary.ActualBaseSalary + salary.TotalCommissionAmount; + + // 2.12 初始化扣款、补贴、奖金字段(默认值为0) + salary.MissingCard = 0; + salary.LateArrival = 0; + salary.LeaveDeduction = 0; + salary.SocialInsuranceDeduction = 0; + salary.RewardDeduction = 0; + salary.AccommodationDeduction = 0; + salary.StudyPeriodDeduction = 0; + salary.WorkClothesDeduction = 0; + salary.TotalDeduction = 0; + + salary.MonthlyTrainingSubsidy = 0; + salary.MonthlyTransportSubsidy = 0; + salary.LastMonthTrainingSubsidy = 0; + salary.LastMonthTransportSubsidy = 0; + salary.TotalSubsidy = 0; + + salary.Bonus = 0; + salary.ReturnPhoneDeposit = 0; + salary.ReturnAccommodationDeposit = 0; + + // 2.13 计算实发工资 + salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; + + // 2.14 初始化支付相关字段 + salary.PaidAmount = 0; + salary.PendingAmount = salary.ActualSalary; + salary.LastMonthSupplement = 0; + salary.MonthlyTotalPayment = 0; + + directorSalaryList.Add(salary); + } + + // 3. 保存数据 + if (directorSalaryList.Any()) + { + // 先删除当月旧数据 (防止重复) + await _db.Deleteable() + .Where(x => x.StatisticsMonth == monthStr) + .ExecuteCommandAsync(); + + await _db.Insertable(directorSalaryList).ExecuteCommandAsync(); + } + } + + /// + /// 计算阶梯提成 + /// + /// 工资实体 + /// 是否新店 + private void CalculateCommission(LqDirectorSalaryStatisticsEntity salary, bool isNewStore) + { + if (salary.StoreLifeline <= 0) + { + // 如果门店生命线未设置,则没有提成 + salary.CommissionRateBelowLifeline = 0; + salary.CommissionRateAboveLifeline = 0; + salary.CommissionAmountBelowLifeline = 0; + salary.CommissionAmountAboveLifeline = 0; + salary.TotalCommissionAmount = 0; + return; + } + + decimal performance = salary.StoreTotalPerformance; + decimal lifeline = salary.StoreLifeline; + + // 确定提成比例(根据新店/老店和门店分类) + decimal rateBelowLifeline; + decimal rateAboveLifeline; + + if (isNewStore) + { + // 新店:统一标准,不区分门店分类 + rateBelowLifeline = 0.02m; // 2% + rateAboveLifeline = 0.025m; // 2.5% + } + else + { + // 老店:根据门店分类确定提成比例 + int? storeCategory = salary.StoreCategory; + if (!storeCategory.HasValue) + { + throw new Exception($"门店【{salary.StoreName}】的门店分类未设置,无法计算提成"); + } + + switch (storeCategory.Value) + { + case 1: // A类门店 + rateBelowLifeline = 0.02m; // 2% + rateAboveLifeline = 0.025m; // 2.5% + break; + case 2: // B类门店 + rateBelowLifeline = 0.025m; // 2.5% + rateAboveLifeline = 0.03m; // 3% + break; + case 3: // C类门店 + rateBelowLifeline = 0.03m; // 3% + rateAboveLifeline = 0.035m; // 3.5% + break; + default: + throw new Exception($"门店【{salary.StoreName}】的门店分类值无效:{storeCategory.Value},有效值为1(A类)、2(B类)、3(C类)"); + } + } + + salary.CommissionRateBelowLifeline = rateBelowLifeline; + salary.CommissionRateAboveLifeline = rateAboveLifeline; + + // 计算阶梯提成 + if (performance <= lifeline) + { + // 业绩 ≤ 生命线:只计算≤生命线部分的提成 + salary.CommissionAmountBelowLifeline = performance * rateBelowLifeline; + salary.CommissionAmountAboveLifeline = 0; + } + else + { + // 业绩 > 生命线:分别计算≤生命线部分和>生命线部分的提成 + salary.CommissionAmountBelowLifeline = lifeline * rateBelowLifeline; + salary.CommissionAmountAboveLifeline = (performance - lifeline) * rateAboveLifeline; + } + + salary.TotalCommissionAmount = salary.CommissionAmountBelowLifeline + salary.CommissionAmountAboveLifeline; + } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs index 84fb70c..9429339 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs @@ -130,6 +130,7 @@ namespace NCC.Extend.LqKdKdjlb output.upgradeLifeBeauty = entity.UpgradeLifeBeauty; output.upgradeTechBeauty = entity.UpgradeTechBeauty; output.upgradeMedicalBeauty = entity.UpgradeMedicalBeauty; + output.fileUrl = entity.F_FIleUrl; if (!string.IsNullOrEmpty(entity.AppointmentId)) { output.appointmentTime = await _db.Queryable().Where(x => x.Id == entity.AppointmentId).Select(x => x.Yysj).FirstAsync(); diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs index 4455261..fa8160e 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs @@ -111,7 +111,7 @@ namespace NCC.Extend LaundrySupplierId = input.LaundrySupplierId, Quantity = input.Quantity, LaundryPrice = supplier.LaundryPrice, // 记录历史价格 - TotalPrice = 0, // 送出时总费用为0 + TotalPrice = input.Quantity * supplier.LaundryPrice, // 送出时总费用为数量 * 单价 Remark = input.Remark, IsEffective = StatusEnum.有效.GetHashCode(), CreateUser = _userManager.UserId, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs index 8738a6b..17f1bd8 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs @@ -15,6 +15,9 @@ using System.Linq; using System.Threading.Tasks; using NCC.Extend.Entitys; using NCC.Extend.Entitys.Dto.LqReimbursementApplication; +using NCC.Extend.Entitys.lq_reimbursement_application_node; +using NCC.Extend.Entitys.lq_reimbursement_application_node_user; +using NCC.Extend.Entitys.lq_reimbursement_approval_record; using Yitter.IdGenerator; using NCC.Common.Helper; using NCC.JsonSerialization; @@ -49,16 +52,85 @@ namespace NCC.Extend.LqReimbursementApplication } /// - /// 获取报销申请表 + /// 获取报销申请表详情(包含表单和流程信息) /// - /// 参数 + /// 申请编号 /// [HttpGet("{id}")] public async Task GetInfo(string id) { var entity = await _db.Queryable().FirstAsync(p => p.Id == id); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + var output = entity.Adapt(); - return output; + + // 获取节点配置 + var nodes = await _db.Queryable() + .Where(x => x.ApplicationId == id) + .OrderBy(x => x.NodeOrder) + .ToListAsync(); + + // 获取每个节点的审批人 + var nodeUsers = await _db.Queryable() + .Where(x => x.ApplicationId == id) + .OrderBy(x => x.NodeOrder) + .OrderBy(x => x.SortOrder) + .ToListAsync(); + + // 获取审批历史 + var approvalRecords = await _db.Queryable() + .Where(x => x.ApplicationId == id) + .OrderBy(x => x.NodeOrder) + .OrderBy(x => x.ApprovalTime) + .ToListAsync(); + + // 组装节点信息 + var nodeList = nodes.Select(n => new + { + nodeId = n.Id, + nodeOrder = n.NodeOrder, + nodeName = n.NodeName, + approvalType = n.ApprovalType, + isRequired = n.IsRequired, + approvers = nodeUsers.Where(u => u.NodeId == n.Id).Select(u => new + { + userId = u.UserId, + userName = u.UserName, + sortOrder = u.SortOrder + }).ToList(), + approvalRecords = approvalRecords.Where(r => r.NodeId == n.Id).Select(r => new + { + approverName = r.ApproverName, + approvalResult = r.ApprovalResult, + approvalOpinion = r.ApprovalOpinion, + approvalTime = r.ApprovalTime + }).ToList() + }).ToList(); + + // 获取当前节点审批人 + var currentApprovers = new List(); + if (entity.CurrentNodeOrder.HasValue && entity.CurrentNodeOrder > 0) + { + currentApprovers = nodeUsers + .Where(u => u.NodeOrder == entity.CurrentNodeOrder.Value) + .Select(u => new + { + userId = u.UserId, + userName = u.UserName + }) + .Cast() + .ToList(); + } + + return new + { + form = output, + nodes = nodeList, + currentApprovers = currentApprovers, + currentNodeOrder = entity.CurrentNodeOrder, + approvalStatus = entity.ApprovalStatus ?? entity.ApproveStatus, + returnedReason = entity.ReturnedReason + }; } /// @@ -76,7 +148,7 @@ namespace NCC.Extend.LqReimbursementApplication List queryApproveTime = input.approveTime != null ? input.approveTime.Split(',').ToObeject>() : null; DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null; DateTime? endApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.Last()) : null; - var data = await _db.Queryable() + var query = _db.Queryable() .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) .WhereIF(!string.IsNullOrEmpty(input.applicationUserId), p => p.ApplicationUserId.Contains(input.applicationUserId)) .WhereIF(!string.IsNullOrEmpty(input.applicationUserName), p => p.ApplicationUserName.Contains(input.applicationUserName)) @@ -85,24 +157,60 @@ namespace NCC.Extend.LqReimbursementApplication .WhereIF(queryApplicationTime != null, p => p.ApplicationTime <= new DateTime(endApplicationTime.ToDate().Year, endApplicationTime.ToDate().Month, endApplicationTime.ToDate().Day, 23, 59, 59)) .WhereIF(!string.IsNullOrEmpty(input.amount), p => p.Amount.Contains(input.amount)) .WhereIF(!string.IsNullOrEmpty(input.approveUser), p => p.ApproveUser.Equals(input.approveUser)) - .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => p.ApproveStatus.Contains(input.approveStatus)) + .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => (p.ApprovalStatus ?? p.ApproveStatus).Contains(input.approveStatus)) // .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0)) //.WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59)) .WhereIF(!string.IsNullOrEmpty(input.purchaseRecordsId), p => p.PurchaseRecordsId.Contains(input.purchaseRecordsId)) - .Select(it => new LqReimbursementApplicationListOutput + .OrderBy(sidx + " " + input.sort); + + var total = await query.CountAsync(); + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize); + + // 获取当前审批人信息 + var applicationIds = entities.Select(x => x.Id).ToList(); + var currentApprovers = new List(); + if (applicationIds.Any()) + { + var approverList = await _db.Queryable() + .Where(x => applicationIds.Contains(x.ApplicationId)) + .InnerJoin((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder) + .Select((u, a) => new + { + applicationId = a.Id, + approverName = u.UserName + }) + .ToListAsync(); + currentApprovers = approverList.Cast().ToList(); + } + + var approverDict = currentApprovers + .GroupBy(x => (string)x.applicationId) + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); + + // 组装返回数据 + var result = entities.Select(item => new LqReimbursementApplicationListOutput + { + id = item.Id, + applicationUserId = item.ApplicationUserId, + applicationUserName = item.ApplicationUserName, + applicationStoreId = item.ApplicationStoreId, + applicationTime = item.ApplicationTime, + amount = item.Amount, + approveUser = item.ApproveUser, + approveStatus = item.ApprovalStatus ?? item.ApproveStatus, + approveTime = item.ApproveTime, + purchaseRecordsId = item.PurchaseRecordsId, + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null, + currentNodeOrder = item.CurrentNodeOrder, + nodeCount = item.NodeCount + }).ToList(); + + return PageResult.SqlSugarPageResult( + new SqlSugarPagedList { - id = it.Id, - applicationUserId = it.ApplicationUserId, - applicationUserName = it.ApplicationUserName, - applicationStoreId = it.ApplicationStoreId, - applicationTime = it.ApplicationTime, - amount = it.Amount, - approveUser = it.ApproveUser, - approveStatus = it.ApproveStatus, - approveTime = it.ApproveTime, - purchaseRecordsId = it.PurchaseRecordsId, - }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); - return PageResult.SqlSugarPageResult(data); + list = result, + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } + }); } /// @@ -122,11 +230,99 @@ namespace NCC.Extend.LqReimbursementApplication //开启事务 _db.BeginTran(); - // 保存报销申请表 - var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); + // 1. 验证节点配置 + if (input.nodes == null || input.nodes.Count < 3 || input.nodes.Count > 5) + { + throw new Exception("节点数量必须在3-5个之间"); + } + + // 验证节点顺序是否连续(1, 2, 3, ...) + var nodeOrders = input.nodes.Select(n => n.nodeOrder).OrderBy(x => x).ToList(); + for (int i = 0; i < nodeOrders.Count; i++) + { + if (nodeOrders[i] != i + 1) + { + throw new Exception($"节点顺序必须连续,从1开始"); + } + } + + // 验证每个节点至少有一个审批人 + foreach (var node in input.nodes) + { + if (node.approverIds == null || node.approverIds.Count == 0) + { + throw new Exception($"节点{node.nodeOrder}({node.nodeName})必须至少指定一个审批人"); + } + } + + // 2. 设置报销申请初始状态 + entity.NodeCount = input.nodes.Count; + entity.CurrentNodeOrder = 0; + entity.ApprovalStatus = "待审批"; + entity.ApplicationTime = DateTime.Now; + if (string.IsNullOrEmpty(entity.ApplicationUserId)) + { + entity.ApplicationUserId = userInfo.userId; + } + if (string.IsNullOrEmpty(entity.ApplicationUserName)) + { + entity.ApplicationUserName = userInfo.userName; + } + + // 3. 保存报销申请表(不使用IgnoreColumns,确保新字段被保存) + var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); - // 更新购买记录的审批单编号和审批状态为"待审批" + // 4. 创建节点配置 + if (input.nodes != null && input.nodes.Count > 0) + { + foreach (var nodeConfig in input.nodes) + { + var node = new LqReimbursementApplicationNodeEntity + { + Id = YitIdHelper.NextId().ToString(), + ApplicationId = entity.Id, + NodeOrder = nodeConfig.nodeOrder, + NodeName = nodeConfig.nodeName, + ApprovalType = nodeConfig.approvalType ?? "会签", + IsRequired = 1, + CreateTime = DateTime.Now + }; + var nodeResult = await _db.Insertable(node).ExecuteCommandAsync(); + if (nodeResult <= 0) + { + throw new Exception($"创建节点{nodeConfig.nodeOrder}失败"); + } + + // 5. 创建节点审批人 + if (nodeConfig.approverIds != null && nodeConfig.approverIds.Count > 0) + { + for (int i = 0; i < nodeConfig.approverIds.Count; i++) + { + var nodeUser = new LqReimbursementApplicationNodeUserEntity + { + Id = YitIdHelper.NextId().ToString(), + ApplicationId = entity.Id, + NodeId = node.Id, + NodeOrder = nodeConfig.nodeOrder, + UserId = nodeConfig.approverIds[i], + UserName = nodeConfig.approverNames != null && i < nodeConfig.approverNames.Count + ? nodeConfig.approverNames[i] + : null, + SortOrder = i + 1, + CreateTime = DateTime.Now + }; + var userResult = await _db.Insertable(nodeUser).ExecuteCommandAsync(); + if (userResult <= 0) + { + throw new Exception($"创建节点{nodeConfig.nodeOrder}的审批人{nodeConfig.approverIds[i]}失败"); + } + } + } + } + } + + // 6. 更新购买记录的审批单编号和审批状态为"待审批" if (input.selectedPurchaseRecordIds != null && input.selectedPurchaseRecordIds.Count > 0) { // 先更新ApplicationId @@ -370,107 +566,582 @@ namespace NCC.Extend.LqReimbursementApplication } /// - /// 通过审批 + /// 提交审批(进入第一个节点) /// /// 申请编号 /// - [HttpPost("{id}/Actions/Approve")] - public async Task Approve(string id) + [HttpPost("{id}/Actions/SubmitApproval")] + public async Task SubmitApproval(string id) { - var userInfo = await _userManager.GetUserInfo(); var entity = await _db.Queryable().FirstAsync(p => p.Id == id); _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + // 允许"待审批"和"已退回"状态的申请提交审批 + if (entity.CurrentNodeOrder != 0 && entity.ApprovalStatus != "已退回") + { + throw new Exception("该申请已经提交审批,不能重复提交"); + } + + if (entity.NodeCount == null || entity.NodeCount < 3 || entity.NodeCount > 5) + { + throw new Exception("节点配置异常,无法提交审批"); + } + try { - //开启事务 _db.BeginTran(); - // 更新申请表的审批状态 - entity.ApproveStatus = "已审批"; - entity.ApproveTime = DateTime.Now; - entity.ApproveUser = userInfo.userId; + // 获取第一个节点 + var firstNode = await _db.Queryable() + .Where(x => x.ApplicationId == id && x.NodeOrder == 1) + .FirstAsync(); + + if (firstNode == null) + { + throw new Exception("未找到第一个审批节点配置"); + } + + // 更新报销申请状态 + entity.CurrentNodeOrder = 1; + entity.CurrentNodeId = firstNode.Id; + entity.ApprovalStatus = "审批中"; await _db.Updateable(entity).ExecuteCommandAsync(); - // 更新关联的购买记录 - if (!string.IsNullOrEmpty(entity.PurchaseRecordsId)) + // 为第一个节点的每个审批人创建待审批记录 + var firstNodeApprovers = await _db.Queryable() + .Where(x => x.ApplicationId == id && x.NodeOrder == 1) + .ToListAsync(); + + foreach (var approver in firstNodeApprovers) { - var purchaseIds = entity.PurchaseRecordsId.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToList(); - if (purchaseIds.Count > 0) + var record = new LqReimbursementApprovalRecordEntity { - await _db.Updateable() - .SetColumns(it => new LqPurchaseRecordsEntity - { - ApproveStatus = "已审批", - ApproveTime = DateTime.Now, - ApproveUser = userInfo.userId - }) - .Where(it => purchaseIds.Contains(it.Id)) - .ExecuteCommandAsync(); - } + Id = YitIdHelper.NextId().ToString(), + ApplicationId = id, + NodeId = firstNode.Id, + NodeOrder = 1, + ApproverId = approver.UserId, + ApproverName = approver.UserName, + ApprovalResult = "待审批", + IsCurrentNode = 1, + ApprovalTime = null + }; + await _db.Insertable(record).ExecuteCommandAsync(); } - //关闭事务 _db.CommitTran(); } catch (Exception) { - //回滚事务 _db.RollbackTran(); throw; } } /// - /// 拒绝审批 + /// 审批操作(通过/不通过/退回) /// /// 申请编号 + /// 审批结果:通过/不通过/退回 + /// 审批意见 /// - [HttpPost("{id}/Actions/Reject")] - public async Task Reject(string id) + [HttpPost("{id}/Actions/Approve")] + public async Task Approve(string id, [FromQuery] string result, [FromQuery] string opinion = "") { var userInfo = await _userManager.GetUserInfo(); var entity = await _db.Queryable().FirstAsync(p => p.Id == id); _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + if (entity.CurrentNodeOrder == null || entity.CurrentNodeOrder == 0) + { + throw new Exception("该申请尚未提交审批"); + } + + if (entity.ApprovalStatus != "审批中") + { + throw new Exception($"该申请当前状态为{entity.ApprovalStatus},无法进行审批操作"); + } + + // 验证当前用户是否有审批权限(管理员可以审批所有节点) + var isAdmin = userInfo.isAdministrator; + if (!isAdmin) + { + var hasPermission = await _db.Queryable() + .Where(x => x.ApplicationId == id + && x.NodeOrder == entity.CurrentNodeOrder + && x.UserId == userInfo.userId) + .AnyAsync(); + + if (!hasPermission) + { + throw new Exception("您没有当前节点的审批权限"); + } + } + + // 检查是否已经审批过 + var hasApproved = await _db.Queryable() + .Where(x => x.ApplicationId == id + && x.NodeOrder == entity.CurrentNodeOrder + && x.ApproverId == userInfo.userId + && x.ApprovalResult != "待审批") + .AnyAsync(); + + if (hasApproved) + { + throw new Exception("您已经审批过该节点"); + } + try { - //开启事务 _db.BeginTran(); - // 更新申请表的审批状态 - entity.ApproveStatus = "未通过"; - entity.ApproveTime = DateTime.Now; - entity.ApproveUser = userInfo.userId; - await _db.Updateable(entity).ExecuteCommandAsync(); + // 获取当前节点信息 + var currentNode = await _db.Queryable() + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) + .FirstAsync(); + + if (currentNode == null) + { + throw new Exception("未找到当前节点配置"); + } - // 更新关联的购买记录 - if (!string.IsNullOrEmpty(entity.PurchaseRecordsId)) + // 更新待审批记录为已审批(如果存在待审批记录) + var existingRecord = await _db.Queryable() + .Where(x => x.ApplicationId == id + && x.NodeOrder == entity.CurrentNodeOrder + && x.ApproverId == userInfo.userId + && x.ApprovalResult == "待审批") + .FirstAsync(); + + if (existingRecord != null) + { + // 更新现有记录 + existingRecord.ApprovalResult = result; + existingRecord.ApprovalOpinion = opinion; + existingRecord.ApprovalTime = DateTime.Now; + await _db.Updateable(existingRecord).ExecuteCommandAsync(); + } + else { - var purchaseIds = entity.PurchaseRecordsId.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToList(); - if (purchaseIds.Count > 0) + // 创建新记录(如果不存在) + var approvalRecord = new LqReimbursementApprovalRecordEntity { - await _db.Updateable() - .SetColumns(it => new LqPurchaseRecordsEntity - { - ApproveStatus = "未通过", - ApproveTime = DateTime.Now, - ApproveUser = userInfo.userId - }) - .Where(it => purchaseIds.Contains(it.Id)) + Id = YitIdHelper.NextId().ToString(), + ApplicationId = id, + NodeId = currentNode.Id, + NodeOrder = entity.CurrentNodeOrder.Value, + ApproverId = userInfo.userId, + ApproverName = userInfo.userName, + ApprovalResult = result, + ApprovalOpinion = opinion, + ApprovalTime = DateTime.Now, + IsCurrentNode = 1 + }; + await _db.Insertable(approvalRecord).ExecuteCommandAsync(); + } + + // 根据审批结果处理 + if (result == "不通过") + { + // 不通过:审批结束 + entity.ApprovalStatus = "未通过"; + await _db.Updateable(entity).ExecuteCommandAsync(); + + // 更新所有购买记录状态为"未通过"(通过ApplicationId关联更新) + await _db.Updateable() + .SetColumns(it => new LqPurchaseRecordsEntity + { + ApproveStatus = "未通过", + ApproveTime = DateTime.Now, + ApproveUser = userInfo.userId + }) + .Where(it => it.ApplicationId == id) + .ExecuteCommandAsync(); + } + else if (result == "退回") + { + // 退回:退回到上一节点 + if (entity.CurrentNodeOrder == 1) + { + // 退回到申请人 + entity.CurrentNodeOrder = 0; + entity.ApprovalStatus = "已退回"; + entity.ReturnedNodeOrder = 0; + entity.ReturnedReason = opinion; + entity.CurrentNodeId = null; + } + else + { + // 退回到上一节点 + entity.CurrentNodeOrder -= 1; + entity.ReturnedNodeOrder = entity.CurrentNodeOrder; + entity.ReturnedReason = opinion; + + // 获取上一节点信息 + var prevNode = await _db.Queryable() + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) + .FirstAsync(); + + if (prevNode != null) + { + entity.CurrentNodeId = prevNode.Id; + } + + // 清除上一节点的所有审批记录(重新审批) + await _db.Deleteable() + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) .ExecuteCommandAsync(); + + // 为上一节点的每个审批人创建新的待审批记录 + var prevNodeApprovers = await _db.Queryable() + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) + .ToListAsync(); + + foreach (var approver in prevNodeApprovers) + { + var record = new LqReimbursementApprovalRecordEntity + { + Id = YitIdHelper.NextId().ToString(), + ApplicationId = id, + NodeId = prevNode.Id, + NodeOrder = entity.CurrentNodeOrder.Value, + ApproverId = approver.UserId, + ApproverName = approver.UserName, + ApprovalResult = "待审批", + IsCurrentNode = 1, + ApprovalTime = null + }; + await _db.Insertable(record).ExecuteCommandAsync(); + } + } + + await _db.Updateable(entity).ExecuteCommandAsync(); + } + else if (result == "通过") + { + // 通过:判断是否需要进入下一个节点 + bool shouldMoveToNext = false; + + if (currentNode.ApprovalType == "或签") + { + // 或签:任意一个审批人通过,立即进入下一个节点 + shouldMoveToNext = true; + } + else // 会签 + { + // 会签:检查是否所有审批人都已通过 + var approvers = await _db.Queryable() + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) + .Select(x => x.UserId) + .ToListAsync(); + + var approvedUsers = await _db.Queryable() + .Where(x => x.ApplicationId == id + && x.NodeOrder == entity.CurrentNodeOrder + && x.ApprovalResult == "通过") + .Select(x => x.ApproverId) + .Distinct() + .ToListAsync(); + + if (approvers.Count == approvedUsers.Count) + { + // 所有人都已通过 + shouldMoveToNext = true; + } + } + + if (shouldMoveToNext) + { + // 进入下一个节点或完成审批 + await MoveToNextNode(id, userInfo.userId); } } - //关闭事务 _db.CommitTran(); } catch (Exception) { - //回滚事务 _db.RollbackTran(); throw; } } + + /// + /// 进入下一个节点 + /// + private async Task MoveToNextNode(string applicationId, string approveUserId) + { + var entity = await _db.Queryable() + .FirstAsync(x => x.Id == applicationId); + + // 判断是否是最后一个节点 + if (entity.CurrentNodeOrder >= entity.NodeCount) + { + // 审批完成 + entity.CurrentNodeOrder = entity.NodeCount + 1; + entity.ApprovalStatus = "已通过"; + entity.CurrentNodeId = null; + await _db.Updateable(entity).ExecuteCommandAsync(); + + // 更新所有购买记录状态为"已审批" + // 通过ApplicationId关联更新,因为PurchaseRecordsId可能为空 + await _db.Updateable() + .SetColumns(it => new LqPurchaseRecordsEntity + { + ApproveStatus = "已审批", + ApproveTime = DateTime.Now, + ApproveUser = approveUserId + }) + .Where(it => it.ApplicationId == applicationId) + .ExecuteCommandAsync(); + } + else + { + // 进入下一个节点 + entity.CurrentNodeOrder += 1; + + // 获取下一个节点信息 + var nextNode = await _db.Queryable() + .Where(x => x.ApplicationId == applicationId && x.NodeOrder == entity.CurrentNodeOrder) + .FirstAsync(); + + if (nextNode == null) + { + throw new Exception($"未找到节点{entity.CurrentNodeOrder}的配置"); + } + + entity.CurrentNodeId = nextNode.Id; + entity.ApprovalStatus = "审批中"; + await _db.Updateable(entity).ExecuteCommandAsync(); + + // 清除之前的当前节点标记 + await _db.Updateable() + .SetColumns(it => new LqReimbursementApprovalRecordEntity { IsCurrentNode = 0 }) + .Where(it => it.ApplicationId == applicationId) + .ExecuteCommandAsync(); + + // 为下一个节点的每个审批人创建待审批记录 + var nextNodeApprovers = await _db.Queryable() + .Where(x => x.ApplicationId == applicationId && x.NodeOrder == entity.CurrentNodeOrder) + .ToListAsync(); + + foreach (var approver in nextNodeApprovers) + { + var record = new LqReimbursementApprovalRecordEntity + { + Id = YitIdHelper.NextId().ToString(), + ApplicationId = applicationId, + NodeId = nextNode.Id, + NodeOrder = entity.CurrentNodeOrder.Value, + ApproverId = approver.UserId, + ApproverName = approver.UserName, + ApprovalResult = "待审批", + IsCurrentNode = 1, + ApprovalTime = null + }; + await _db.Insertable(record).ExecuteCommandAsync(); + } + } + } + + /// + /// 获取所有待办列表(管理员用,所有待审批的申请) + /// + /// 查询参数 + /// + [HttpGet("Actions/AllPendingApproval")] + public async Task GetAllPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input) + { + var sidx = input.sidx == null ? "id" : input.sidx; + + // 管理员可以查看所有待审批的申请 + var query = _db.Queryable() + .Where(x => x.ApprovalStatus == "审批中") + .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId)) + .OrderBy(sidx + " " + input.sort); + + var total = await query.CountAsync(); + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize); + + // 获取当前审批人信息 + var applicationIds = entities.Select(x => x.Id).ToList(); + var currentApprovers = new List(); + if (applicationIds.Any()) + { + var approverList = await _db.Queryable() + .Where(x => applicationIds.Contains(x.ApplicationId)) + .InnerJoin((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder) + .Select((u, a) => new + { + applicationId = a.Id, + approverName = u.UserName + }) + .ToListAsync(); + currentApprovers = approverList.Cast().ToList(); + } + + var approverDict = currentApprovers + .GroupBy(x => (string)x.applicationId) + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); + + // 组装返回数据 + var result = entities.Select(item => new LqReimbursementApplicationListOutput + { + id = item.Id, + applicationUserId = item.ApplicationUserId, + applicationUserName = item.ApplicationUserName, + applicationStoreId = item.ApplicationStoreId, + applicationTime = item.ApplicationTime, + amount = item.Amount, + approveUser = item.ApproveUser, + approveStatus = item.ApprovalStatus ?? item.ApproveStatus, + approveTime = item.ApproveTime, + purchaseRecordsId = item.PurchaseRecordsId, + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null, + currentNodeOrder = item.CurrentNodeOrder, + nodeCount = item.NodeCount + }).ToList(); + + return PageResult.SqlSugarPageResult( + new SqlSugarPagedList + { + list = result, + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } + }); + } + + /// + /// 获取待审批列表(当前用户作为审批人的申请) + /// + /// 查询参数 + /// + [HttpGet("Actions/PendingApproval")] + public async Task GetPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input) + { + var userInfo = await _userManager.GetUserInfo(); + var sidx = input.sidx == null ? "id" : input.sidx; + + // 查询当前用户作为审批人的节点 + var userNodeOrders = await _db.Queryable() + .Where(x => x.UserId == userInfo.userId) + .Select(x => new { x.ApplicationId, x.NodeOrder }) + .ToListAsync(); + + if (!userNodeOrders.Any()) + { + return PageResult.SqlSugarPageResult( + new SqlSugarPagedList + { + list = new List(), + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = 0 } + }); + } + + // 获取用户有权限的申请ID和节点顺序 + var userApplications = userNodeOrders + .GroupBy(x => x.ApplicationId) + .ToDictionary(g => g.Key, g => g.Select(x => x.NodeOrder).ToList()); + + var applicationIds = userApplications.Keys.ToList(); + + // 查询这些申请中,当前节点是用户有权限的节点,且状态为"审批中"的申请 + var query = _db.Queryable() + .Where(x => applicationIds.Contains(x.Id) && x.ApprovalStatus == "审批中") + .Where(x => userApplications.ContainsKey(x.Id) + && userApplications[x.Id].Contains(x.CurrentNodeOrder ?? 0)) + .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId)) + .OrderBy(sidx + " " + input.sort); + + var total = await query.CountAsync(); + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize); + + // 获取当前审批人信息 + var resultApplicationIds = entities.Select(x => x.Id).ToList(); + var currentApprovers = new List(); + if (resultApplicationIds.Any()) + { + var approverList = await _db.Queryable() + .Where(x => resultApplicationIds.Contains(x.ApplicationId)) + .InnerJoin((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder) + .Select((u, a) => new + { + applicationId = a.Id, + approverName = u.UserName + }) + .ToListAsync(); + currentApprovers = approverList.Cast().ToList(); + } + + var approverDict = currentApprovers + .GroupBy(x => (string)x.applicationId) + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); + + // 组装返回数据 + var result = entities.Select(item => new LqReimbursementApplicationListOutput + { + id = item.Id, + applicationUserId = item.ApplicationUserId, + applicationUserName = item.ApplicationUserName, + applicationStoreId = item.ApplicationStoreId, + applicationTime = item.ApplicationTime, + amount = item.Amount, + approveUser = item.ApproveUser, + approveStatus = item.ApprovalStatus ?? item.ApproveStatus, + approveTime = item.ApproveTime, + purchaseRecordsId = item.PurchaseRecordsId, + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null, + currentNodeOrder = item.CurrentNodeOrder, + nodeCount = item.NodeCount + }).ToList(); + + return PageResult.SqlSugarPageResult( + new SqlSugarPagedList + { + list = result, + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } + }); + } + + /// + /// 获取审批历史 + /// + /// 申请编号 + /// + [HttpGet("{id}/Actions/ApprovalHistory")] + public async Task GetApprovalHistory(string id) + { + // 先查询审批记录 + var approvalRecords = await _db.Queryable() + .Where(x => x.ApplicationId == id) + .OrderBy(x => x.NodeOrder) + .OrderBy(x => x.ApprovalTime) + .ToListAsync(); + + // 获取节点信息 + if (approvalRecords.Any()) + { + var nodeIds = approvalRecords.Select(x => x.NodeId).Distinct().ToList(); + var nodes = await _db.Queryable() + .Where(x => nodeIds.Contains(x.Id)) + .ToListAsync(); + + var nodeDict = nodes.ToDictionary(x => x.Id, x => x.NodeName); + + // 组装结果 + var records = approvalRecords.Select(r => new + { + nodeOrder = r.NodeOrder, + nodeName = nodeDict.ContainsKey(r.NodeId) ? nodeDict[r.NodeId] : null, + approverName = r.ApproverName, + approvalResult = r.ApprovalResult, + approvalOpinion = r.ApprovalOpinion, + approvalTime = r.ApprovalTime + }).ToList(); + + return records; + } + + return new List(); + } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs index 601d440..d153b7d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs @@ -47,10 +47,11 @@ namespace NCC.Extend /// 查询参数 /// 健康师工资额外计算分页列表 [HttpGet] - public async Task> GetList([FromQuery] SalaryExtraCalculationInput input) + public async Task GetList([FromQuery] SalaryExtraCalculationInput input) { - var query = _db.Queryable() - .LeftJoin((ec, u) => ec.EmployeeId == u.Id); + var query = _db.Queryable((ec, u) => new JoinQueryInfos( + JoinType.Left, u.Id == SqlFunc.ToString(ec.EmployeeId))) + .Where((ec, u) => u.Id != null && (u.DeleteMark == null || u.DeleteMark == 0)); // 年份筛选 if (input.Year.HasValue) @@ -76,7 +77,7 @@ namespace NCC.Extend query = query.Where((ec, u) => u.RealName.Contains(input.Keyword) || u.MobilePhone.Contains(input.Keyword)); } - var list = await query.Select((ec, u) => new SalaryExtraCalculationOutput + var data = await query.Select((ec, u) => new SalaryExtraCalculationOutput { Id = ec.Id, EmployeeId = ec.EmployeeId, @@ -94,9 +95,11 @@ namespace NCC.Extend OtherPerformanceAdd = ec.OtherPerformanceAdd, OtherPerformanceSubtract = ec.OtherPerformanceSubtract }) + .MergeTable() + .OrderByIF(!string.IsNullOrEmpty(input.sidx), input.sidx + " " + input.sort) .ToPagedListAsync(input.currentPage, input.pageSize); - return PageResult.SqlSugarPageResult(list); + return PageResult.SqlSugarPageResult(data); } /// diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs index 1e5c9bf..7870d0c 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs @@ -30,9 +30,9 @@ using System.Threading.Tasks; namespace NCC.Extend { /// - /// 薪酬服务 + /// 健康师薪酬服务 /// - [ApiDescriptionSettings(Tag = "薪酬服务", Name = "LqSalary", Order = 300)] + [ApiDescriptionSettings(Tag = "健康师薪酬服务", Name = "LqSalary", Order = 300)] [Route("api/Extend/[controller]")] public class LqSalaryService : IDynamicApiController, ITransient { @@ -52,7 +52,7 @@ namespace NCC.Extend /// 查询参数 /// 健康师工资分页列表 [HttpGet("health-coach")] - public async Task> GetHealthCoachSalaryList([FromQuery] HealthCoachSalaryInput input) + public async Task GetHealthCoachSalaryList([FromQuery] HealthCoachSalaryInput input) { var monthStr = $"{input.Year}{input.Month:D2}"; @@ -83,40 +83,93 @@ namespace NCC.Extend var list = await query.Select(x => new HealthCoachSalaryOutput { Id = x.Id, + StoreId = x.StoreId, StoreName = x.StoreName, + EmployeeId = x.EmployeeId, EmployeeName = x.EmployeeName, Position = x.Position, + GoldTriangleId = x.GoldTriangleId, GoldTriangleTeam = x.GoldTriangleTeam, TotalPerformance = x.TotalPerformance, BasePerformance = x.BasePerformance, CooperationPerformance = x.CooperationPerformance, + BaseRewardPerformance = x.BaseRewardPerformance, + CooperationRewardPerformance = x.CooperationRewardPerformance, + ActualBasePerformance = x.ActualBasePerformance, + ActualCooperationPerformance = x.ActualCooperationPerformance, RewardPerformance = x.RewardPerformance, + StoreTotalPerformance = x.StoreTotalPerformance, + TeamPerformance = x.TeamPerformance, + Percentage = x.Percentage, + NewCustomerPerformance = x.NewCustomerPerformance, + NewCustomerConversionRate = x.NewCustomerConversionRate, + NewCustomerPoint = x.NewCustomerPoint, + UpgradeCustomerCount = x.UpgradeCustomerCount, + UpgradePerformance = x.UpgradePerformance, + UpgradePoint = x.UpgradePoint, + NewCustomerCommission = x.NewCustomerPerformanceCommission, + UpgradeCommission = x.UpgradePerformanceCommission, + OtherPerformanceAdd = x.OtherPerformanceAdd, + OtherPerformanceSubtract = x.OtherPerformanceSubtract, Consumption = x.Consumption, ProjectCount = x.ProjectCount, CustomerCount = x.CustomerCount, WorkingDays = x.WorkingDays, - HealthCoachBaseSalary = x.HealthCoachBaseSalary, + LeaveDays = x.LeaveDays, + CommissionPoint = x.CommissionPoint, + BasePerformanceCommission = x.BasePerformanceCommission, + CooperationPerformanceCommission = x.CooperationPerformanceCommission, + ConsultantCommission = x.ConsultantCommission, + StoreTZoneCommission = x.StoreTZoneCommission, TotalCommission = x.TotalCommission, + HealthCoachBaseSalary = x.HealthCoachBaseSalary, HandworkFee = x.HandworkFee, + OutherHandworkFee = x.OutherHandworkFee, + TransportationAllowance = x.TransportationAllowance, + LessRest = x.LessRest, + FullAttendance = x.FullAttendance, + CalculatedGrossSalary = x.CalculatedGrossSalary, + GuaranteedSalary = x.GuaranteedSalary, + GuaranteedLeaveDeduction = x.GuaranteedLeaveDeduction, + GuaranteedBaseSalary = x.GuaranteedBaseSalary, + GuaranteedSupplement = x.GuaranteedSupplement, + FinalGrossSalary = x.FinalGrossSalary, + MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy, + MonthlyTransportSubsidy = x.MonthlyTransportSubsidy, + LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy, + LastMonthTransportSubsidy = x.LastMonthTransportSubsidy, TotalSubsidy = x.TotalSubsidy, + MissingCard = x.MissingCard, + LateArrival = x.LateArrival, + LeaveDeduction = x.LeaveDeduction, + SocialInsuranceDeduction = x.SocialInsuranceDeduction, + RewardDeduction = x.RewardDeduction, + AccommodationDeduction = x.AccommodationDeduction, + StudyPeriodDeduction = x.StudyPeriodDeduction, + WorkClothesDeduction = x.WorkClothesDeduction, TotalDeduction = x.TotalDeduction, + Bonus = x.Bonus, + ReturnPhoneDeposit = x.ReturnPhoneDeposit, + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, ActualSalary = x.ActualSalary, + MonthlyPaymentStatus = x.MonthlyPaymentStatus, + PaidAmount = x.PaidAmount, + PendingAmount = x.PendingAmount, + LastMonthSupplement = x.LastMonthSupplement, + MonthlyTotalPayment = x.MonthlyTotalPayment, + StatisticsMonth = x.StatisticsMonth, IsLocked = x.IsLocked, + CreateTime = x.CreateTime, + CreateUser = x.CreateUser, UpdateTime = x.UpdateTime, + UpdateUser = x.UpdateUser, IsNewStore = x.IsNewStore, NewStoreProtectionStage = x.NewStoreProtectionStage, StoreType = x.StoreType, StoreCategory = x.StoreCategory, - ActualBasePerformance = x.ActualBasePerformance, - ActualCooperationPerformance = x.ActualCooperationPerformance, - NewCustomerCommission = x.NewCustomerPerformanceCommission, - UpgradeCommission = x.UpgradePerformanceCommission, - NewCustomerPerformance = x.NewCustomerPerformance, - NewCustomerConversionRate = x.NewCustomerConversionRate, - NewCustomerPoint = x.NewCustomerPoint, - UpgradeCustomerCount = x.UpgradeCustomerCount, - UpgradePerformance = x.UpgradePerformance, - UpgradePoint = x.UpgradePoint + DailyAverageConsumption = x.DailyAverageConsumption, + DailyAverageProjectCount = x.DailyAverageProjectCount, + TeamTotalConsumption = x.TeamTotalConsumption }) .ToPagedListAsync(input.currentPage, input.pageSize); @@ -360,8 +413,8 @@ namespace NCC.Extend salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0")); // 新客与升单业绩 - salary.NewCustomerPerformance = myPerf.Where(x => x.Sfskdd == "是").Sum(x => decimal.Parse(x.Jksyj ?? "0")); - salary.UpgradePerformance = myPerf.Where(x => x.Sfskdd == "否").Sum(x => decimal.Parse(x.Jksyj ?? "0")); + salary.NewCustomerPerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "是")).Sum(x => decimal.Parse(x.Jksyj ?? "0")); + salary.UpgradePerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "否")).Sum(x => decimal.Parse(x.Jksyj ?? "0")); // 2.1.1 填充额外计算数据 if (extraCalculationDict.ContainsKey(empId)) @@ -419,6 +472,19 @@ namespace NCC.Extend salary.WorkingDays = myAtt?.WorkDays ?? 0; salary.LeaveDays = myAtt?.LeaveDays ?? 0; + // 计算日均消耗和日均项目数 (用于底薪计算) + // 逻辑: 总消耗/在店天数, 总项目数/在店天数 + if (salary.WorkingDays > 0) + { + salary.DailyAverageConsumption = salary.Consumption / salary.WorkingDays; + salary.DailyAverageProjectCount = salary.ProjectCount / salary.WorkingDays; + } + else + { + salary.DailyAverageConsumption = 0; + salary.DailyAverageProjectCount = 0; + } + // 2.4 到店人头 var myHeadcount = headcountList.FirstOrDefault(x => x.PersonId == empId); salary.CustomerCount = myHeadcount?.Count ?? 0; @@ -458,7 +524,7 @@ namespace NCC.Extend } // 3. 处理战队逻辑 (考勤规则) - // 规则:若出勤天数 < 21天,则该健康师不计入战队,按单人计算。 + // 规则:若出勤天数 < 20天,则该健康师不计入战队,按单人计算。 // 按战队分组 var teamGroups = employeeStats.Values @@ -473,7 +539,7 @@ namespace NCC.Extend foreach (var member in group) { - if (member.WorkingDays >= 21) + if (member.WorkingDays >= 20) { validMembers.Add(member); } @@ -491,13 +557,15 @@ namespace NCC.Extend member.Position = "健康师"; // 降级为健康师 } - // 计算有效战队的总业绩 + // 计算有效战队的总业绩和总消耗 var teamTotalPerformance = validMembers.Sum(x => x.TotalPerformance); + var teamTotalConsumption = validMembers.Sum(x => x.Consumption); - // 更新有效成员的战队业绩 + // 更新有效成员的战队业绩和战队总消耗 foreach (var member in validMembers) { member.TeamPerformance = teamTotalPerformance; + member.TeamTotalConsumption = teamTotalConsumption; } } @@ -518,11 +586,18 @@ namespace NCC.Extend int newStoreStage = salary.NewStoreProtectionStage; // 4.1 底薪计算 - salary.HealthCoachBaseSalary = CalculateBaseSalary(salary.Consumption, salary.ProjectCount, isNewStore); + // 4.1 底薪计算 + // 传入当月天数用于计算标准日均 + int daysInMonth = DateTime.DaysInMonth(year, month); + salary.HealthCoachBaseSalary = CalculateBaseSalary( + salary.DailyAverageConsumption, + salary.DailyAverageProjectCount, + daysInMonth, + isNewStore); // 4.2 提成计算 - // 业绩门槛: 个人总业绩 <= 6000 无提成 - if (salary.TotalPerformance <= 6000) + // 业绩门槛: 战队成员个人总业绩 <= 6000 无提成 + if (!string.IsNullOrEmpty(salary.GoldTriangleId) && salary.TotalPerformance <= 6000) { salary.TotalCommission = 0; salary.BasePerformanceCommission = 0; @@ -564,17 +639,17 @@ namespace NCC.Extend { if (newStoreStage == 1) { - // 第一阶段:计算新客转化率提成 + // 第一阶段:计算新客转化率提成 (需乘以0.95) salary.NewCustomerPerformanceCommission = CalculateNewCustomerConversionCommission( salary.NewCustomerPerformance, - salary.NewCustomerConversionRate); + salary.NewCustomerConversionRate) * 0.95m; } else if (newStoreStage == 2) { - // 第二阶段:计算升单人头提成 + // 第二阶段:计算升单人头提成 (需乘以0.95) salary.UpgradePerformanceCommission = CalculateUpgradeCustomerCommission( salary.UpgradePerformance, - salary.UpgradeCustomerCount); + salary.UpgradeCustomerCount) * 0.95m; } // 第三阶段:不计算新客/升单提成 } @@ -590,11 +665,19 @@ namespace NCC.Extend isNewStore); } + // 计算门店T区提成 + // 规则:姓名包含"T区" -> 门店总业绩 * 0.05 * 0.05 + if (!string.IsNullOrEmpty(salary.EmployeeName) && salary.EmployeeName.Contains("T区")) + { + salary.StoreTZoneCommission = salary.StoreTotalPerformance * 0.05m * 0.05m; + } + salary.TotalCommission = salary.BasePerformanceCommission + salary.CooperationPerformanceCommission + salary.ConsultantCommission + salary.NewCustomerPerformanceCommission - + salary.UpgradePerformanceCommission; + + salary.UpgradePerformanceCommission + + salary.StoreTZoneCommission; } // 计算占比 @@ -623,25 +706,39 @@ namespace NCC.Extend /// /// 计算底薪 /// - private decimal CalculateBaseSalary(decimal consumption, decimal projectCount, bool isNewStore) + private decimal CalculateBaseSalary(decimal dailyAvgConsumption, decimal dailyAvgProjectCount, int daysInMonth, bool isNewStore) { - // 0星:<1w 或 <96个 -> 1800 - // 1星:>=1w 且 >=96个 -> 2000 - // 2星:>=2w 且 >=126个 -> 2200 - // 3星:>=4w 且 >=156个 -> 2400 + // 规则调整:按日均计算 + // 一星:月消耗 10000 / 当月天数,项目数 96 / 当月天数 + // 二星:月消耗 20000 / 当月天数,项目数 126 / 当月天数 + // 三星:月消耗 40000 / 当月天数,项目数 156 / 当月天数 + + // 计算各星级日均标准 + decimal consStar1 = 10000m / daysInMonth; + decimal consStar2 = 20000m / daysInMonth; + decimal consStar3 = 40000m / daysInMonth; + + decimal projStar1 = 96m / daysInMonth; + decimal projStar2 = 126m / daysInMonth; + decimal projStar3 = 156m / daysInMonth; + + // 0星:未达标 -> 1800 + // 1星:>=1w标准 且 >=96个标准 -> 2000 + // 2星:>=2w标准 且 >=126个标准 -> 2200 + // 3星:>=4w标准 且 >=156个标准 -> 2400 // 特殊规则:若消耗或项目数中仅一项未达标(0星),底薪按1星(2000元)计算 // 新店规则:新店底薪最低为1星(2000元),不满足1星按1星算 int starCons = 0; - if (consumption >= 40000) starCons = 3; - else if (consumption >= 20000) starCons = 2; - else if (consumption >= 10000) starCons = 1; + if (dailyAvgConsumption >= consStar3) starCons = 3; + else if (dailyAvgConsumption >= consStar2) starCons = 2; + else if (dailyAvgConsumption >= consStar1) starCons = 1; int starProj = 0; - if (projectCount >= 156) starProj = 3; - else if (projectCount >= 126) starProj = 2; - else if (projectCount >= 96) starProj = 1; + if (dailyAvgProjectCount >= projStar3) starProj = 3; + else if (dailyAvgProjectCount >= projStar2) starProj = 2; + else if (dailyAvgProjectCount >= projStar1) starProj = 1; int finalStar = Math.Min(starCons, starProj); @@ -713,6 +810,10 @@ namespace NCC.Extend // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X% // 4. 新店顾问不考核消耗 + // 使用传入的 teamMembers 计算总消耗,或者直接使用 TeamTotalConsumption (如果已计算) + // 但为了保险起见,这里重新计算或使用已有的逻辑 + // 注意:CalculateConsultantCommission 方法签名未变,但逻辑需确认 teamConsumption 来源 + // 在调用此方法前,teamMembers 已经有了 Consumption 数据 var teamConsumption = teamMembers.Sum(x => x.Consumption); // 计算组员(非顾问)业绩总和 diff --git a/netcore/src/Modularity/System/NCC.System.Entitys/Entity/Permission/UserEntity.cs b/netcore/src/Modularity/System/NCC.System.Entitys/Entity/Permission/UserEntity.cs index cc2f599..0bb563c 100644 --- a/netcore/src/Modularity/System/NCC.System.Entitys/Entity/Permission/UserEntity.cs +++ b/netcore/src/Modularity/System/NCC.System.Entitys/Entity/Permission/UserEntity.cs @@ -358,5 +358,11 @@ namespace NCC.System.Entitys.Permission [SugarColumn(ColumnName = "F_GW")] public string Gw { get; set; } + /// + /// 是否在职(1-在职,0-离职) + /// + [SugarColumn(ColumnName = "F_IsOnJob")] + public int? IsOnJob { get; set; } + } } diff --git a/sql/创建主任工资统计表.sql b/sql/创建主任工资统计表.sql new file mode 100644 index 0000000..742aeaa --- /dev/null +++ b/sql/创建主任工资统计表.sql @@ -0,0 +1,191 @@ +-- ============================================ +-- 创建主任工资统计表 +-- 功能:存储主任每月的工资计算数据,包括底薪、提成、考核扣款、扣款、补贴、奖金、支付等信息 +-- 创建时间:2025年 +-- ============================================ + +-- 删除表(如果存在) +DROP TABLE IF EXISTS lq_director_salary_statistics; + +-- ============================================ +-- 创建主任工资统计表 +-- ============================================ +CREATE TABLE lq_director_salary_statistics ( + -- 主键 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', + + -- 一、基础信息字段 + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID', + F_StoreName VARCHAR(200) NOT NULL COMMENT '门店名称', + F_Position VARCHAR(50) NOT NULL DEFAULT '主任' COMMENT '核算岗位(固定为"主任")', + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名', + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID', + F_StatisticsMonth VARCHAR(20) NOT NULL COMMENT '统计月份(YYYYMM格式)', + F_StoreType INT NULL COMMENT '门店类型(200平/旗舰店)', + F_StoreCategory INT NOT NULL COMMENT '门店分类(1=A类,2=B类,3=C类)', + F_IsNewStore VARCHAR(10) NULL COMMENT '是否新店(是/否)', + F_NewStoreProtectionStage INT NULL DEFAULT 0 COMMENT '新店保护阶段(0/1/2)', + + -- 二、业绩相关字段 + F_StoreTotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店总业绩(门店开单业绩-门店退卡业绩)', + F_StoreBillingPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店开单业绩', + F_StoreRefundPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店退卡业绩', + F_StoreLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店生命线', + F_PerformanceCompletionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩完成率(门店业绩/门店生命线)', + + -- 三、考核相关字段 + F_PerformanceReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '业绩是否达标(是/否,业绩≥生命线为是)', + F_HeadCountReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '人头是否达标(是/否,实际人头≥目标人头为是)', + F_ConsumeReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '消耗是否达标(是/否,实际消耗≥目标消耗为是,仅老店考核)', + F_AssessmentDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '考核扣款金额(未达标指标数×500)', + F_UnreachedIndicatorCount INT NOT NULL DEFAULT 0 COMMENT '未达标指标数量(老店最多3个,新店最多2个)', + + -- 四、人头相关字段 + F_HeadCount INT NOT NULL DEFAULT 0 COMMENT '进店消耗人数(有消费金额的,按门店按月去重客户数)', + F_TargetHeadCount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '目标人头数(从lq_md_target获取)', + + -- 五、消耗相关字段 + F_StoreConsume DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店消耗金额(门店当月总消耗)', + F_TargetConsume DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '目标消耗金额(从lq_md_target获取)', + + -- 六、提成相关字段(阶梯提成) + F_CommissionRateBelowLifeline DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '≤生命线部分提成比例(老店:A类2%,B类2.5%,C类3%;新店:统一2%)', + F_CommissionRateAboveLifeline DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '>生命线部分提成比例(老店:A类2.5%,B类3%,C类3.5%;新店:统一2.5%)', + F_CommissionAmountBelowLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '≤生命线部分提成金额', + F_CommissionAmountAboveLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '>生命线部分提成金额', + F_TotalCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成总金额(≤生命线部分+>生命线部分)', + + -- 七、底薪相关字段 + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 3500.00 COMMENT '底薪金额(固定3500元)', + F_ActualBaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实际底薪(底薪-考核扣款)', + + -- 八、考勤相关字段 + F_WorkingDays INT NOT NULL DEFAULT 0 COMMENT '在店天数', + F_LeaveDays INT NOT NULL DEFAULT 0 COMMENT '请假天数', + + -- 九、工资计算字段 + F_GrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '应发工资(实际底薪+提成总金额)', + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(应发工资-扣款合计+补贴合计+奖金)', + + -- 十、扣款相关字段 + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款', + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款', + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款', + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保', + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励', + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费', + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用', + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用', + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计', + + -- 十一、补贴相关字段 + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴', + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴', + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴', + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴', + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计', + + -- 十二、奖金相关字段 + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金', + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金', + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金', + + -- 十三、支付相关字段 + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)', + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额', + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额', + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月', + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额', + + -- 十四、系统字段 + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)', + F_CreateTime DATETIME NOT NULL COMMENT '创建时间', + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间', + F_CreateUser VARCHAR(50) NULL COMMENT '创建人', + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人', + + -- 主键约束 + PRIMARY KEY (F_Id), + + -- 唯一索引:确保同一员工同一月份只有一条记录 + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth), + + -- 普通索引 + KEY `idx_store_id` (F_StoreId), + KEY `idx_statistics_month` (F_StatisticsMonth), + KEY `idx_employee_id` (F_EmployeeId), + KEY `idx_store_category` (F_StoreCategory), + KEY `idx_create_time` (F_CreateTime) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主任工资统计表'; + +-- ============================================ +-- 表结构说明 +-- ============================================ +/* +表名:lq_director_salary_statistics(主任工资统计表) + +功能说明: +1. 存储主任每月的工资计算数据 +2. 包括底薪、提成、考核扣款、扣款、补贴、奖金、支付等信息 +3. 支持按门店、员工、月份查询 + +主要字段说明: +- F_StoreCategory:门店分类(1=A类,2=B类,3=C类),用于确定提成比例 +- F_StoreTotalPerformance:门店总业绩,用于计算提成 +- F_StoreLifeline:门店生命线,用于判断提成比例和考核 +- F_HeadCount:进店消耗人数,用于考核 +- F_StoreConsume:门店消耗金额,用于考核(仅老店) +- F_BaseSalary:底薪(固定3500元) +- F_AssessmentDeduction:考核扣款(未达标指标数×500) +- F_ActualBaseSalary:实际底薪(底薪-考核扣款) +- F_CommissionAmountBelowLifeline:≤生命线部分提成金额 +- F_CommissionAmountAboveLifeline:>生命线部分提成金额 +- F_TotalCommissionAmount:提成总金额(阶梯提成) + +索引说明: +- 主键索引:F_Id +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录) +- 普通索引: + - F_StoreId:按门店查询 + - F_StatisticsMonth:按月份查询 + - F_EmployeeId:按员工查询 + - F_StoreCategory:按门店分类查询 + - F_CreateTime:按创建时间查询 + +数据校验要求: +1. 门店分类(F_StoreCategory)必须设置,不允许为NULL +2. 门店生命线(F_StoreLifeline)必须设置,未设置应报错 +3. 目标人头数(F_TargetHeadCount)必须设置(用于考核) +4. 目标消耗(F_TargetConsume)必须设置(老店考核用) + +计算公式: +- 应发工资 = 实际底薪 + 提成总金额 +- 实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金 +- 业绩完成率 = 门店业绩 / 门店生命线 × 100% +- 实际底薪 = 底薪(3500)- 考核扣款 +- 考核扣款 = 未达标指标数 × 500 +- 提成总金额 = ≤生命线部分提成金额 + >生命线部分提成金额 + +提成计算规则(阶梯提成): +老店: +- A类门店:≤生命线部分2%,>生命线部分2.5% +- B类门店:≤生命线部分2.5%,>生命线部分3% +- C类门店:≤生命线部分3%,>生命线部分3.5% + +新店(统一标准): +- ≤生命线部分2%,>生命线部分2.5% + +考核规则: +老店(3个指标): +- 业绩考核:门店业绩是否达到门店生命线 +- 人头考核:进店消耗人数是否达到目标人头数 +- 消耗考核:门店消耗是否达到目标消耗 +- 每个未达标指标扣500元 + +新店(2个指标): +- 业绩考核:门店业绩是否达到门店生命线 +- 人头考核:进店消耗人数是否达到目标人头数 +- 每个未达标指标扣500元 +- 新店不考核消耗 +*/ + diff --git a/sql/创建店助工资统计表.sql b/sql/创建店助工资统计表.sql new file mode 100644 index 0000000..dc881f0 --- /dev/null +++ b/sql/创建店助工资统计表.sql @@ -0,0 +1,159 @@ +-- ============================================ +-- 创建店助工资统计表 +-- 功能:存储店助每月的工资计算数据,包括底薪、提成、阶段奖励、扣款、补贴、奖金、支付等信息 +-- 创建时间:2025年 +-- ============================================ + +-- 删除表(如果存在) +DROP TABLE IF EXISTS lq_assistant_salary_statistics; + +-- ============================================ +-- 创建店助工资统计表 +-- ============================================ +CREATE TABLE lq_assistant_salary_statistics ( + -- 主键 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', + + -- 一、基础信息字段 + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID', + F_StoreName VARCHAR(200) NOT NULL COMMENT '门店名称', + F_Position VARCHAR(50) NOT NULL COMMENT '核算岗位(店助/店助主任)', + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名', + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID', + F_StatisticsMonth VARCHAR(20) NOT NULL COMMENT '统计月份(YYYYMM格式)', + F_StoreType INT NULL COMMENT '门店类型(200平/旗舰店)', + F_StoreCategory INT NOT NULL COMMENT '门店分类(1=A类,2=B类,3=C类)', + F_IsNewStore VARCHAR(10) NULL COMMENT '是否新店(是/否)', + F_NewStoreProtectionStage INT NULL DEFAULT 0 COMMENT '新店保护阶段(0/1/2)', + + -- 二、业绩相关字段 + F_StoreTotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店总业绩(门店开单业绩-门店退卡业绩)', + F_StoreBillingPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店开单业绩', + F_StoreRefundPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店退卡业绩', + F_StoreLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店生命线', + F_PerformanceCompletionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩完成率(门店业绩/门店生命线)', + + -- 三、提成相关字段 + F_CommissionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '提成比例(0%/0.4%/0.6%)', + F_CommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成金额(门店业绩×提成比例)', + + -- 四、阶段奖励相关字段 + F_HeadCount INT NOT NULL DEFAULT 0 COMMENT '进店消耗人数(有消费金额的,按门店按月去重客户数)', + F_Stage1TargetHeadCount INT NOT NULL DEFAULT 0 COMMENT '第一阶段目标人数', + F_Stage2TargetHeadCount INT NOT NULL DEFAULT 0 COMMENT '第二阶段目标人数', + F_ReachedStage1 VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '是否达到第一阶段(是/否)', + F_ReachedStage2 VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '是否达到第二阶段(是/否)', + F_StageRewardAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '阶段奖励金额(0/200/400元)', + F_Stage1Reward DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '第一阶段奖励金额(0或200元)', + F_Stage2Reward DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '第二阶段奖励金额(0或200元)', + + -- 五、底薪相关字段 + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '底薪金额(A类3000,B类3100,C类3200)', + + -- 六、固定奖励字段 + F_PhoneManagementFee DECIMAL(18,2) NOT NULL DEFAULT 150.00 COMMENT '手机管理费(固定150元/月)', + + -- 七、考勤相关字段 + F_WorkingDays INT NOT NULL DEFAULT 0 COMMENT '在店天数', + F_LeaveDays INT NOT NULL DEFAULT 0 COMMENT '请假天数', + + -- 八、工资计算字段 + F_GrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '应发工资(底薪+提成+阶段奖励+固定奖励)', + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(应发工资-扣款合计+补贴合计+奖金)', + + -- 九、扣款相关字段 + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款', + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款', + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款', + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保', + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励', + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费', + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用', + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用', + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计', + + -- 十、补贴相关字段 + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴', + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴', + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴', + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴', + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计', + + -- 十一、奖金相关字段 + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金', + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金', + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金', + + -- 十二、支付相关字段 + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)', + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额', + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额', + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月', + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额', + + -- 十三、系统字段 + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)', + F_CreateTime DATETIME NOT NULL COMMENT '创建时间', + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间', + F_CreateUser VARCHAR(50) NULL COMMENT '创建人', + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人', + + -- 主键约束 + PRIMARY KEY (F_Id), + + -- 唯一索引:确保同一员工同一月份只有一条记录 + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth), + + -- 普通索引 + KEY `idx_store_id` (F_StoreId), + KEY `idx_statistics_month` (F_StatisticsMonth), + KEY `idx_employee_id` (F_EmployeeId), + KEY `idx_store_category` (F_StoreCategory), + KEY `idx_create_time` (F_CreateTime) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店助工资统计表'; + +-- ============================================ +-- 表结构说明 +-- ============================================ +/* +表名:lq_assistant_salary_statistics(店助工资统计表) + +功能说明: +1. 存储店助每月的工资计算数据 +2. 包括底薪、提成、阶段奖励、扣款、补贴、奖金、支付等信息 +3. 支持按门店、员工、月份查询 + +主要字段说明: +- F_StoreCategory:门店分类(1=A类,2=B类,3=C类),用于确定底薪 +- F_StoreTotalPerformance:门店总业绩,用于计算提成 +- F_StoreLifeline:门店生命线,用于判断提成比例 +- F_HeadCount:进店消耗人数,用于判断阶段奖励 +- F_Stage1TargetHeadCount/F_Stage2TargetHeadCount:阶段目标人数 +- F_BaseSalary:底薪(A类3000,B类3100,C类3200) +- F_CommissionAmount:提成金额(门店业绩×提成比例) +- F_StageRewardAmount:阶段奖励金额(0/200/400元) +- F_PhoneManagementFee:手机管理费(固定150元/月) + +索引说明: +- 主键索引:F_Id +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录) +- 普通索引: + - F_StoreId:按门店查询 + - F_StatisticsMonth:按月份查询 + - F_EmployeeId:按员工查询 + - F_StoreCategory:按门店分类查询 + - F_CreateTime:按创建时间查询 + +数据校验要求: +1. 门店分类(F_StoreCategory)必须设置,不允许为NULL +2. 门店生命线(F_StoreLifeline)必须设置,未设置应报错 +3. 阶段目标(F_Stage1TargetHeadCount、F_Stage2TargetHeadCount)必须设置,未设置应报错 + +计算公式: +- 应发工资 = 底薪 + 提成金额 + 阶段奖励金额 + 手机管理费 +- 实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金 +- 业绩完成率 = 门店业绩 / 门店生命线 × 100% +- 提成比例:根据门店业绩与门店生命线的比例确定(0%/0.4%/0.6%) +- 阶段奖励:根据进店消耗人数是否达到阶段目标(0/200/400元) +*/ + diff --git a/sql/创建报销多级审批流程表.sql b/sql/创建报销多级审批流程表.sql new file mode 100644 index 0000000..f17182f --- /dev/null +++ b/sql/创建报销多级审批流程表.sql @@ -0,0 +1,89 @@ +-- 报销多级审批流程改造 - 数据库表结构 +-- 执行时间:2024年 +-- 说明:支持3-5个动态审批节点,每个报销申请在创建时设置节点和审批人,支持通过/不通过/退回操作 + +-- 1. 报销申请节点表(每个报销申请的节点配置) +CREATE TABLE IF NOT EXISTS `lq_reimbursement_application_node` ( + `F_Id` varchar(50) NOT NULL COMMENT '节点编号', + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID', + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)', + `F_NodeName` varchar(100) DEFAULT NULL COMMENT '节点名称', + `F_ApprovalType` varchar(20) DEFAULT '会签' COMMENT '审批类型(会签/或签)', + `F_IsRequired` int DEFAULT 1 COMMENT '是否必审(1-必审,0-可选)', + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`F_Id`), + KEY `idx_application_id` (`F_ApplicationId`), + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报销申请节点表'; + +-- 2. 报销申请节点审批人表(每个节点的审批人,在创建报销申请时指定) +CREATE TABLE IF NOT EXISTS `lq_reimbursement_application_node_user` ( + `F_Id` varchar(50) NOT NULL COMMENT '记录编号', + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID', + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号', + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)', + `F_UserId` varchar(50) NOT NULL COMMENT '审批人ID', + `F_UserName` varchar(100) DEFAULT NULL COMMENT '审批人姓名', + `F_SortOrder` int DEFAULT 0 COMMENT '排序', + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`F_Id`), + KEY `idx_application_id` (`F_ApplicationId`), + KEY `idx_node_id` (`F_NodeId`), + KEY `idx_user_id` (`F_UserId`), + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`), + UNIQUE KEY `uk_application_node_user` (`F_ApplicationId`, `F_NodeId`, `F_UserId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报销申请节点审批人表'; + +-- 3. 审批记录表 +CREATE TABLE IF NOT EXISTS `lq_reimbursement_approval_record` ( + `F_Id` varchar(50) NOT NULL COMMENT '记录编号', + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID', + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号', + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)', + `F_ApproverId` varchar(50) NOT NULL COMMENT '审批人ID', + `F_ApproverName` varchar(100) DEFAULT NULL COMMENT '审批人姓名', + `F_ApprovalResult` varchar(20) NOT NULL COMMENT '审批结果(通过/不通过/退回)', + `F_ApprovalOpinion` varchar(500) DEFAULT NULL COMMENT '审批意见', + `F_ApprovalTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '审批时间', + `F_IsCurrentNode` int DEFAULT 0 COMMENT '是否当前节点(1-是,0-否)', + PRIMARY KEY (`F_Id`), + KEY `idx_application_id` (`F_ApplicationId`), + KEY `idx_node_id` (`F_NodeId`), + KEY `idx_approver_id` (`F_ApproverId`), + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='审批记录表'; + +-- 4. 修改报销申请表,添加新字段 +ALTER TABLE `lq_reimbursement_application` + ADD COLUMN `F_NodeCount` int DEFAULT 0 COMMENT '节点数量(3,4,5)' AFTER `F_PurchaseRecordsId`, + ADD COLUMN `F_CurrentNodeOrder` int DEFAULT 0 COMMENT '当前审批节点(0-待审批,1-N节点,N+1-已完成)' AFTER `F_NodeCount`, + ADD COLUMN `F_CurrentNodeId` varchar(50) DEFAULT NULL COMMENT '当前节点ID' AFTER `F_CurrentNodeOrder`, + ADD COLUMN `F_ApprovalStatus` varchar(20) DEFAULT '待审批' COMMENT '审批状态(待审批/审批中/已通过/未通过/已退回)' AFTER `F_CurrentNodeId`, + ADD COLUMN `F_ReturnedNodeOrder` int DEFAULT NULL COMMENT '退回节点' AFTER `F_ApprovalStatus`, + ADD COLUMN `F_ReturnedReason` varchar(500) DEFAULT NULL COMMENT '退回原因' AFTER `F_ReturnedNodeOrder`, + ADD KEY `idx_current_node` (`F_CurrentNodeId`), + ADD KEY `idx_approval_status` (`F_ApprovalStatus`), + ADD KEY `idx_node_count` (`F_NodeCount`); + +-- 5. 数据迁移:更新已有数据的审批状态 +-- 已审批的数据(假设为4个节点) +UPDATE `lq_reimbursement_application` +SET `F_NodeCount` = 4, + `F_CurrentNodeOrder` = 5, + `F_ApprovalStatus` = '已通过' +WHERE `F_ApproveStatus` = '已审批'; + +-- 未通过的数据(假设为4个节点) +UPDATE `lq_reimbursement_application` +SET `F_NodeCount` = 4, + `F_CurrentNodeOrder` = 1, + `F_ApprovalStatus` = '未通过' +WHERE `F_ApproveStatus` = '未通过'; + +-- 待审批的数据 +UPDATE `lq_reimbursement_application` +SET `F_NodeCount` = 4, -- 默认4个节点,实际使用时需要根据具体情况设置 + `F_CurrentNodeOrder` = 0, + `F_ApprovalStatus` = '待审批' +WHERE `F_ApproveStatus` = '待审批' OR `F_ApproveStatus` IS NULL; + diff --git a/sql/添加BASE_USER表是否在职字段.sql b/sql/添加BASE_USER表是否在职字段.sql new file mode 100644 index 0000000..08642b4 --- /dev/null +++ b/sql/添加BASE_USER表是否在职字段.sql @@ -0,0 +1,10 @@ +-- 在 BASE_USER 表中添加是否在职字段 +-- 执行时间:2024年 +-- 说明:添加 F_IsOnJob 字段,用于标识员工是否在职(1-在职,0-离职) + +ALTER TABLE BASE_USER +ADD COLUMN F_IsOnJob INT DEFAULT 1 COMMENT '是否在职(1-在职,0-离职)' AFTER F_GW; + +-- 更新现有数据:默认所有用户为在职状态(如果字段已存在,此语句会报错,可忽略) +-- UPDATE BASE_USER SET F_IsOnJob = 1 WHERE F_IsOnJob IS NULL; + diff --git a/主任工资计算规则梳理.md b/主任工资计算规则梳理.md new file mode 100644 index 0000000..55a72fc --- /dev/null +++ b/主任工资计算规则梳理.md @@ -0,0 +1,454 @@ +# 主任工资计算规则梳理 + +## 📋 概述 + +主任工资由以下几个部分组成: +1. **底薪**:固定3500元,根据考核指标扣款 +2. **提成**:根据门店业绩与门店生命线的关系,使用阶梯提成模式计算 + +--- + +## 💰 工资组成规则 + +### 1. 底薪规则 + +**固定底薪**:3500元 + +#### 老店主任底薪考核 + +**考核指标**(3个): +1. **业绩考核**:门店业绩是否达到门店生命线 +2. **人头考核**:进店消耗人数是否达到目标人头数 +3. **消耗考核**:门店消耗是否达到目标消耗 + +**扣款规则**: +- 每个指标未达到:扣除500元 +- 如果3个指标都未达到:扣除1500元(500 × 3) + +**计算公式**: +``` +底薪 = 3500 - (未达标指标数 × 500) +``` + +#### 新店主任底薪考核 + +**考核指标**(2个): +1. **业绩考核**:门店业绩是否达到门店生命线 +2. **人头考核**:进店消耗人数是否达到目标人头数 + +**扣款规则**: +- 每个指标未达到:扣除500元 +- 如果2个指标都未达到:扣除1000元(500 × 2) + +**计算公式**: +``` +底薪 = 3500 - (未达标指标数 × 500) +``` + +**注意**:新店不考核消耗指标 + +--- + +### 2. 提成规则 + +**提成计算方式**:阶梯提成模式 + +#### 老店主任提成规则 + +根据门店分类(A、B、C类)和业绩是否超过生命线,使用不同的阶梯提成比例: + +| 门店分类 | 业绩 ≤ 生命线部分 | 业绩 > 生命线部分 | +|---------|----------------|-----------------| +| A类门店 | 2% | 2.5% | +| B类门店 | 2.5% | 3% | +| C类门店 | 3% | 3.5% | + +**计算公式**: +``` +如果 业绩 ≤ 生命线: + 提成 = 业绩 × 对应提成比例(≤生命线部分) + +如果 业绩 > 生命线: + 提成 = 生命线 × 对应提成比例(≤生命线部分) + (业绩 - 生命线) × 对应提成比例(>生命线部分) +``` + +**计算示例**: +- 生命线:100,000元 +- 业绩:150,000元 +- 门店分类:A类 + +计算过程: +1. 业绩(150,000)> 生命线(100,000) +2. ≤生命线部分:100,000 × 2% = 2,000元 +3. >生命线部分:(150,000 - 100,000) × 2.5% = 50,000 × 2.5% = 1,250元 +4. 总提成:2,000 + 1,250 = 3,250元 + +#### 新店主任提成规则 + +**统一标准**(不区分A、B、C类门店): + +| 业绩范围 | 提成比例 | +|---------|---------| +| 业绩 ≤ 生命线部分 | 2% | +| 业绩 > 生命线部分 | 2.5% | + +**计算公式**: +``` +如果 业绩 ≤ 生命线: + 提成 = 业绩 × 2% + +如果 业绩 > 生命线: + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5% +``` + +**计算示例**: +- 生命线:100,000元 +- 业绩:150,000元 + +计算过程: +1. 业绩(150,000)> 生命线(100,000) +2. ≤生命线部分:100,000 × 2% = 2,000元 +3. >生命线部分:(150,000 - 100,000) × 2.5% = 50,000 × 2.5% = 1,250元 +4. 总提成:2,000 + 1,250 = 3,250元 + +--- + +## 📊 最终工资计算 + +### 计算公式 + +``` +最终工资 = 底薪 + 提成 +``` + +其中: +- **底薪** = 3500 - (未达标指标数 × 500) +- **提成** = 根据阶梯提成规则计算 + +### 计算示例 + +#### 示例1:老店主任(A类门店,全部达标) + +**基础数据**: +- 门店分类:A类 +- 门店生命线:100,000元 +- 门店业绩:150,000元 +- 目标人头:100人 +- 实际人头:120人 +- 目标消耗:80,000元 +- 实际消耗:90,000元 +- 是否新店:否 + +**计算过程**: + +1. **底薪计算**: + - 业绩考核:150,000 ≥ 100,000 ✓(达标) + - 人头考核:120 ≥ 100 ✓(达标) + - 消耗考核:90,000 ≥ 80,000 ✓(达标) + - 未达标指标数:0 + - 底薪 = 3500 - (0 × 500) = 3500元 + +2. **提成计算**: + - 业绩(150,000)> 生命线(100,000) + - ≤生命线部分:100,000 × 2% = 2,000元 + - >生命线部分:(150,000 - 100,000) × 2.5% = 1,250元 + - 总提成 = 2,000 + 1,250 = 3,250元 + +3. **最终工资**: + - 最终工资 = 3500 + 3250 = 6,750元 + +#### 示例2:老店主任(B类门店,部分未达标) + +**基础数据**: +- 门店分类:B类 +- 门店生命线:100,000元 +- 门店业绩:80,000元 +- 目标人头:100人 +- 实际人头:90人 +- 目标消耗:80,000元 +- 实际消耗:75,000元 +- 是否新店:否 + +**计算过程**: + +1. **底薪计算**: + - 业绩考核:80,000 < 100,000 ✗(未达标) + - 人头考核:90 < 100 ✗(未达标) + - 消耗考核:75,000 < 80,000 ✗(未达标) + - 未达标指标数:3 + - 底薪 = 3500 - (3 × 500) = 3500 - 1500 = 2000元 + +2. **提成计算**: + - 业绩(80,000)< 生命线(100,000) + - 提成 = 80,000 × 2.5% = 2,000元 + +3. **最终工资**: + - 最终工资 = 2000 + 2000 = 4,000元 + +#### 示例3:新店主任(全部达标) + +**基础数据**: +- 门店生命线:100,000元 +- 门店业绩:150,000元 +- 目标人头:100人 +- 实际人头:120人 +- 是否新店:是 + +**计算过程**: + +1. **底薪计算**: + - 业绩考核:150,000 ≥ 100,000 ✓(达标) + - 人头考核:120 ≥ 100 ✓(达标) + - 未达标指标数:0 + - 底薪 = 3500 - (0 × 500) = 3500元 + +2. **提成计算**: + - 业绩(150,000)> 生命线(100,000) + - ≤生命线部分:100,000 × 2% = 2,000元 + - >生命线部分:(150,000 - 100,000) × 2.5% = 1,250元 + - 总提成 = 2,000 + 1,250 = 3,250元 + +3. **最终工资**: + - 最终工资 = 3500 + 3250 = 6,750元 + +--- + +## 🔍 数据来源说明 + +### 1. 门店业绩 + +**数据来源**: +- 开单业绩:`lq_kd_kdjlb` 表 + - 字段:`Djmd`(门店ID)、`Sfyj`(实付业绩)、`Kdrq`(开单日期) + - 条件:`F_IsEffective = 1`(有效记录) +- 退卡业绩:`lq_hytk_hytk` 表 + - 字段:`Md`(门店ID)、`F_ActualRefundAmount`(实际退卡金额,优先使用,如果没有则用`Tkje`)、`Tksj`(退卡时间) + - 条件:`F_IsEffective = 1`(有效记录) + +**计算公式**: +``` +门店业绩 = SUM(开单实付) - SUM(退卡金额) +``` + +### 2. 门店生命线 + +**数据来源**:`lq_md_target` 表 +- 字段:`F_StoreLifeline`(门店生命线) +- 查询条件:`F_StoreId` = 门店ID,`F_Month` = 统计月份(YYYYMM格式) + +### 3. 门店分类 + +**数据来源**:`lq_mdxx` 表 +- 字段:`F_StoreCategory`(门店分类) + - `1` = A类门店 + - `2` = B类门店 + - `3` = C类门店 + +### 4. 人头数据(进店消耗人数) + +**数据来源**: +- 主表:`lq_xh_hyhk`(耗卡记录表) +- 关联表:`lq_xh_jksyj`(健康师业绩表,用于判断是否有消费金额) + +**统计规则**: +- 有消费金额的,按门店按月去重客户数 +- 只统计有效记录(`F_IsEffective = 1`) +- 只统计有消费金额的记录(`jksyj > 0`) + +**目标人头数**: +- 数据来源:`lq_md_target` 表 +- 字段:`F_StoreHeadcountTarget`(门店人头目标) + +### 5. 消耗数据 + +**数据来源**:`lq_xh_jksyj` 表 +- 字段:`jksyj`(健康师业绩,即消耗金额) +- 关联:通过 `glkdbh` 关联到 `lq_xh_hyhk.F_Id` +- 条件:`F_IsEffective = 1`(有效记录) + +**统计规则**: +- 按门店统计当月总消耗金额 + +**目标消耗**: +- 数据来源:`lq_md_target` 表 +- 字段:`F_StoreConsumeTarget`(门店消耗目标) + +### 6. 新店信息 + +**数据来源**:`lq_md_xdbhsj` 表 +- 字段: + - `Mdid`(门店ID) + - `Bhkssj`(保护开始时间) + - `Bhjssj`(保护结束时间) + - `Sfqy`(是否启用,1=启用) + - `Stage`(新店保护阶段) + +**判断逻辑**: +- 如果统计月份的第一天在保护期内(`Bhkssj <= startDate && Bhjssj >= startDate`),则为新店 +- 否则为老店 + +### 7. 主任员工信息 + +**数据来源**:`BASE_USER` 表 +- 字段: + - `F_Id`(员工ID) + - `F_RealName`(员工姓名) + - `F_Gw`(岗位,应为"主任") + - `F_Mdid`(门店ID) + +**查询条件**: +- `F_Gw = "主任"` +- `F_DeleteMark = null`(未删除) +- `F_EnabledMark = 1`(启用) + +--- + +## 📝 计算流程 + +### 1. 获取基础数据 + +- 从 `BASE_USER` 获取主任员工列表(岗位为"主任") +- 获取门店信息(`lq_mdxx`) +- 获取门店目标信息(`lq_md_target`) +- 获取新店保护信息(`lq_md_xdbhsj`) + +### 2. 计算门店业绩 + +- 统计开单业绩(`lq_kd_kdjlb`) +- 统计退卡业绩(`lq_hytk_hytk`) +- 计算门店总业绩 = 开单业绩 - 退卡业绩 + +### 3. 计算门店消耗 + +- 统计门店当月总消耗金额(`lq_xh_jksyj`) + +### 4. 统计进店消耗人数 + +- 统计有消费金额的,按门店按月去重客户数 + +### 5. 判断新店/老店 + +- 根据新店保护信息判断是否为新店 + +### 6. 计算底薪 + +- 判断业绩是否达标(业绩 ≥ 生命线) +- 判断人头是否达标(实际人头 ≥ 目标人头) +- 判断消耗是否达标(实际消耗 ≥ 目标消耗,仅老店考核) +- 根据未达标指标数量计算扣款 +- 底薪 = 3500 - (未达标指标数 × 500) + +### 7. 计算提成 + +- 判断新店/老店 +- 判断业绩是否超过生命线 +- 根据门店分类(老店)和业绩是否超过生命线,确定阶梯提成比例 +- 计算提成金额(阶梯提成模式) + +### 8. 计算最终工资 + +- 最终工资 = 底薪 + 提成 + +--- + +## ⚠️ 注意事项 + +### 1. 数据校验 + +- 门店分类必须设置,未设置应报错 +- 门店生命线必须设置,未设置应报错 +- 门店人头目标必须设置(用于考核) +- 门店消耗目标必须设置(老店考核用) + +### 2. 数据一致性 + +- 门店业绩计算逻辑必须与其他统计接口保持一致 +- 人头统计逻辑必须与其他统计接口保持一致 +- 消耗统计逻辑必须与其他统计接口保持一致 + +### 3. 边界情况 + +- 如果门店没有业绩数据,业绩为0 +- 如果门店没有人头数据,人头为0 +- 如果门店没有消耗数据,消耗为0 +- 新店不考核消耗,只考核业绩和人头 + +### 4. 提成计算注意事项 + +- **阶梯提成模式**:必须严格按照阶梯提成规则计算,不能使用单一提成比例 +- **业绩分段计算**: + - 如果业绩 ≤ 生命线:只计算 ≤ 生命线部分的提成 + - 如果业绩 > 生命线:分别计算 ≤ 生命线部分和 > 生命线部分的提成,然后相加 + +--- + +## 📋 关键计算公式总结 + +### 底薪计算公式 + +``` +底薪 = 3500 - (未达标指标数 × 500) +``` + +其中: +- **老店**:未达标指标数 = 未达标的指标数量(业绩、人头、消耗,最多3个) +- **新店**:未达标指标数 = 未达标的指标数量(业绩、人头,最多2个) + +### 提成计算公式 + +#### 老店提成计算公式 + +**A类门店**: +``` +如果 业绩 ≤ 生命线: + 提成 = 业绩 × 2% +如果 业绩 > 生命线: + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5% +``` + +**B类门店**: +``` +如果 业绩 ≤ 生命线: + 提成 = 业绩 × 2.5% +如果 业绩 > 生命线: + 提成 = 生命线 × 2.5% + (业绩 - 生命线) × 3% +``` + +**C类门店**: +``` +如果 业绩 ≤ 生命线: + 提成 = 业绩 × 3% +如果 业绩 > 生命线: + 提成 = 生命线 × 3% + (业绩 - 生命线) × 3.5% +``` + +#### 新店提成计算公式 + +``` +如果 业绩 ≤ 生命线: + 提成 = 业绩 × 2% +如果 业绩 > 生命线: + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5% +``` + +--- + +## ✅ 验证要点 + +1. **底薪计算验证**: + - 验证考核指标判断是否正确 + - 验证扣款金额是否正确(每个未达标指标扣500元) + - 验证新店不考核消耗 + +2. **提成计算验证**: + - 验证阶梯提成计算是否正确 + - 验证业绩分段计算是否正确 + - 验证不同门店分类的提成比例是否正确 + - 验证新店统一提成标准是否正确 + +3. **数据来源验证**: + - 验证门店业绩计算是否正确 + - 验证人头统计是否正确 + - 验证消耗统计是否正确 + - 验证新店判断是否正确 + diff --git a/健康师工资信息说明.html b/健康师工资信息说明.html index f425350..57c7e94 100644 --- a/健康师工资信息说明.html +++ b/健康师工资信息说明.html @@ -192,6 +192,7 @@
其他业绩加:4,000.00
其他业绩减:0.00
队伍业绩:92,548.30
+
队伍总消耗:68,582.05
占比:0.18
新客业绩:0.00
新客转化率:0.00
@@ -206,7 +207,9 @@
消耗与项目数据
消耗:22,650.24
+
日均消耗:838.90
项目数:114.00
+
日均项目数:4.22
到店人头:57
@@ -237,7 +240,7 @@
底薪与补贴
-
健康师底薪:2,000.00
+
健康师底薪:2,200.00
手工费:1,583.00
额外手工费:0.00
车补:0.00
@@ -249,7 +252,7 @@
实发工资 - 5,050.98 + 5,250.98
@@ -284,16 +287,16 @@
6. 顾问提成 (740.39)
92,548.30 × 0.8% = 740.39
-
未达顾问提成标准
+
高级顾问: 战队业绩(92,548.30)≥6万 且 战队消耗(68,582.05)≥6万 → 提成率 0.8%
-
7. 实发工资 (5,050.98)
-
2,000.00 + 1,467.98 + 1,583.00 = 5,050.98
+
7. 实发工资 (5,250.98)
+
2,200.00 + 1,467.98 + 1,583.00 = 5,250.98
底薪 + 提成合计 + 手工费
- + @@ -329,6 +332,7 @@
其他业绩加:0.00
其他业绩减:0.00
队伍业绩:92,548.30
+
队伍总消耗:68,582.05
占比:0.38
新客业绩:0.00
新客转化率:0.00
@@ -343,7 +347,9 @@
消耗与项目数据
消耗:18,341.43
+
日均消耗:797.45
项目数:96.00
+
日均项目数:4.17
到店人头:50
@@ -418,20 +424,20 @@ - +
-

罗丹 (健康师)

+

刘恬恬 (健康师)

ID: 766260517810472197
基本信息
-
姓名:罗丹
+
姓名:刘恬恬
门店:绿纤468店
-
员工ID:13540428522
+
员工ID:18863128472
统计月份:202511
岗位:健康师
金三角战队:精英队 (3人)
@@ -444,29 +450,32 @@
业绩数据
总业绩:40,840.10
-
基础业绩:23,190.10
-
合作业绩:17,650.00
+
基础业绩:28,000.50
+
合作业绩:12,839.60
基础奖励业绩:0.00
合作奖励业绩:0.00
其他业绩加:0.00
其他业绩减:0.00
队伍业绩:92,548.30
+
队伍总消耗:68,582.05
占比:0.44
新客业绩:0.00
新客转化率:0.00
升单业绩:0.00
升单人头数:0
-
实际基础业绩:23,190.10
-
实际合作业绩:17,650.00
+
实际基础业绩:28,000.50
+
实际合作业绩:12,839.60
消耗与项目数据
-
消耗:28,095.53
-
项目数:119.00
-
到店人头:64
+
消耗:27,590.38
+
日均消耗:1,061.17
+
项目数:101.50
+
日均项目数:3.90
+
到店人头:51
@@ -484,12 +493,12 @@
提成计算
提点:0.05
-
基础业绩提成:1,101.53
-
合作业绩提成:544.94
+
基础业绩提成:1,330.02
+
合作业绩提成:396.42
顾问提成:0.00
新客业绩提成:0.00
升单业绩提成:0.00
-
提成合计:1,646.47
+
提成合计:1,726.45
@@ -497,7 +506,7 @@
底薪与补贴
健康师底薪:2,000.00
-
手工费:1,310.00
+
手工费:1,302.00
额外手工费:0.00
车补:0.00
少休费:0.00
@@ -508,7 +517,7 @@
实发工资 - 4,956.47 + 5,028.45
@@ -521,18 +530,18 @@
根据提成点表查询得出
-
2. 基础业绩提成 (1,101.53)
-
23,190.10 × 0.95 × 5% = 1,101.53
+
2. 基础业绩提成 (1,330.02)
+
28,000.50 × 0.95 × 5% = 1,330.02
实际基础业绩 × 95% × 提成点
-
3. 合作业绩提成 (544.94)
-
17,650.00 × 0.95 × 0.65 × 5% = 544.94
+
3. 合作业绩提成 (396.42)
+
12,839.60 × 0.95 × 0.65 × 5% = 396.42
实际合作业绩 × 95% × 65% × 提成点
-
4. 实发工资 (4,956.47)
-
2,000.00 + 1,646.47 + 1,310.00 = 4,956.47
+
4. 实发工资 (5,028.45)
+
2,000.00 + 1,726.45 + 1,302.00 = 5,028.45
底薪 + 提成合计 + 手工费
@@ -573,6 +582,7 @@
其他业绩加:6,189.21
其他业绩减:162.07
队伍业绩:28,235.40
+
队伍总消耗:4,199.07
占比:1.00
新客业绩:7,679.50
新客转化率:0.46
@@ -587,7 +597,9 @@
消耗与项目数据
消耗:4,199.07
+
日均消耗:155.52
项目数:89.50
+
日均项目数:3.31
到店人头:53
@@ -609,9 +621,9 @@
基础业绩提成:642.09
合作业绩提成:45.25
顾问提成:0.00
-
新客业绩提成:1,151.93
+
新客业绩提成:1,094.33
升单业绩提成:0.00
-
提成合计:1,839.27
+
提成合计:1,781.67
@@ -630,7 +642,7 @@
实发工资 - 4,953.27 + 4,895.67
@@ -663,13 +675,13 @@
实际合作业绩 × 95% × 65% × 提成点
-
6. 新客转化率提成 (1,151.93)
-
7,679.50 × 15% = 1,151.93
-
新客业绩 × 转化率提成比例(46% → 15%)
+
6. 新客转化率提成 (1,094.33)
+
7,679.50 × 15% × 0.95 = 1,094.33
+
新客业绩 × 转化率提成比例(46% → 15%) × 0.95
-
7. 实发工资 (4,953.27)
-
2,000.00 + 1,839.27 + 1,114.00 = 4,953.27
+
7. 实发工资 (4,895.67)
+
2,000.00 + 1,781.67 + 1,114.00 = 4,895.67
底薪 + 提成合计 + 手工费
@@ -710,6 +722,7 @@
其他业绩加:0.00
其他业绩减:0.00
队伍业绩:5,373.70
+
队伍总消耗:10,102.27
占比:1.00
新客业绩:0.00
新客转化率:0.00
@@ -724,7 +737,9 @@
消耗与项目数据
消耗:10,102.27
+
日均消耗:531.70
项目数:72.00
+
日均项目数:3.79
到店人头:59
@@ -776,8 +791,8 @@
计算过程说明
1. 提成资格判定
-
出勤19天 < 21天 → 无提成资格
-
出勤不足21天,所有提成归零
+
出勤19天 < 20天 → 无提成资格
+
出勤不足20天,所有提成归零
2. 实发工资 (2,880.00)
@@ -789,5 +804,6 @@
+ \ No newline at end of file diff --git a/店助主任工资计算规则梳理.md b/店助主任工资计算规则梳理.md new file mode 100644 index 0000000..56ae0b9 --- /dev/null +++ b/店助主任工资计算规则梳理.md @@ -0,0 +1,450 @@ +# 店助主任工资计算规则梳理 + +## 📋 概述 + +店助主任工资由以下几个部分组成: +1. **底薪**:从工资配置表(`lq_gz`)获取,字段为 `dzzrdx` +2. **提成**:根据门店业绩与门店生命线的比例计算(阶梯提成模式) +3. **阶段奖励**:根据进店消耗人数是否达到阶段目标(同店助) +4. **固定奖励**:手机管理费 + +--- + +# 第一部分:计算规则 + +## 💰 工资组成规则 + +### 1. 底薪规则 + +**计算规则**:从工资配置表(`lq_gz`)获取,字段为 `dzzrdx` + +**重要说明**: +- 底薪从配置表获取,如果未设置,应使用默认值或报错提示 +- 底薪是固定值,不随门店分类变化 + +--- + +### 2. 提成规则 + +**计算公式**:根据门店业绩与门店生命线的比例确定提成比例,使用阶梯提成模式计算 + +#### 提成比例规则 + +| 门店业绩范围 | 提成计算方式 | +|------------|------------| +| 门店业绩 < 门店生命线 × 70% | 0%(无提成) | +| 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% | 0.4%(全部业绩按0.4%计算) | +| 门店业绩 ≥ 门店生命线 × 100% | **阶梯提成**:
- 超过生命线部分:1%
- 剩余部分(≤生命线):0.6% | + +#### 提成计算示例 + +**示例1:业绩未达到70%** +- 门店生命线 = 100,000元 +- 门店业绩 = 60,000元 +- 计算:60,000 < 100,000 × 70% = 70,000 +- 提成金额 = 0元(无提成) + +**示例2:业绩在70%-100%之间** +- 门店生命线 = 100,000元 +- 门店业绩 = 85,000元 +- 计算:70,000 ≤ 85,000 < 100,000 +- 提成金额 = 85,000 × 0.4% = 340元 + +**示例3:业绩超过100%(阶梯提成)** +- 门店生命线 = 100,000元 +- 门店业绩 = 150,000元 +- 计算过程: + 1. 业绩(150,000)> 生命线(100,000) + 2. ≤生命线部分:100,000 × 0.6% = 600元 + 3. >生命线部分:(150,000 - 100,000) × 1% = 50,000 × 1% = 500元 + 4. 总提成 = 600 + 500 = 1,100元 + +**重要说明**: +- 门店生命线必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示 +- 当业绩超过生命线时,必须使用阶梯提成模式,不能使用单一提成比例 +- 提成按门店业绩的百分比计算 + +--- + +### 3. 阶段奖励规则 + +**考核指标**:进店消耗人数(有消费金额的,按门店按月去重客户数) + +#### 奖励规则 + +| 阶段 | 目标人数 | 奖励金额 | 说明 | +|-----|---------|---------|------| +| 第一阶段 | 达到目标(如:100人) | 200元/月 | 只要达到第一阶段目标即可获得 | +| 第二阶段 | 达到目标(如:140人) | 再奖励200元/月 | 在达到第一阶段的基础上,再达到第二阶段目标 | +| 两个阶段都达到 | - | 总计400元/月 | 第一阶段200元 + 第二阶段200元 | + +**奖励说明**: +- 两个阶段的奖励是累加的,不是互斥的 +- 如果只达到第二阶段但未达到第一阶段,只获得第二阶段奖励(200元) +- 如果两个阶段都达到,获得总计400元奖励 + +**统计规则**: +- 按门店统计(`md` 字段) +- 只统计有消费金额的记录(消耗金额 > 0) +- 同一个会员按月去重(`COUNT(DISTINCT hy)`),其中 `hy` 为会员ID +- 只统计当月有效消耗记录(`F_IsEffective = 1`) + +**重要说明**: +- 阶段目标必须在 `lq_md_target` 表中进行设置,如果未设置,奖励金额为0(不报错) +- 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人) + +--- + +### 4. 固定奖励规则 + +**手机管理费**:150元/月 + +**说明**: +- 固定金额,无需计算 +- 每月固定发放 + +--- + +## 📊 完整计算公式 + +``` +店助主任月工资 = 底薪 + 提成 + 阶段奖励 + 固定奖励 + +其中: +- 底薪 = 从lq_gz.dzzrdx获取 +- 提成 = 根据业绩与生命线比例确定(阶梯提成模式) +- 阶段奖励 = 根据进店消耗人数是否达到阶段目标(0/200/400元) +- 固定奖励 = 150元(手机管理费) +``` + +--- + +## ⚠️ 注意事项 + +1. **门店业绩计算**: + - 门店业绩 = 开单业绩 - 退卡业绩 + - 只统计当月有效记录(`F_IsEffective = 1`) + +2. **进店消耗人数统计**: + - 必须使用 `COUNT(DISTINCT hy)` 去重(同一个会员按月去重) + - 只统计有消费金额的记录(消耗金额 > 0) + - 按门店统计(`md` 字段) + - 只统计当月有效消耗记录(`F_IsEffective = 1`) + - 通过关联 `lq_xh_jksyj` 表判断是否有消费金额 + +3. **阶段目标配置**: + - 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人) + - 必须从 `lq_md_target` 表获取对应门店的目标值 + - **重要**:如果 `lq_md_target` 表中未设置阶段目标,奖励金额为0(不报错) + +4. **提成比例判断**: + - 严格按照门店业绩与门店生命线的比例判断 + - 注意边界值:70% 和 100% + - **重要**:当业绩超过生命线时,必须使用阶梯提成模式(超过部分1%,剩余部分0.6%) + +5. **数据一致性**: + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致 + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致 + +6. **数据校验要求**: + - 门店分类(`F_StoreCategory`)必须设置,不允许为NULL,未设置应报错 + - 门店生命线(`F_StoreLifeline`)如果未设置,提成金额为0(不报错) + - 阶段目标(`F_AssistantHeadcountTargetStage1`、`F_AssistantHeadcountTargetStage2`)如果未设置,奖励金额为0(不报错) + +7. **在店天数比例计算**: + - 如果店助主任中间离职或请假,提成和奖励需要按在店天数比例计算 + - 计算公式: + - 店助主任提成 = 门店总提成 ÷ 当月天数 × 在店天数 + - 店助主任奖励 = 门店总奖励 ÷ 当月天数 × 在店天数 + +--- + +# 第二部分:数据来源说明 + +## 📊 数据表说明 + +### 1. `lq_gz` - 工资配置表 + +**用途**:获取店助主任底薪 + +**关键字段**: +- `dzzrdx`:店助主任底薪 + +**查询逻辑**: +- 从配置表获取底薪值 +- 如果未设置,应使用默认值或报错提示 + +--- + +### 2. `lq_mdxx` - 门店信息表 + +**用途**:获取门店分类、门店名称等信息 + +**关键字段**: +- `F_Id`:门店ID(主键) +- `dm`:门店名称 +- `F_StoreCategory`:门店分类(1=A类,2=B类,3=C类) +- `F_StoreType`:门店类型(200平/旗舰店) + +**查询逻辑**: +- 通过门店ID关联获取门店信息 +- 门店分类必须设置,不允许为NULL + +--- + +### 3. `lq_md_target` - 门店目标表 + +**用途**:获取门店生命线和阶段目标 + +**关键字段**: +- `F_StoreId`:门店ID +- `F_Month`:月份(YYYYMM格式) +- `F_StoreLifeline`:门店生命线 +- `F_AssistantHeadcountTargetStage1`:店助人头目标数阶段一 +- `F_AssistantHeadcountTargetStage2`:店助人头目标数阶段二 + +**查询逻辑**: +- 通过门店ID和月份关联获取目标数据 +- 门店生命线如果未设置,提成金额为0 +- 阶段目标如果未设置,奖励金额为0 + +--- + +### 4. `lq_kd_kdjlb` - 开单记录表 + +**用途**:计算门店开单业绩 + +**关键字段**: +- `Djmd`:登记门店(门店ID) +- `Kdrq`:开单日期 +- `Sfyj`:实付业绩 +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 按门店、月份统计开单业绩总和 +- 只统计有效记录(`F_IsEffective = 1`) + +--- + +### 5. `lq_hytk_hytk` - 退卡信息表 + +**用途**:计算门店退卡业绩 + +**关键字段**: +- `md`:门店编号(门店ID) +- `tksj`:退卡时间 +- `F_ActualRefundAmount`:实退金额(优先使用) +- `tkje`:退卡金额(如果实退金额为空则使用) +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 按门店、月份统计退卡金额总和 +- 优先使用 `F_ActualRefundAmount`,如果为空则使用 `tkje` +- 只统计有效记录(`F_IsEffective = 1`) + +--- + +### 6. `lq_xh_hyhk` - 会员耗卡表 + +**用途**:统计进店消耗人数 + +**关键字段**: +- `F_Id`:耗卡编号(主键) +- `md`:门店ID +- `hy`:会员ID(用于去重统计) +- `hksj`:耗卡时间 +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 按门店、月份统计去重会员数 +- 只统计有效记录(`F_IsEffective = 1`) +- 必须关联 `lq_xh_jksyj` 表判断是否有消费金额 + +--- + +### 7. `lq_xh_jksyj` - 耗卡健康师业绩表 + +**用途**:判断是否有消费金额 + +**关键字段**: +- `glkdbh`:关联耗卡记录ID(关联 `lq_xh_hyhk.F_Id`) +- `jksyj`:健康师业绩(消耗金额) +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 用于判断耗卡记录是否有消费金额(`jksyj > 0`) +- 只统计有效记录(`F_IsEffective = 1`) + +--- + +### 8. `lq_attendance_summary` - 考勤汇总表 + +**用途**:获取在店天数 + +**关键字段**: +- `F_UserId`:用户ID(员工ID) +- `F_Year`:年份 +- `F_Month`:月份 +- `F_WorkDays`:出勤天数(在店天数) +- `F_LeaveDays`:请假天数 +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 通过员工ID、年份、月份获取考勤数据 +- 用于计算按在店天数比例计算的提成和奖励 + +--- + +## 📝 数据查询逻辑 + +### 1. 门店业绩计算 + +**计算公式**:门店业绩 = 开单业绩 - 退卡业绩 + +**查询逻辑**: +```sql +-- 1. 计算门店开单业绩 +SELECT COALESCE(SUM(sfyj), 0) as BillingPerformance +FROM lq_kd_kdjlb +WHERE Djmd = @StoreId + AND DATE_FORMAT(Kdrq, '%Y%m') = @Month + AND F_IsEffective = 1 + +-- 2. 计算门店退卡业绩 +SELECT COALESCE(SUM(COALESCE(F_ActualRefundAmount, tkje, 0)), 0) as RefundPerformance +FROM lq_hytk_hytk +WHERE md = @StoreId + AND DATE_FORMAT(tksj, '%Y%m') = @Month + AND F_IsEffective = 1 + +-- 3. 计算门店实际业绩 +门店业绩 = 开单业绩 - 退卡业绩 +``` + +--- + +### 2. 进店消耗人数统计 + +**统计规则**:有消费金额的,按门店按月去重客户数 + +**查询逻辑**: +```sql +-- 统计门店当月进店消耗人数(有消费金额的,按门店按月去重客户数) +SELECT COUNT(DISTINCT hy) as HeadCount +FROM lq_xh_hyhk hk +WHERE hk.md = @StoreId + AND DATE_FORMAT(hk.hksj, '%Y%m') = @Month + AND hk.F_IsEffective = 1 + AND EXISTS ( + -- 确保有消费金额(通过关联消耗业绩表判断) + SELECT 1 + FROM lq_xh_jksyj jksyj + WHERE jksyj.glkdbh = hk.F_Id + AND jksyj.F_IsEffective = 1 + AND jksyj.jksyj > 0 + ) +``` + +**说明**: +- `md`:门店ID,按门店统计 +- `hy`:会员ID,用于去重统计(同一个会员按月去重) +- `hksj`:耗卡时间,用于按月过滤 +- `F_IsEffective = 1`:只统计有效记录 +- **重要**:只统计有消费金额的记录(消耗金额 > 0),通过关联 `lq_xh_jksyj` 表判断是否有消费金额 + +--- + +### 3. 门店分类获取 + +**查询逻辑**: +```sql +SELECT + F_Id as StoreId, + dm as StoreName, + F_StoreCategory as StoreCategory, + F_StoreType as StoreType +FROM lq_mdxx +WHERE F_Id = @StoreId +``` + +**说明**: +- `F_StoreCategory`:门店分类(1=A类,2=B类,3=C类) +- 门店分类必须设置,不允许为NULL + +--- + +### 4. 门店生命线和阶段目标获取 + +**查询逻辑**: +```sql +SELECT + F_StoreLifeline as StoreLifeline, + F_AssistantHeadcountTargetStage1 as Stage1Target, + F_AssistantHeadcountTargetStage2 as Stage2Target +FROM lq_md_target +WHERE F_StoreId = @StoreId + AND F_Month = @Month +``` + +**说明**: +- `F_StoreLifeline`:门店生命线,用于计算提成 +- `F_AssistantHeadcountTargetStage1`:第一阶段目标人数 +- `F_AssistantHeadcountTargetStage2`:第二阶段目标人数 +- 如果门店生命线未设置,提成金额为0 +- 如果阶段目标未设置,奖励金额为0 + +--- + +### 5. 店助主任底薪获取 + +**查询逻辑**: +```sql +SELECT dzzrdx as BaseSalary +FROM lq_gz +LIMIT 1 +``` + +**说明**: +- `dzzrdx`:店助主任底薪 +- 从配置表获取,如果未设置,应使用默认值或报错提示 + +--- + +## 🔄 店助主任与店助的主要区别 + +### 1. 底薪规则 + +| 岗位 | 底薪来源 | +|-----|---------| +| 店助 | 根据门店分类确定(A类3000,B类3100,C类3200) | +| 店助主任 | 从配置表 `lq_gz.dzzrdx` 获取 | + +### 2. 提成规则 + +| 岗位 | 业绩 ≥ 100%时的提成计算 | +|-----|---------------------| +| 店助 | 全部业绩按 0.6% 计算 | +| 店助主任 | **阶梯提成**:
- 超过生命线部分:1%
- 剩余部分(≤生命线):0.6% | + +**示例对比**: +- 门店生命线 = 100,000元 +- 门店业绩 = 150,000元 + +**店助提成**: +- 150,000 × 0.6% = 900元 + +**店助主任提成**: +- ≤生命线部分:100,000 × 0.6% = 600元 +- >生命线部分:(150,000 - 100,000) × 1% = 500元 +- 总提成 = 600 + 500 = 1,100元 + +### 3. 阶段奖励规则 + +**相同**:两个岗位的阶段奖励规则完全一致 + +--- + +## 📅 更新记录 + +- 2025-01-XX:初始版本,根据项目文档梳理店助主任工资计算规则 + diff --git a/店助工资表字段设计.md b/店助工资表字段设计.md new file mode 100644 index 0000000..a26680f --- /dev/null +++ b/店助工资表字段设计.md @@ -0,0 +1,264 @@ +# 店助工资表字段设计 + +## 📋 表名建议 +`lq_assistant_salary_statistics`(店助工资统计表) + +--- + +## 🔍 字段分类说明 + +根据店助工资计算规则,参考健康师工资表结构,店助工资表需要包含以下字段: + +--- + +## 一、基础信息字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 主键ID | F_Id | VARCHAR(50) | 主键,使用YitIdHelper生成 | ✅ | +| 门店ID | F_StoreId | VARCHAR(50) | 关联门店信息表 | ✅ | +| 门店名称 | F_StoreName | VARCHAR(200) | 门店名称(冗余字段,便于查询) | ✅ | +| 核算岗位 | F_Position | VARCHAR(50) | 固定为"店助"或"店助主任" | ✅ | +| 员工姓名 | F_EmployeeName | VARCHAR(100) | 员工姓名 | ✅ | +| 员工ID | F_EmployeeId | VARCHAR(50) | 关联BASE_USER表 | ✅ | +| 统计月份 | F_StatisticsMonth | VARCHAR(20) | 格式:YYYYMM,如:202501 | ✅ | +| 门店类型 | F_StoreType | INT | 门店类型(200平/旗舰店) | ❌ | +| 门店类别 | F_StoreCategory | INT | 门店分类(1=A类,2=B类,3=C类) | ✅ | +| 是否新店 | F_IsNewStore | VARCHAR(10) | 是/否 | ❌ | +| 新店保护阶段 | F_NewStoreProtectionStage | INT | 新店保护阶段(0/1/2) | ❌ | + +--- + +## 二、业绩相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 门店总业绩 | F_StoreTotalPerformance | DECIMAL(18,2) | 门店开单业绩 - 门店退卡业绩 | ✅ | +| 门店开单业绩 | F_StoreBillingPerformance | DECIMAL(18,2) | 门店开单业绩总和 | ✅ | +| 门店退卡业绩 | F_StoreRefundPerformance | DECIMAL(18,2) | 门店退卡业绩总和 | ✅ | +| 门店生命线 | F_StoreLifeline | DECIMAL(18,2) | 门店生命线(从lq_md_target获取) | ✅ | +| 业绩完成率 | F_PerformanceCompletionRate | DECIMAL(18,4) | 门店业绩 / 门店生命线 | ✅ | + +--- + +## 三、提成相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 提成比例 | F_CommissionRate | DECIMAL(18,4) | 根据业绩与生命线比例确定(0%/0.4%/0.6%) | ✅ | +| 提成金额 | F_CommissionAmount | DECIMAL(18,2) | 门店业绩 × 提成比例 | ✅ | + +--- + +## 四、阶段奖励相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 进店消耗人数 | F_HeadCount | INT | 有消费金额的,按门店按月去重客户数 | ✅ | +| 第一阶段目标人数 | F_Stage1TargetHeadCount | INT | 从lq_md_target获取 | ✅ | +| 第二阶段目标人数 | F_Stage2TargetHeadCount | INT | 从lq_md_target获取 | ✅ | +| 是否达到第一阶段 | F_ReachedStage1 | VARCHAR(10) | 是/否 | ✅ | +| 是否达到第二阶段 | F_ReachedStage2 | VARCHAR(10) | 是/否 | ✅ | +| 阶段奖励金额 | F_StageRewardAmount | DECIMAL(18,2) | 根据阶段达成情况计算(0/200/400元) | ✅ | +| 第一阶段奖励 | F_Stage1Reward | DECIMAL(18,2) | 第一阶段奖励金额(0或200元) | ✅ | +| 第二阶段奖励 | F_Stage2Reward | DECIMAL(18,2) | 第二阶段奖励金额(0或200元) | ✅ | + +--- + +## 五、底薪相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 门店分类 | F_StoreCategory | INT | 门店分类(1=A类,2=B类,3=C类) | ✅ | +| 底薪金额 | F_BaseSalary | DECIMAL(18,2) | 根据门店分类确定(A类3000,B类3100,C类3200) | ✅ | + +--- + +## 六、固定奖励字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 手机管理费 | F_PhoneManagementFee | DECIMAL(18,2) | 固定150元/月 | ✅ | + +--- + +## 七、考勤相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 在店天数 | F_WorkingDays | INT | 在店工作天数 | ✅ | +| 请假天数 | F_LeaveDays | INT | 请假天数 | ✅ | + +--- + +## 八、工资计算字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 应发工资 | F_GrossSalary | DECIMAL(18,2) | 底薪 + 提成 + 阶段奖励 + 固定奖励 | ✅ | +| 实发工资 | F_ActualSalary | DECIMAL(18,2) | 应发工资 - 扣款合计 + 补贴合计 + 奖金 | ✅ | + +--- + +## 九、扣款相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 缺卡扣款 | F_MissingCard | DECIMAL(18,2) | 缺卡扣款金额 | ✅ | +| 迟到扣款 | F_LateArrival | DECIMAL(18,2) | 迟到扣款金额 | ✅ | +| 请假扣款 | F_LeaveDeduction | DECIMAL(18,2) | 请假扣款金额 | ✅ | +| 扣社保 | F_SocialInsuranceDeduction | DECIMAL(18,2) | 社保扣款金额 | ✅ | +| 扣除奖励 | F_RewardDeduction | DECIMAL(18,2) | 扣除奖励金额 | ✅ | +| 扣住宿费 | F_AccommodationDeduction | DECIMAL(18,2) | 住宿费扣款金额 | ✅ | +| 扣学习期费用 | F_StudyPeriodDeduction | DECIMAL(18,2) | 学习期费用扣款 | ✅ | +| 扣工作服费用 | F_WorkClothesDeduction | DECIMAL(18,2) | 工作服费用扣款 | ✅ | +| 扣款合计 | F_TotalDeduction | DECIMAL(18,2) | 所有扣款金额总和 | ✅ | + +--- + +## 十、补贴相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 当月培训补贴 | F_MonthlyTrainingSubsidy | DECIMAL(18,2) | 当月培训补贴 | ✅ | +| 当月交通补贴 | F_MonthlyTransportSubsidy | DECIMAL(18,2) | 当月交通补贴 | ✅ | +| 上月培训补贴 | F_LastMonthTrainingSubsidy | DECIMAL(18,2) | 上月培训补贴 | ✅ | +| 上月交通补贴 | F_LastMonthTransportSubsidy | DECIMAL(18,2) | 上月交通补贴 | ✅ | +| 补贴合计 | F_TotalSubsidy | DECIMAL(18,2) | 所有补贴金额总和 | ✅ | + +--- + +## 十一、奖金相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 发奖金 | F_Bonus | DECIMAL(18,2) | 奖金金额 | ✅ | +| 退手机押金 | F_ReturnPhoneDeposit | DECIMAL(18,2) | 退手机押金金额 | ✅ | +| 退住宿押金 | F_ReturnAccommodationDeposit | DECIMAL(18,2) | 退住宿押金金额 | ✅ | + +--- + +## 十二、支付相关字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 当月是否发放 | F_MonthlyPaymentStatus | VARCHAR(20) | 已发放/未发放/部分发放 | ✅ | +| 支付金额 | F_PaidAmount | DECIMAL(18,2) | 已支付金额 | ✅ | +| 待支付金额 | F_PendingAmount | DECIMAL(18,2) | 待支付金额 | ✅ | +| 补发上月 | F_LastMonthSupplement | DECIMAL(18,2) | 补发上月金额 | ✅ | +| 当月支付总额 | F_MonthlyTotalPayment | DECIMAL(18,2) | 当月支付总额 | ✅ | + +--- + +## 十三、系统字段 + +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | +|--------|-----------|------|------|------| +| 是否锁定 | F_IsLocked | INT | 0=未锁定,1=已锁定 | ✅ | +| 创建时间 | F_CreateTime | DATETIME | 创建时间 | ✅ | +| 更新时间 | F_UpdateTime | DATETIME | 更新时间 | ✅ | +| 创建人 | F_CreateUser | VARCHAR(50) | 创建人ID | ❌ | +| 更新人 | F_UpdateUser | VARCHAR(50) | 更新人ID | ❌ | + +--- + +## 📊 字段统计 + +- **总字段数**:约 60+ 个字段 +- **必填字段**:约 50+ 个(包含奖金、补贴、支付相关字段) +- **可选字段**:约 10 个(主要是系统字段中的创建人、更新人等) + +--- + +## 🔑 索引建议 + +1. **主键索引**:`F_Id`(PRIMARY KEY) +2. **唯一索引**:`F_EmployeeId + F_StatisticsMonth`(确保同一员工同一月份只有一条记录) +3. **普通索引**: + - `F_StoreId`(按门店查询) + - `F_StatisticsMonth`(按月份查询) + - `F_EmployeeId`(按员工查询) + - `F_StoreCategory`(按门店分类查询) + +--- + +## 📝 字段说明 + +### 1. 业绩完成率计算 +``` +业绩完成率 = 门店业绩 / 门店生命线 × 100% +``` + +### 2. 提成比例判断 +``` +if (门店业绩 < 门店生命线 × 70%) + 提成比例 = 0% +else if (门店业绩 < 门店生命线 × 100%) + 提成比例 = 0.4% +else + 提成比例 = 0.6% +``` + +### 3. 阶段奖励计算 +``` +if (进店消耗人数 >= 第二阶段目标) + 阶段奖励 = 400元(第一阶段200 + 第二阶段200) +else if (进店消耗人数 >= 第一阶段目标) + 阶段奖励 = 200元(第一阶段200) +else + 阶段奖励 = 0元 +``` + +### 4. 应发工资计算 +``` +应发工资 = 底薪 + 提成金额 + 阶段奖励金额 + 手机管理费 +``` + +### 5. 实发工资计算 +``` +实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金 +``` + +--- + +## ⚠️ 注意事项 + +1. **数据校验**: + - 门店分类必须设置,不允许为NULL + - 门店生命线必须设置,未设置应报错 + - 阶段目标必须设置,未设置应报错 + +2. **数据一致性**: + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致 + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致 + +3. **字段命名规范**: + - 所有字段使用 `F_` 前缀 + - 金额字段使用 `DECIMAL(18,2)` 类型 + - 日期字段使用 `DATETIME` 类型 + - 月份字段使用 `VARCHAR(20)` 类型,格式为 YYYYMM + +4. **与健康师工资表的差异**: + - 店助工资表不需要:个人业绩、战队业绩、新客业绩、升单业绩等个人业绩相关字段 + - 店助工资表需要:门店业绩、门店生命线、进店消耗人数、阶段奖励等门店相关字段 + - 店助工资表不需要:顾问提成、门店T区提成等健康师特有字段 + - 店助工资表需要:手机管理费固定奖励字段 + +--- + +## 🔗 相关表关联 + +1. **BASE_USER**:通过 `F_EmployeeId` 关联员工信息 +2. **lq_mdxx**:通过 `F_StoreId` 关联门店信息,获取门店分类 +3. **lq_md_target**:通过 `F_StoreId + F_StatisticsMonth` 关联门店目标,获取门店生命线和阶段目标 +4. **lq_kd_kdjlb**:用于计算门店开单业绩 +5. **lq_hytk_hytk**:用于计算门店退卡业绩 +6. **lq_xh_hyhk**:用于统计进店消耗人数 +7. **lq_xh_jksyj**:用于判断是否有消费金额 + +--- + +## 📅 更新记录 + +- 2025-01-XX:初始版本,根据健康师工资表结构和店助工资计算规则梳理店助工资表字段 + diff --git a/店助工资计算规则梳理.md b/店助工资计算规则梳理.md new file mode 100644 index 0000000..3a0b36b --- /dev/null +++ b/店助工资计算规则梳理.md @@ -0,0 +1,383 @@ +# 店助工资计算规则梳理 + +## 📋 概述 + +店助工资由以下几个部分组成: +1. **底薪**:根据门店分类(A、B、C类)按规则计算 +2. **提成**:根据门店业绩与门店生命线的比例计算 +3. **阶段奖励**:根据进店消耗人数是否达到阶段目标 +4. **固定奖励**:手机管理费 + +--- + +# 第一部分:计算规则 + +## 💰 工资组成规则 + +### 1. 底薪规则 + +**计算规则**:根据门店分类(A、B、C类)确定底薪金额 + +| 门店分类 | 底薪金额 | +|---------|---------| +| A类门店 | 3000元 | +| B类门店 | 3100元 | +| C类门店 | 3200元 | + +**重要说明**: +- 门店分类必须设置,系统不允许设置为NULL +- 如果门店分类未设置,应在计算工资前进行校验并提示错误 + +--- + +### 2. 提成规则 + +**计算公式**:根据门店业绩与门店生命线的比例确定提成比例,然后按门店业绩的百分比计算提成 + +#### 提成比例规则 + +| 门店业绩范围 | 提成比例 | +|------------|---------| +| 门店业绩 < 门店生命线 × 70% | 0%(无提成) | +| 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% | 0.4% | +| 门店业绩 ≥ 门店生命线 × 100% | 0.6% | + +#### 提成计算示例 + +假设: +- 门店生命线 = 100,000元 +- 门店业绩 = 85,000元 + +计算过程: +1. 判断比例:85,000 / 100,000 = 85% +2. 判断区间:70% ≤ 85% < 100% +3. 提成比例:0.4% +4. 提成金额:85,000 × 0.4% = 340元 + +**重要说明**: +- 门店生命线必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示 +- 提成按门店业绩的百分比计算 + +--- + +### 3. 阶段奖励规则 + +**考核指标**:进店消耗人数(有消费金额的,按门店按月去重客户数) + +#### 奖励规则 + +| 阶段 | 目标人数 | 奖励金额 | 说明 | +|-----|---------|---------|------| +| 第一阶段 | 达到目标(如:100人) | 200元/月 | 只要达到第一阶段目标即可获得 | +| 第二阶段 | 达到目标(如:140人) | 再奖励200元/月 | 在达到第一阶段的基础上,再达到第二阶段目标 | +| 两个阶段都达到 | - | 总计400元/月 | 第一阶段200元 + 第二阶段200元 | + +**奖励说明**: +- 两个阶段的奖励是累加的,不是互斥的 +- 如果只达到第二阶段但未达到第一阶段,只获得第二阶段奖励(200元) +- 如果两个阶段都达到,获得总计400元奖励 + +**统计规则**: +- 按门店统计(`md` 字段) +- 只统计有消费金额的记录(消耗金额 > 0) +- 同一个会员按月去重(`COUNT(DISTINCT hy)`),其中 `hy` 为会员ID +- 只统计当月有效消耗记录(`F_IsEffective = 1`) + +**重要说明**: +- 阶段目标必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示 +- 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人) + +--- + +### 4. 固定奖励规则 + +**手机管理费**:150元/月 + +**说明**: +- 固定金额,无需计算 +- 每月固定发放 + +--- + +## 📊 完整计算公式 + +``` +店助月工资 = 底薪 + 提成 + 阶段奖励 + 固定奖励 + +其中: +- 底薪 = 根据门店分类确定(A类3000元,B类3100元,C类3200元) +- 提成 = 门店业绩 × 提成比例(根据业绩与生命线比例确定) +- 阶段奖励 = 根据进店消耗人数是否达到阶段目标(0/200/400元) +- 固定奖励 = 150元(手机管理费) +``` + +--- + +## ⚠️ 注意事项 + +1. **门店业绩计算**: + - 门店业绩 = 开单业绩 - 退卡业绩 + - 只统计当月有效记录(`F_IsEffective = 1`) + +2. **进店消耗人数统计**: + - 必须使用 `COUNT(DISTINCT hy)` 去重(同一个会员按月去重) + - 只统计有消费金额的记录(消耗金额 > 0) + - 按门店统计(`md` 字段) + - 只统计当月有效消耗记录(`F_IsEffective = 1`) + - 通过关联 `lq_xh_jksyj` 表判断是否有消费金额 + +3. **阶段目标配置**: + - 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人) + - 必须从 `lq_md_target` 表获取对应门店的目标值 + - **重要**:如果 `lq_md_target` 表中未设置阶段目标,系统应报错提示 + +4. **提成比例判断**: + - 严格按照门店业绩与门店生命线的比例判断 + - 注意边界值:70% 和 100% + - 提成按门店业绩的百分比计算 + +5. **数据一致性**: + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致 + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致 + +6. **数据校验要求**: + - 门店分类(`F_StoreCategory`)必须设置,不允许为NULL,未设置应报错 + - 门店生命线(`F_StoreLifeline`)必须设置,未设置应报错 + - 阶段目标(`F_AssistantHeadcountTargetStage1`、`F_AssistantHeadcountTargetStage2`)必须设置,未设置应报错 + +--- + +# 第二部分:数据来源说明 + +## 🔍 核心数据表 + +### 1. `lq_mdxx` - 门店信息表 + +**用途**:获取门店分类,用于确定店助底薪 + +**关键字段**: +- `F_Id`:门店ID(主键) +- `F_StoreCategory`:门店分类 + - `1` = A类门店 + - `2` = B类门店 + - `3` = C类门店 + +**查询条件**:门店ID(`F_Id`) + +**查询示例**: +```sql +SELECT F_StoreCategory FROM lq_mdxx WHERE F_Id = @StoreId +``` + +**代码实现**: +- 实体类:`LqMdxxEntity.StoreCategory`(对应数据库字段 `F_StoreCategory`) +- 枚举类:`StoreCategoryEnum`(定义A、B、C类门店) +- 在工资计算时,已从门店信息中获取 `StoreCategory` 字段(见 `LqSalaryService.cs` 第345行) + +--- + +### 2. `lq_md_target` - 门店目标表 + +**用途**:获取门店生命线和阶段目标 + +**关键字段**: +- `F_StoreId`:门店ID +- `F_Month`:月份(YYYYMM格式) +- `F_StoreLifeline`:门店生命线(必须设置,未设置则报错) +- `F_AssistantHeadcountTargetStage1`:店助第一阶段目标人数(必须设置,未设置则报错) +- `F_AssistantHeadcountTargetStage2`:店助第二阶段目标人数(必须设置,未设置则报错) + +**查询条件**:门店ID(`F_StoreId`)+ 月份(`F_Month`,YYYYMM格式) + +**查询示例**: +```sql +SELECT + F_StoreLifeline, + F_AssistantHeadcountTargetStage1, + F_AssistantHeadcountTargetStage2 +FROM lq_md_target +WHERE F_StoreId = @StoreId AND F_Month = @Month +``` + +**重要说明**:所有目标字段都必须设置,如果未设置,系统应报错提示 + +--- + +### 3. `lq_kd_kdjlb` - 开单记录表 + +**用途**:计算门店开单业绩 + +**关键字段**: +- `sfyj`:实付业绩(用于计算门店开单业绩总和) +- `Kdrq`:开单日期(用于按月过滤) +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 按门店、月份汇总 `sfyj` 字段 +- 只统计有效记录(`F_IsEffective = 1`) + +--- + +### 4. `lq_hytk_hytk` - 退卡记录表 + +**用途**:计算门店退卡业绩 + +**关键字段**: +- `F_ActualRefundAmount`:实际退卡金额(用于计算门店退卡业绩总和) +- `tksj`:退卡时间(用于按月过滤) +- `md`:门店ID +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 按门店、月份汇总 `F_ActualRefundAmount` 字段 +- 只统计有效记录(`F_IsEffective = 1`) + +--- + +### 5. `lq_xh_hyhk` - 耗卡记录表 + +**用途**:统计进店消耗人数 + +**关键字段**: +- `F_Id`:耗卡记录ID(主键) +- `hy`:会员ID(用于去重统计) +- `md`:门店ID(按门店统计) +- `hksj`:耗卡时间(用于按月过滤) +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 按门店、月份统计去重会员数 +- 只统计有效记录(`F_IsEffective = 1`) +- 只统计有消费金额的记录(通过关联 `lq_xh_jksyj` 表判断) + +--- + +### 6. `lq_xh_jksyj` - 耗卡健康师业绩表 + +**用途**:判断是否有消费金额 + +**关键字段**: +- `glkdbh`:关联耗卡记录ID(关联 `lq_xh_hyhk.F_Id`) +- `jksyj`:健康师业绩(消耗金额) +- `F_IsEffective`:是否有效(只统计有效记录) + +**查询逻辑**: +- 用于判断耗卡记录是否有消费金额(`jksyj > 0`) +- 只统计有效记录(`F_IsEffective = 1`) + +--- + +## 📝 数据查询逻辑 + +### 1. 门店业绩计算 + +**计算公式**:门店业绩 = 开单业绩 - 退卡业绩 + +**查询逻辑**: +```sql +-- 1. 计算门店开单业绩 +SELECT COALESCE(SUM(sfyj), 0) as BillingPerformance +FROM lq_kd_kdjlb +WHERE Djmd = @StoreId + AND DATE_FORMAT(Kdrq, '%Y%m') = @Month + AND F_IsEffective = 1 + +-- 2. 计算门店退卡业绩 +SELECT COALESCE(SUM(F_ActualRefundAmount), 0) as RefundPerformance +FROM lq_hytk_hytk +WHERE md = @StoreId + AND DATE_FORMAT(tksj, '%Y%m') = @Month + AND F_IsEffective = 1 + +-- 3. 计算门店实际业绩 +门店业绩 = 开单业绩 - 退卡业绩 +``` + +--- + +### 2. 进店消耗人数统计 + +**统计规则**:有消费金额的,按门店按月去重客户数 + +**查询逻辑**: +```sql +-- 统计门店当月进店消耗人数(有消费金额的,按门店按月去重客户数) +SELECT COUNT(DISTINCT hy) as HeadCount +FROM lq_xh_hyhk hk +WHERE hk.md = @StoreId + AND DATE_FORMAT(hk.hksj, '%Y%m') = @Month + AND hk.F_IsEffective = 1 + AND EXISTS ( + -- 确保有消费金额(通过关联消耗业绩表判断) + SELECT 1 + FROM lq_xh_jksyj jksyj + WHERE jksyj.glkdbh = hk.F_Id + AND jksyj.F_IsEffective = 1 + AND jksyj.jksyj > 0 + ) +``` + +**说明**: +- `md`:门店ID,按门店统计 +- `hy`:会员ID,用于去重统计(同一个会员按月去重) +- `hksj`:耗卡时间,用于按月过滤 +- `F_IsEffective = 1`:只统计有效记录 +- **重要**:只统计有消费金额的记录(消耗金额 > 0),通过关联 `lq_xh_jksyj` 表判断是否有消费金额 + +--- + +### 3. 门店分类获取 + +**查询逻辑**: +```sql +-- 获取门店分类 +SELECT F_StoreCategory FROM lq_mdxx WHERE F_Id = @StoreId + +-- 根据门店分类确定底薪 +-- F_StoreCategory = 1 → 底薪 = 3000元 +-- F_StoreCategory = 2 → 底薪 = 3100元 +-- F_StoreCategory = 3 → 底薪 = 3200元 +``` + +**代码实现**: +```csharp +// 从门店信息中获取 +var store = await _db.Queryable() + .Where(x => x.Id == storeId) + .FirstAsync(); + +int? storeCategory = store.StoreCategory; // 1=A类, 2=B类, 3=C类 + +// 根据门店分类确定店助底薪 +decimal baseSalary = storeCategory switch +{ + 1 => 3000m, // A类门店 + 2 => 3100m, // B类门店 + 3 => 3200m, // C类门店 + _ => throw new Exception($"门店分类未设置或无效:门店ID={storeId}") +}; +``` + +--- + +## 🔗 相关代码位置 + +- **门店信息查询**:`LqMdxxService.cs` +- **门店分类枚举**:`StoreCategoryEnum.cs`(`netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/`) +- **门店实体类**:`LqMdxxEntity.cs`(`F_StoreCategory` 字段) +- **工资计算服务**:`LqSalaryService.cs`(第345行获取 `StoreCategory`) +- **门店目标查询**:`LqMdTargetService.cs` +- **门店业绩统计**:`LqStatisticsService.cs` / `LqDailyReportService.cs` +- **进店消耗人数统计**:`LqStatisticsService.cs` / `LqDailyReportService.cs` + +--- + +## 📅 更新记录 + +- 2025-01-XX:初始版本,梳理店助工资计算规则 +- 2025-01-XX:更新底薪规则,明确按A、B、C类门店分类确定底薪(A类3000元,B类3100元,C类3200元) +- 2025-01-XX:明确A、B、C类门店来源:`lq_mdxx` 表的 `F_StoreCategory` 字段(枚举值:1=A类,2=B类,3=C类) +- 2025-01-XX:明确数据校验要求:门店分类必须设置,`lq_md_target` 表的目标字段必须设置,未设置应报错 +- 2025-01-XX:明确进店消耗人数统计规则:有消费金额的,按门店按月去重,同一个会员按月去重 +- 2025-01-XX:明确提成计算:按门店业绩的百分比计算 +- 2025-01-XX:重新组织文档结构,分为规则部分和数据来源说明部分 diff --git a/项目信息-薪酬规则与名词解释.md b/项目信息-薪酬规则与名词解释.md index c0b12e1..e7b69cf 100644 --- a/项目信息-薪酬规则与名词解释.md +++ b/项目信息-薪酬规则与名词解释.md @@ -68,7 +68,6 @@ ### 通用规则 -1. **单人业绩提成门槛**:单人业绩 ≤ 6000元,无提成 2. **金三角(战队)标准**:组员当月考勤天数 ≥ 20天,则算战队组成成功,可按战队提成计算 ### 健康师薪酬规则 @@ -129,6 +128,11 @@ ### 店助考核规则 +店助底薪规则 +A类门店:3000 元 +B类门店:3100 元 +C类门店:3200 元 + #### 考核阶段 每个门店均有两个阶段的考核(如:川师) - 第一阶段:100人(进店消耗人数) @@ -141,8 +145,8 @@ #### 提成计算 - 当月门店业绩 < 门店生命线×70% → 无提成 -- 门店生命线×70% ≤ 当月门店业绩 < 门店生命线×100% → 0.4% -- 当月门店业绩 > 门店生命线 → 0.6% +- 门店生命线×70% ≤ 当月门店业绩 ≤门店生命线×100% → 0.4% +- 当月门店业绩 > 门店生命线*100%→ 0.6% #### 固定奖励 - 手机管理费:150元 -- libgit2 0.21.4