diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentCrInput.cs new file mode 100644 index 0000000..50dbc83 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentCrInput.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; + +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment +{ + /// + /// 门店大项目部老师归属设置创建输入 + /// + public class LqMdMajorProjectTeacherAssignmentCrInput + { + /// + /// 门店ID + /// + [Required(ErrorMessage = "门店ID不能为空")] + public string storeId { get; set; } + + /// + /// 年份(YYYY格式) + /// + [Required(ErrorMessage = "年份不能为空")] + [StringLength(4, MinimumLength = 4, ErrorMessage = "年份格式必须为YYYY")] + public string year { get; set; } + + /// + /// 月份(MM格式) + /// + [Required(ErrorMessage = "月份不能为空")] + [StringLength(2, MinimumLength = 2, ErrorMessage = "月份格式必须为MM")] + public string month { get; set; } + + /// + /// 大项目部老师用户ID + /// + [Required(ErrorMessage = "大项目部老师用户ID不能为空")] + public string teacherId { get; set; } + + /// + /// 备注说明 + /// + public string remark { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentInfoOutput.cs new file mode 100644 index 0000000..394d4da --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentInfoOutput.cs @@ -0,0 +1,70 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment +{ + /// + /// 门店大项目部老师归属设置信息输出 + /// + public class LqMdMajorProjectTeacherAssignmentInfoOutput + { + /// + /// 主键ID + /// + public string id { get; set; } + + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 年份(YYYY格式) + /// + public string year { get; set; } + + /// + /// 月份(MM格式) + /// + public string month { get; set; } + + /// + /// 大项目部老师用户ID + /// + public string teacherId { get; set; } + + /// + /// 大项目部老师姓名 + /// + public string teacherName { get; set; } + + /// + /// 备注说明 + /// + public string remark { get; set; } + + /// + /// 创建时间 + /// + public DateTime? createTime { get; set; } + + /// + /// 创建人ID + /// + public string createUserId { get; set; } + + /// + /// 更新时间 + /// + public DateTime? updateTime { get; set; } + + /// + /// 更新人ID + /// + public string updateUserId { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListOutput.cs new file mode 100644 index 0000000..db6b5f9 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListOutput.cs @@ -0,0 +1,70 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment +{ + /// + /// 门店大项目部老师归属设置列表输出 + /// + public class LqMdMajorProjectTeacherAssignmentListOutput + { + /// + /// 主键ID + /// + public string id { get; set; } + + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 年份(YYYY格式) + /// + public string year { get; set; } + + /// + /// 月份(MM格式) + /// + public string month { get; set; } + + /// + /// 大项目部老师用户ID + /// + public string teacherId { get; set; } + + /// + /// 大项目部老师姓名 + /// + public string teacherName { get; set; } + + /// + /// 备注说明 + /// + public string remark { get; set; } + + /// + /// 创建时间 + /// + public DateTime? createTime { get; set; } + + /// + /// 创建人ID + /// + public string createUserId { get; set; } + + /// + /// 更新时间 + /// + public DateTime? updateTime { get; set; } + + /// + /// 更新人ID + /// + public string updateUserId { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListQueryInput.cs new file mode 100644 index 0000000..5182254 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListQueryInput.cs @@ -0,0 +1,30 @@ +using NCC.Common.Filter; + +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment +{ + /// + /// 门店大项目部老师归属设置列表查询输入 + /// + public class LqMdMajorProjectTeacherAssignmentListQueryInput : PageInputBase + { + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 年份(YYYY格式) + /// + public string year { get; set; } + + /// + /// 月份(MM格式) + /// + public string month { get; set; } + + /// + /// 大项目部老师用户ID + /// + public string teacherId { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentUpInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentUpInput.cs new file mode 100644 index 0000000..ece1829 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentUpInput.cs @@ -0,0 +1,47 @@ +using System.ComponentModel.DataAnnotations; + +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment +{ + /// + /// 门店大项目部老师归属设置更新输入 + /// + public class LqMdMajorProjectTeacherAssignmentUpInput + { + /// + /// 主键ID + /// + [Required(ErrorMessage = "主键ID不能为空")] + public string id { get; set; } + + /// + /// 门店ID + /// + [Required(ErrorMessage = "门店ID不能为空")] + public string storeId { get; set; } + + /// + /// 年份(YYYY格式) + /// + [Required(ErrorMessage = "年份不能为空")] + [StringLength(4, MinimumLength = 4, ErrorMessage = "年份格式必须为YYYY")] + public string year { get; set; } + + /// + /// 月份(MM格式) + /// + [Required(ErrorMessage = "月份不能为空")] + [StringLength(2, MinimumLength = 2, ErrorMessage = "月份格式必须为MM")] + public string month { get; set; } + + /// + /// 大项目部老师用户ID + /// + [Required(ErrorMessage = "大项目部老师用户ID不能为空")] + public string teacherId { get; set; } + + /// + /// 备注说明 + /// + public string remark { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherSalaryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherSalaryInput.cs new file mode 100644 index 0000000..24e8abe --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherSalaryInput.cs @@ -0,0 +1,32 @@ +using NCC.Common.Filter; +using System; + +namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary +{ + /// + /// 科技老师工资查询参数 + /// + public class TechTeacherSalaryInput : 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/LqTechTeacherSalary/TechTeacherSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherSalaryOutput.cs new file mode 100644 index 0000000..3e254cd --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherSalaryOutput.cs @@ -0,0 +1,321 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary +{ + /// + /// 科技老师工资输出 + /// + public class TechTeacherSalaryOutput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 统计月份 + /// + public string StatisticsMonth { get; set; } + + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 核算岗位 + /// + public string Position { get; set; } + + /// + /// 员工姓名 + /// + public string EmployeeName { get; set; } + + /// + /// 员工ID + /// + public string EmployeeId { get; set; } + + /// + /// 员工账号 + /// + public string EmployeeAccount { get; set; } + + /// + /// 开单业绩 + /// + public decimal OrderAchievement { get; set; } + + /// + /// 消耗业绩 + /// + public decimal ConsumeAchievement { get; set; } + + /// + /// 退卡业绩 + /// + public decimal RefundAchievement { get; set; } + + /// + /// 总业绩 + /// + public decimal TotalPerformance { get; set; } + + /// + /// 项目数 + /// + public decimal ProjectCount { get; set; } + + /// + /// 底薪档位 + /// + public int BaseSalaryLevel { get; set; } + + /// + /// 底薪 + /// + public decimal BaseSalary { get; set; } + + /// + /// 业绩提成比例 + /// + public decimal PerformanceCommissionRate { get; set; } + + /// + /// 业绩提成金额 + /// + public decimal PerformanceCommissionAmount { get; set; } + + /// + /// 消耗提成比例 + /// + public decimal ConsumeCommissionRate { get; set; } + + /// + /// 消耗提成金额 + /// + public decimal ConsumeCommissionAmount { get; set; } + + /// + /// 手工费 + /// + public decimal HandworkFee { get; set; } + + /// + /// 在店天数 + /// + public decimal WorkingDays { get; set; } + + /// + /// 请假天数 + /// + public decimal LeaveDays { get; set; } + + /// + /// 提成合计 + /// + public decimal TotalCommission { 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 TotalSubsidy { 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 TotalDeduction { get; set; } + + /// + /// 发奖金 + /// + public decimal Bonus { get; set; } + + /// + /// 退手机押金 + /// + public decimal ReturnPhoneDeposit { get; set; } + + /// + /// 退住宿押金 + /// + public decimal ReturnAccommodationDeposit { get; set; } + + /// + /// 实发工资 + /// + public decimal ActualSalary { 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 int IsTerminated { 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/Entity/lq_md_major_project_teacher_assignment/LqMdMajorProjectTeacherAssignmentEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_md_major_project_teacher_assignment/LqMdMajorProjectTeacherAssignmentEntity.cs new file mode 100644 index 0000000..4e76dbc --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_md_major_project_teacher_assignment/LqMdMajorProjectTeacherAssignmentEntity.cs @@ -0,0 +1,74 @@ +using NCC.Common.Const; +using SqlSugar; +using System; + +namespace NCC.Extend.Entitys.lq_md_major_project_teacher_assignment +{ + /// + /// 门店大项目部老师归属设置 + /// + [SugarTable("lq_md_major_project_teacher_assignment")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqMdMajorProjectTeacherAssignmentEntity + { + /// + /// 主键ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 门店ID + /// + [SugarColumn(ColumnName = "F_StoreId")] + public string StoreId { get; set; } + + /// + /// 年份(YYYY格式) + /// + [SugarColumn(ColumnName = "F_Year")] + public string Year { get; set; } + + /// + /// 月份(MM格式) + /// + [SugarColumn(ColumnName = "F_Month")] + public string Month { get; set; } + + /// + /// 大项目部老师用户ID + /// + [SugarColumn(ColumnName = "F_TeacherId")] + public string TeacherId { get; set; } + + /// + /// 备注说明 + /// + [SugarColumn(ColumnName = "F_Remark")] + public string Remark { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime? CreateTime { get; set; } + + /// + /// 创建人ID + /// + [SugarColumn(ColumnName = "F_CreateUserId")] + public string CreateUserId { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(ColumnName = "F_UpdateTime")] + public DateTime? UpdateTime { get; set; } + + /// + /// 更新人ID + /// + [SugarColumn(ColumnName = "F_UpdateUserId")] + public string UpdateUserId { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_tech_teacher_salary_statistics/LqTechTeacherSalaryStatisticsEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_tech_teacher_salary_statistics/LqTechTeacherSalaryStatisticsEntity.cs new file mode 100644 index 0000000..731192b --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_tech_teacher_salary_statistics/LqTechTeacherSalaryStatisticsEntity.cs @@ -0,0 +1,404 @@ +using System; +using NCC.Common.Const; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_tech_teacher_salary_statistics +{ + /// + /// 科技部老师工资统计表 + /// + [SugarTable("lq_tech_teacher_salary_statistics")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqTechTeacherSalaryStatisticsEntity + { + /// + /// 主键ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 统计月份(YYYYMM) + /// + [SugarColumn(ColumnName = "F_StatisticsMonth", Length = 6)] + public string StatisticsMonth { 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; } + + /// + /// 员工账号 + /// + [SugarColumn(ColumnName = "F_EmployeeAccount")] + public string EmployeeAccount { get; set; } + + /// + /// 开单业绩 + /// + [SugarColumn(ColumnName = "F_OrderAchievement", DecimalDigits = 2)] + public decimal OrderAchievement { get; set; } + + /// + /// 消耗业绩(对应规则中的"消耗") + /// + [SugarColumn(ColumnName = "F_ConsumeAchievement", DecimalDigits = 2)] + public decimal ConsumeAchievement { get; set; } + + /// + /// 退卡业绩 + /// + [SugarColumn(ColumnName = "F_RefundAchievement", DecimalDigits = 2)] + public decimal RefundAchievement { get; set; } + + /// + /// 总业绩(开单业绩 + 消耗业绩 + 退卡业绩) + /// + [SugarColumn(ColumnName = "F_TotalPerformance", DecimalDigits = 2)] + public decimal TotalPerformance { get; set; } + + /// + /// 项目数(用于底薪计算,主要来源于耗卡品相次数) + /// + [SugarColumn(ColumnName = "F_ProjectCount", DecimalDigits = 2)] + public decimal ProjectCount { get; set; } + + /// + /// 底薪档位(1=第一档2500, 2=第二档3000, 3=第三档3500) + /// + [SugarColumn(ColumnName = "F_BaseSalaryLevel")] + public int BaseSalaryLevel { get; set; } + + /// + /// 健康师底薪(根据项目数和业绩计算) + /// + [SugarColumn(ColumnName = "F_BaseSalary", DecimalDigits = 2)] + public decimal BaseSalary { get; set; } + + /// + /// 业绩提成比例(百分比,如2表示2%) + /// + [SugarColumn(ColumnName = "F_PerformanceCommissionRate", DecimalDigits = 2)] + public decimal PerformanceCommissionRate { get; set; } + + /// + /// 业绩提成金额 + /// + [SugarColumn(ColumnName = "F_PerformanceCommissionAmount", DecimalDigits = 2)] + public decimal PerformanceCommissionAmount { get; set; } + + /// + /// 消耗提成比例(百分比,如0.5表示0.5%,负数表示扣除,如-300表示扣除300元) + /// + [SugarColumn(ColumnName = "F_ConsumeCommissionRate", DecimalDigits = 2)] + public decimal ConsumeCommissionRate { get; set; } + + /// + /// 消耗提成金额(可能为负数,如扣除300元) + /// + [SugarColumn(ColumnName = "F_ConsumeCommissionAmount", DecimalDigits = 2)] + public decimal ConsumeCommissionAmount { get; set; } + + /// + /// 手工费 + /// + [SugarColumn(ColumnName = "F_HandworkFee", DecimalDigits = 2)] + public decimal HandworkFee { get; set; } + + /// + /// 在店天数 + /// + [SugarColumn(ColumnName = "F_WorkingDays", DecimalDigits = 2)] + public decimal WorkingDays { get; set; } + + /// + /// 请假天数 + /// + [SugarColumn(ColumnName = "F_LeaveDays", DecimalDigits = 2)] + public decimal LeaveDays { get; set; } + + /// + /// 提成合计(业绩提成 + 消耗提成) + /// + [SugarColumn(ColumnName = "F_TotalCommission", DecimalDigits = 2)] + public decimal TotalCommission { get; set; } + + /// + /// 车补 + /// + [SugarColumn(ColumnName = "F_TransportationAllowance", DecimalDigits = 2)] + public decimal TransportationAllowance { get; set; } + + /// + /// 少休费 + /// + [SugarColumn(ColumnName = "F_LessRest", DecimalDigits = 2)] + public decimal LessRest { get; set; } + + /// + /// 全勤奖 + /// + [SugarColumn(ColumnName = "F_FullAttendance", DecimalDigits = 2)] + public decimal FullAttendance { get; set; } + + /// + /// 核算应发工资 + /// + [SugarColumn(ColumnName = "F_CalculatedGrossSalary", DecimalDigits = 2)] + public decimal CalculatedGrossSalary { get; set; } + + /// + /// 保底工资 + /// + [SugarColumn(ColumnName = "F_GuaranteedSalary", DecimalDigits = 2)] + public decimal GuaranteedSalary { get; set; } + + /// + /// 保底请假扣款 + /// + [SugarColumn(ColumnName = "F_GuaranteedLeaveDeduction", DecimalDigits = 2)] + public decimal GuaranteedLeaveDeduction { get; set; } + + /// + /// 保底底薪 + /// + [SugarColumn(ColumnName = "F_GuaranteedBaseSalary", DecimalDigits = 2)] + public decimal GuaranteedBaseSalary { get; set; } + + /// + /// 保底补差 + /// + [SugarColumn(ColumnName = "F_GuaranteedSupplement", DecimalDigits = 2)] + public decimal GuaranteedSupplement { get; set; } + + /// + /// 最终应发工资 + /// + [SugarColumn(ColumnName = "F_FinalGrossSalary", DecimalDigits = 2)] + public decimal FinalGrossSalary { get; set; } + + /// + /// 当月培训补贴 + /// + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy", DecimalDigits = 2)] + public decimal MonthlyTrainingSubsidy { get; set; } + + /// + /// 当月交通补贴 + /// + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy", DecimalDigits = 2)] + public decimal MonthlyTransportSubsidy { get; set; } + + /// + /// 上月培训补贴 + /// + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy", DecimalDigits = 2)] + public decimal LastMonthTrainingSubsidy { get; set; } + + /// + /// 上月交通补贴 + /// + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy", DecimalDigits = 2)] + public decimal LastMonthTransportSubsidy { get; set; } + + /// + /// 补贴合计 + /// + [SugarColumn(ColumnName = "F_TotalSubsidy", DecimalDigits = 2)] + public decimal TotalSubsidy { get; set; } + + /// + /// 缺卡扣款 + /// + [SugarColumn(ColumnName = "F_MissingCard", DecimalDigits = 2)] + public decimal MissingCard { get; set; } + + /// + /// 迟到扣款 + /// + [SugarColumn(ColumnName = "F_LateArrival", DecimalDigits = 2)] + public decimal LateArrival { get; set; } + + /// + /// 请假扣款 + /// + [SugarColumn(ColumnName = "F_LeaveDeduction", DecimalDigits = 2)] + public decimal LeaveDeduction { get; set; } + + /// + /// 扣社保 + /// + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction", DecimalDigits = 2)] + public decimal SocialInsuranceDeduction { get; set; } + + /// + /// 扣除奖励 + /// + [SugarColumn(ColumnName = "F_RewardDeduction", DecimalDigits = 2)] + public decimal RewardDeduction { get; set; } + + /// + /// 扣住宿费 + /// + [SugarColumn(ColumnName = "F_AccommodationDeduction", DecimalDigits = 2)] + public decimal AccommodationDeduction { get; set; } + + /// + /// 扣学习期费用 + /// + [SugarColumn(ColumnName = "F_StudyPeriodDeduction", DecimalDigits = 2)] + public decimal StudyPeriodDeduction { get; set; } + + /// + /// 扣工作服费用 + /// + [SugarColumn(ColumnName = "F_WorkClothesDeduction", DecimalDigits = 2)] + public decimal WorkClothesDeduction { get; set; } + + /// + /// 扣款合计 + /// + [SugarColumn(ColumnName = "F_TotalDeduction", DecimalDigits = 2)] + public decimal TotalDeduction { get; set; } + + /// + /// 发奖金 + /// + [SugarColumn(ColumnName = "F_Bonus", DecimalDigits = 2)] + public decimal Bonus { get; set; } + + /// + /// 退手机押金 + /// + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit", DecimalDigits = 2)] + public decimal ReturnPhoneDeposit { get; set; } + + /// + /// 退住宿押金 + /// + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit", DecimalDigits = 2)] + public decimal ReturnAccommodationDeposit { get; set; } + + /// + /// 实发工资 + /// + [SugarColumn(ColumnName = "F_ActualSalary", DecimalDigits = 2)] + public decimal ActualSalary { get; set; } + + /// + /// 当月是否发放 + /// + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")] + public string MonthlyPaymentStatus { get; set; } + + /// + /// 支付金额 + /// + [SugarColumn(ColumnName = "F_PaidAmount", DecimalDigits = 2)] + public decimal PaidAmount { get; set; } + + /// + /// 待支付金额 + /// + [SugarColumn(ColumnName = "F_PendingAmount", DecimalDigits = 2)] + public decimal PendingAmount { get; set; } + + /// + /// 补发上月 + /// + [SugarColumn(ColumnName = "F_LastMonthSupplement", DecimalDigits = 2)] + public decimal LastMonthSupplement { get; set; } + + /// + /// 当月支付总额 + /// + [SugarColumn(ColumnName = "F_MonthlyTotalPayment", DecimalDigits = 2)] + public decimal MonthlyTotalPayment { get; set; } + + /// + /// 是否锁定(0未锁定,1已锁定) + /// + [SugarColumn(ColumnName = "F_IsLocked")] + public int IsLocked { get; set; } + + /// + /// 是否离职(0=在职,1=离职) + /// + [SugarColumn(ColumnName = "F_IsTerminated")] + public int IsTerminated { 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; } + + /// + /// 是否新店 + /// + [SugarColumn(ColumnName = "F_IsNewStore")] + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")] + public int NewStoreProtectionStage { get; set; } + + /// + /// 门店类型 + /// + [SugarColumn(ColumnName = "F_StoreType")] + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + [SugarColumn(ColumnName = "F_StoreCategory")] + public int? StoreCategory { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqMdMajorProjectTeacherAssignment/ILqMdMajorProjectTeacherAssignmentService.cs b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqMdMajorProjectTeacherAssignment/ILqMdMajorProjectTeacherAssignmentService.cs new file mode 100644 index 0000000..03d3bf7 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqMdMajorProjectTeacherAssignment/ILqMdMajorProjectTeacherAssignmentService.cs @@ -0,0 +1,19 @@ +using NCC.Common.Filter; +using NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment; +using System.Threading.Tasks; + +namespace NCC.Extend.Interfaces.LqMdMajorProjectTeacherAssignment +{ + /// + /// 门店大项目部老师归属设置服务接口 + /// + public interface ILqMdMajorProjectTeacherAssignmentService + { + /// + /// 复制上月设置 + /// + /// 目标月份(YYYYMM格式),如果为空则复制到当前月份 + /// + Task CopyLastMonthData(string targetMonth = null); + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqMdMajorProjectTeacherAssignmentService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqMdMajorProjectTeacherAssignmentService.cs new file mode 100644 index 0000000..ef86b5f --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqMdMajorProjectTeacherAssignmentService.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using NCC.Common.Core.Manager; +using NCC.Common.Enum; +using NCC.Common.Extension; +using NCC.Common.Filter; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment; +using NCC.Extend.Entitys.lq_md_major_project_teacher_assignment; +using NCC.Extend.Entitys.lq_mdxx; +using NCC.Extend.Interfaces.LqMdMajorProjectTeacherAssignment; +using NCC.FriendlyException; +using NCC.System.Entitys.Permission; +using SqlSugar; +using Yitter.IdGenerator; + +namespace NCC.Extend +{ + /// + /// 门店大项目部老师归属设置服务 + /// + [ApiDescriptionSettings(Tag = "绿纤门店大项目部老师归属设置服务", Name = "LqMdMajorProjectTeacherAssignment", Order = 201)] + [Route("api/Extend/[controller]")] + public class LqMdMajorProjectTeacherAssignmentService : ILqMdMajorProjectTeacherAssignmentService, IDynamicApiController, ITransient + { + private readonly ISqlSugarRepository _repository; + private readonly SqlSugarScope _db; + private readonly IUserManager _userManager; + + /// + /// 初始化一个类型的新实例 + /// + public LqMdMajorProjectTeacherAssignmentService(ISqlSugarRepository repository, IUserManager userManager) + { + _repository = repository; + _db = _repository.Context; + _userManager = userManager; + } + + #region 获取门店大项目部老师归属设置信息 + /// + /// 获取门店大项目部老师归属设置信息 + /// + /// 主键ID + /// + [HttpGet("{id}")] + public async Task GetInfo(string id) + { + var entity = await _db.Queryable() + .Where(p => p.Id == id) + .Select(it => new LqMdMajorProjectTeacherAssignmentInfoOutput + { + id = it.Id, + storeId = it.StoreId, + storeName = SqlFunc.Subqueryable().Where(x => x.Id == it.StoreId).Select(x => x.Dm), + year = it.Year, + month = it.Month, + teacherId = it.TeacherId, + teacherName = SqlFunc.Subqueryable().Where(x => x.Id == it.TeacherId).Select(x => x.RealName), + remark = it.Remark, + createTime = it.CreateTime, + createUserId = it.CreateUserId, + updateTime = it.UpdateTime, + updateUserId = it.UpdateUserId, + }) + .FirstAsync(); + + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + return entity; + } + #endregion + + #region 获取门店大项目部老师归属设置列表 + /// + /// 获取门店大项目部老师归属设置列表 + /// + /// 请求参数 + /// + [HttpGet("")] + public async Task GetList([FromQuery] LqMdMajorProjectTeacherAssignmentListQueryInput input) + { + var sidx = input.sidx == null ? "id" : input.sidx; + var data = await _db.Queryable() + .WhereIF(!string.IsNullOrEmpty(input.storeId), p => p.StoreId == input.storeId) + .WhereIF(!string.IsNullOrEmpty(input.year), p => p.Year == input.year) + .WhereIF(!string.IsNullOrEmpty(input.month), p => p.Month == input.month) + .WhereIF(!string.IsNullOrEmpty(input.teacherId), p => p.TeacherId == input.teacherId) + .Select(it => new LqMdMajorProjectTeacherAssignmentListOutput + { + id = it.Id, + storeId = it.StoreId, + storeName = SqlFunc.Subqueryable().Where(x => x.Id == it.StoreId).Select(x => x.Dm), + year = it.Year, + month = it.Month, + teacherId = it.TeacherId, + teacherName = SqlFunc.Subqueryable().Where(x => x.Id == it.TeacherId).Select(x => x.RealName), + remark = it.Remark, + createTime = it.CreateTime, + createUserId = it.CreateUserId, + updateTime = it.UpdateTime, + updateUserId = it.UpdateUserId, + }) + .MergeTable() + .OrderBy(sidx + " " + input.sort) + .ToPagedListAsync(input.currentPage, input.pageSize); + + return PageResult.SqlSugarPageResult(data); + } + #endregion + + #region 新建门店大项目部老师归属设置 + /// + /// 新建门店大项目部老师归属设置 + /// + /// 参数 + /// + [HttpPost("")] + public async Task Create([FromBody] LqMdMajorProjectTeacherAssignmentCrInput input) + { + var userInfo = await _userManager.GetUserInfo(); + + // 验证年份格式 + if (input.year.Length != 4 || !Regex.IsMatch(input.year, @"^\d{4}$")) + { + throw NCCException.Oh(ErrorCode.COM1000, "年份格式必须为YYYY(如:2025)"); + } + + // 验证月份格式 + if (input.month.Length != 2 || !Regex.IsMatch(input.month, @"^\d{2}$")) + { + throw NCCException.Oh(ErrorCode.COM1000, "月份格式必须为MM(如:01表示1月)"); + } + + // 验证月份范围(01-12) + if (!int.TryParse(input.month, out int monthInt) || monthInt < 1 || monthInt > 12) + { + throw NCCException.Oh(ErrorCode.COM1000, "月份必须在01-12之间"); + } + + // 验证门店ID、年份、月份、老师ID的唯一性 + var exists = await _db.Queryable() + .Where(p => p.StoreId == input.storeId && p.Year == input.year && p.Month == input.month && p.TeacherId == input.teacherId) + .AnyAsync(); + if (exists) + { + throw NCCException.Oh(ErrorCode.COM1000, "该门店在该年份该月份该老师已存在归属设置记录"); + } + + var entity = input.Adapt(); + entity.Id = YitIdHelper.NextId().ToString(); + entity.CreateTime = DateTime.Now; + entity.CreateUserId = userInfo.userId; + + var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); + if (!(isOk > 0)) + throw NCCException.Oh(ErrorCode.COM1000); + } + #endregion + + #region 更新门店大项目部老师归属设置 + /// + /// 更新门店大项目部老师归属设置 + /// + /// 主键ID + /// 更新参数 + /// + [HttpPut("{id}")] + public async Task Update([FromRoute] string id, [FromBody] LqMdMajorProjectTeacherAssignmentUpInput input) + { + var userInfo = await _userManager.GetUserInfo(); + + // 验证年份格式 + if (input.year.Length != 4 || !Regex.IsMatch(input.year, @"^\d{4}$")) + { + throw NCCException.Oh(ErrorCode.COM1000, "年份格式必须为YYYY(如:2025)"); + } + + // 验证月份格式 + if (input.month.Length != 2 || !Regex.IsMatch(input.month, @"^\d{2}$")) + { + throw NCCException.Oh(ErrorCode.COM1000, "月份格式必须为MM(如:01表示1月)"); + } + + // 验证月份范围(01-12) + if (!int.TryParse(input.month, out int monthInt) || monthInt < 1 || monthInt > 12) + { + throw NCCException.Oh(ErrorCode.COM1000, "月份必须在01-12之间"); + } + + var entity = await _db.Queryable().FirstAsync(p => p.Id == id); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + + // 如果门店、年份、月份、老师ID有变化,需要验证唯一性 + if (entity.StoreId != input.storeId || entity.Year != input.year || entity.Month != input.month || entity.TeacherId != input.teacherId) + { + var exists = await _db.Queryable() + .Where(p => p.Id != id && p.StoreId == input.storeId && p.Year == input.year && p.Month == input.month && p.TeacherId == input.teacherId) + .AnyAsync(); + if (exists) + { + throw NCCException.Oh(ErrorCode.COM1000, "该门店在该年份该月份该老师已存在归属设置记录"); + } + } + + entity.StoreId = input.storeId; + entity.Year = input.year; + entity.Month = input.month; + entity.TeacherId = input.teacherId; + entity.Remark = input.remark; + entity.UpdateTime = DateTime.Now; + entity.UpdateUserId = userInfo.userId; + + var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); + if (!(isOk > 0)) + throw NCCException.Oh(ErrorCode.COM1000); + } + #endregion + + #region 删除门店大项目部老师归属设置 + /// + /// 删除门店大项目部老师归属设置 + /// + /// 主键ID + /// + [HttpDelete("{id}")] + public async Task Delete(string id) + { + var entity = await _db.Queryable().FirstAsync(p => p.Id == id); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + + var isOk = await _db.Deleteable().Where(p => p.Id == id).ExecuteCommandAsync(); + if (!(isOk > 0)) + throw NCCException.Oh(ErrorCode.COM1000); + } + #endregion + + #region 复制上月设置 + /// + /// 从上个月复制设置数据到目标月份 + /// + /// + /// 从上个月复制门店大项目部老师归属设置数据到目标月份 + /// + /// 示例请求: + /// POST /api/Extend/LqMdMajorProjectTeacherAssignment/CopyLastMonthData?targetYear=2025&targetMonth=02 + /// + /// 业务逻辑: + /// 1. 确定目标年份和月份(如果未指定,使用当前年月) + /// 2. 计算上个月份 + /// 3. 查询上个月的所有设置数据 + /// 4. 查询目标月份已存在的记录 + /// 5. 过滤掉已存在的记录,只复制新记录 + /// 6. 批量插入新记录 + /// + /// 目标年份(YYYY格式),如果为空则使用当前年份 + /// 目标月份(MM格式),如果为空则使用当前月份 + /// 复制结果 + /// 成功返回复制结果 + /// 参数错误 + /// 服务器内部错误 + [HttpPost("CopyLastMonthData")] + public async Task CopyLastMonthData([FromQuery] string targetYear = null, [FromQuery] string targetMonth = null) + { + // 确定目标年份和月份 + DateTime targetDate; + if (string.IsNullOrEmpty(targetYear) || string.IsNullOrEmpty(targetMonth)) + { + targetDate = DateTime.Now; + } + else + { + if (targetYear.Length != 4 || !Regex.IsMatch(targetYear, @"^\d{4}$")) + { + throw NCCException.Oh(ErrorCode.COM1000, "目标年份格式必须为YYYY(如:2025)"); + } + if (targetMonth.Length != 2 || !Regex.IsMatch(targetMonth, @"^\d{2}$")) + { + throw NCCException.Oh(ErrorCode.COM1000, "目标月份格式必须为MM(如:01表示1月)"); + } + if (!int.TryParse(targetMonth, out int monthInt) || monthInt < 1 || monthInt > 12) + { + throw NCCException.Oh(ErrorCode.COM1000, "目标月份必须在01-12之间"); + } + targetDate = DateTime.ParseExact($"{targetYear}{targetMonth}", "yyyyMM", null); + } + + // 计算上个月份 + var lastMonthDate = targetDate.AddMonths(-1); + var lastYear = lastMonthDate.ToString("yyyy"); + var lastMonth = lastMonthDate.ToString("MM"); + var targetYearStr = targetDate.ToString("yyyy"); + var targetMonthStr = targetDate.ToString("MM"); + + // 查询上个月的所有设置数据 + var lastMonthData = await _db.Queryable() + .Where(p => p.Year == lastYear && p.Month == lastMonth) + .ToListAsync(); + + if (lastMonthData == null || lastMonthData.Count == 0) + { + return new { message = $"上个月({lastYear}年{lastMonth}月)没有设置数据,无法复制" }; + } + + // 查询目标月份已存在的记录 + var existingTargetMonthData = await _db.Queryable() + .Where(p => p.Year == targetYearStr && p.Month == targetMonthStr) + .Select(p => new { p.StoreId, p.TeacherId }) + .ToListAsync(); + + var existingKeys = existingTargetMonthData + .Select(x => $"{x.StoreId}_{x.TeacherId}") + .ToHashSet(); + + // 过滤掉已存在的记录,只复制新记录 + var newData = lastMonthData + .Where(x => !existingKeys.Contains($"{x.StoreId}_{x.TeacherId}")) + .Select(x => new LqMdMajorProjectTeacherAssignmentEntity + { + Id = YitIdHelper.NextId().ToString(), + StoreId = x.StoreId, + Year = targetYearStr, + Month = targetMonthStr, + TeacherId = x.TeacherId, + Remark = x.Remark, + CreateTime = DateTime.Now, + CreateUserId = _userManager.UserId, + UpdateTime = null, + UpdateUserId = null + }) + .ToList(); + + if (newData.Count == 0) + { + return new { message = $"目标月份({targetYearStr}年{targetMonthStr}月)已存在所有设置,无需复制" }; + } + + // 批量插入新记录 + var insertCount = await _db.Insertable(newData).ExecuteCommandAsync(); + + return new { message = $"成功从{lastYear}年{lastMonth}月复制{insertCount}条记录到{targetYearStr}年{targetMonthStr}月" }; + } + #endregion + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs new file mode 100644 index 0000000..84c73d4 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs @@ -0,0 +1,514 @@ +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.LqTechTeacherSalary; +using NCC.Extend.Entitys.lq_hytk_kjbsyj; +using NCC.Extend.Entitys.lq_kd_kjbsyj; +using NCC.Extend.Entitys.lq_md_xdbhsj; +using NCC.Extend.Entitys.lq_mdxx; +using NCC.Extend.Entitys.lq_attendance_summary; +using NCC.Extend.Entitys.lq_tech_teacher_salary_statistics; +using NCC.Extend.Entitys.lq_xh_hyhk; +using NCC.Extend.Entitys.lq_xh_kjbsyj; +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 = "LqTechTeacherSalary", Order = 302)] + [Route("api/Extend/[controller]")] + public class LqTechTeacherSalaryService : IDynamicApiController, ITransient + { + private readonly ISqlSugarClient _db; + + /// + /// 初始化一个类型的新实例 + /// + public LqTechTeacherSalaryService(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 获取科技老师工资列表 + /// + /// 查询参数 + /// 科技老师工资分页列表 + [HttpGet("tech-teacher")] + public async Task GetTechTeacherSalaryList([FromQuery] TechTeacherSalaryInput input) + { + var monthStr = $"{input.Year}{input.Month:D2}"; + + // 1. 检查当月是否已生成工资数据 + var exists = await _db.Queryable() + .AnyAsync(x => x.StatisticsMonth == monthStr); + + // 2. 如果没有数据,则进行计算 + if (!exists) + { + await CalculateTechTeacherSalary(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.EmployeeAccount.Contains(input.Keyword)); + } + + var list = await query.Select(x => new TechTeacherSalaryOutput + { + Id = x.Id, + StatisticsMonth = x.StatisticsMonth, + StoreId = x.StoreId, + StoreName = x.StoreName, + Position = x.Position, + EmployeeName = x.EmployeeName, + EmployeeId = x.EmployeeId, + EmployeeAccount = x.EmployeeAccount, + OrderAchievement = x.OrderAchievement, + ConsumeAchievement = x.ConsumeAchievement, + RefundAchievement = x.RefundAchievement, + TotalPerformance = x.TotalPerformance, + ProjectCount = x.ProjectCount, + BaseSalaryLevel = x.BaseSalaryLevel, + BaseSalary = x.BaseSalary, + PerformanceCommissionRate = x.PerformanceCommissionRate, + PerformanceCommissionAmount = x.PerformanceCommissionAmount, + ConsumeCommissionRate = x.ConsumeCommissionRate, + ConsumeCommissionAmount = x.ConsumeCommissionAmount, + HandworkFee = x.HandworkFee, + WorkingDays = x.WorkingDays, + LeaveDays = x.LeaveDays, + TotalCommission = x.TotalCommission, + 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, + IsLocked = x.IsLocked, + IsTerminated = x.IsTerminated, + 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/tech-teacher")] + public async Task CalculateTechTeacherSalary(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 techTeacherUserList = await _db.Queryable() + .Where(x => x.Gw == "科技老师" && x.DeleteMark == null && x.EnabledMark == 1) + .Select(x => new { x.Id, x.RealName, x.Account, x.Mdid, x.IsOnJob }) + .ToListAsync(); + + if (!techTeacherUserList.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_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.4 开单业绩数据 (lq_kd_kjbsyj) + var orderPerformanceList = await _db.Queryable() + .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1) && x.IsEffective == 1) + .ToListAsync(); + + // 1.5 消耗业绩和项目数数据 (lq_xh_kjbsyj,关联lq_xh_hyhk获取时间) + var consumePerformanceList = await _db.Queryable( + (kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id && hyhk.IsEffective == 1) + .Where((kjbsyj, hyhk) => kjbsyj.IsEffective == 1 + && hyhk.Hksj >= startDate && hyhk.Hksj <= endDate.AddDays(1)) + .Select((kjbsyj, hyhk) => new + { + kjbsyj.Kjblszh, + kjbsyj.Kjblsyj, + kjbsyj.HdpxNumber, + kjbsyj.LaborCost, + hyhk.Md + }) + .ToListAsync(); + + // 1.6 退卡业绩数据 (lq_hytk_kjbsyj) + var refundPerformanceList = await _db.Queryable() + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) + .ToListAsync(); + + // 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 techTeacherStats = new Dictionary(); + + foreach (var techTeacherUser in techTeacherUserList) + { + var teacherId = techTeacherUser.Id; + var isTerminated = techTeacherUser.IsOnJob == 0; + + // 2.1 创建工资统计对象 + var salary = new LqTechTeacherSalaryStatisticsEntity + { + Id = YitIdHelper.NextId().ToString(), + StatisticsMonth = monthStr, + EmployeeId = teacherId, + EmployeeName = techTeacherUser.RealName, + EmployeeAccount = techTeacherUser.Account ?? "", + Position = "科技部老师", + IsTerminated = isTerminated ? 1 : 0, + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now, + IsLocked = 0 + }; + + // 2.2 填充门店信息 + var storeId = techTeacherUser.Mdid; + if (!string.IsNullOrEmpty(storeId) && storeDict.ContainsKey(storeId)) + { + var store = storeDict[storeId]; + salary.StoreId = storeId; + salary.StoreName = store.Dm ?? ""; + salary.StoreType = store.StoreType; + salary.StoreCategory = store.StoreCategory; + } + else + { + // 如果用户没有门店,尝试从业绩数据中获取 + var firstOrderStore = orderPerformanceList.FirstOrDefault(x => x.Kjbls == teacherId || x.Kjblszh == teacherId); + var firstConsumeStore = consumePerformanceList.FirstOrDefault(x => x.Kjblszh == teacherId); + var firstRefundStore = refundPerformanceList.FirstOrDefault(x => x.Kjbls == teacherId || x.Kjblszh == teacherId); + + if (firstOrderStore != null && !string.IsNullOrEmpty(firstOrderStore.StoreId)) + { + storeId = firstOrderStore.StoreId; + } + else if (firstConsumeStore != null && !string.IsNullOrEmpty(firstConsumeStore.Md)) + { + storeId = firstConsumeStore.Md; + } + else if (firstRefundStore != null && !string.IsNullOrEmpty(firstRefundStore.StoreId)) + { + storeId = firstRefundStore.StoreId; + } + + if (!string.IsNullOrEmpty(storeId) && storeDict.ContainsKey(storeId)) + { + var store = storeDict[storeId]; + salary.StoreId = storeId; + salary.StoreName = store.Dm ?? ""; + salary.StoreType = store.StoreType; + salary.StoreCategory = store.StoreCategory; + } + } + + // 2.3 新店保护信息 + if (!string.IsNullOrEmpty(salary.StoreId) && newStoreProtectionDict.ContainsKey(salary.StoreId)) + { + var protection = newStoreProtectionDict[salary.StoreId]; + salary.IsNewStore = "是"; + salary.NewStoreProtectionStage = protection.Stage; + } + else + { + salary.IsNewStore = "否"; + salary.NewStoreProtectionStage = 0; + } + + // 2.4 统计业绩数据 + // 开单业绩(注意:kjblsyj字段是string类型,需要转换) + var orderPerformance = orderPerformanceList + .Where(x => (x.Kjbls == teacherId || x.Kjblszh == teacherId) && !string.IsNullOrEmpty(x.Kjblsyj)) + .Sum(x => decimal.TryParse(x.Kjblsyj, out var val) ? val : 0m); + salary.OrderAchievement = orderPerformance; + + // 消耗业绩和项目数 + var consumeData = consumePerformanceList + .Where(x => x.Kjblszh == teacherId) + .ToList(); + salary.ConsumeAchievement = consumeData.Sum(x => x.Kjblsyj ?? 0m); + salary.ProjectCount = consumeData.Sum(x => x.HdpxNumber ?? 0m); + salary.HandworkFee = consumeData.Sum(x => x.LaborCost ?? 0m); + + // 退卡业绩 + var refundPerformance = refundPerformanceList + .Where(x => (x.Kjbls == teacherId || x.Kjblszh == teacherId)) + .Sum(x => x.Kjblsyj ?? 0m); + salary.RefundAchievement = refundPerformance; + + // 总业绩 + salary.TotalPerformance = salary.OrderAchievement + salary.ConsumeAchievement + salary.RefundAchievement; + + // 2.5 考勤数据 + var attendance = attendanceDict.ContainsKey(teacherId) ? attendanceDict[teacherId] : null; + salary.WorkingDays = attendance?.WorkDays ?? 0; + salary.LeaveDays = attendance?.LeaveDays ?? 0; + + // 3. 工资计算 + if (isTerminated) + { + // 离职员工特殊处理 + if (salary.TotalPerformance > 30000m) + { + // 总业绩 > 30,000元:只计算2%提成 + salary.BaseSalary = 0; + salary.BaseSalaryLevel = 0; + salary.PerformanceCommissionRate = 2m; + salary.PerformanceCommissionAmount = salary.TotalPerformance * 0.02m; + salary.ConsumeCommissionRate = 0; + salary.ConsumeCommissionAmount = 0; + salary.TotalCommission = salary.PerformanceCommissionAmount; + } + else + { + // 总业绩 ≤ 30,000元:无任何工资 + salary.BaseSalary = 0; + salary.BaseSalaryLevel = 0; + salary.PerformanceCommissionRate = 0; + salary.PerformanceCommissionAmount = 0; + salary.ConsumeCommissionRate = 0; + salary.ConsumeCommissionAmount = 0; + salary.TotalCommission = 0; + } + } + else + { + // 在职员工正常计算 + + // 3.1 计算底薪(根据项目数和总业绩) + var baseSalaryResult = CalculateBaseSalary(salary.ProjectCount, salary.TotalPerformance); + salary.BaseSalary = baseSalaryResult.BaseSalary; + salary.BaseSalaryLevel = baseSalaryResult.Level; + + // 3.2 计算业绩提成(分段累进) + var performanceCommissionResult = CalculatePerformanceCommission(salary.TotalPerformance); + salary.PerformanceCommissionRate = performanceCommissionResult.Rate; + salary.PerformanceCommissionAmount = performanceCommissionResult.Amount; + + // 3.3 计算消耗提成(分段累进,可能为负数) + var consumeCommissionResult = CalculateConsumeCommission(salary.ConsumeAchievement); + salary.ConsumeCommissionRate = consumeCommissionResult.Rate; + salary.ConsumeCommissionAmount = consumeCommissionResult.Amount; + + // 3.4 提成合计 + salary.TotalCommission = salary.PerformanceCommissionAmount + salary.ConsumeCommissionAmount; + } + + // 3.5 初始化其他字段(默认值为0) + salary.TransportationAllowance = 0; + salary.LessRest = 0; + salary.FullAttendance = 0; + salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission + salary.HandworkFee; + salary.GuaranteedSalary = 0; + salary.GuaranteedLeaveDeduction = 0; + salary.GuaranteedBaseSalary = 0; + salary.GuaranteedSupplement = 0; + salary.FinalGrossSalary = salary.CalculatedGrossSalary; + salary.MonthlyTrainingSubsidy = 0; + salary.MonthlyTransportSubsidy = 0; + salary.LastMonthTrainingSubsidy = 0; + salary.LastMonthTransportSubsidy = 0; + salary.TotalSubsidy = 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.Bonus = 0; + salary.ReturnPhoneDeposit = 0; + salary.ReturnAccommodationDeposit = 0; + salary.ActualSalary = salary.FinalGrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; + salary.MonthlyPaymentStatus = ""; + salary.PaidAmount = 0; + salary.PendingAmount = salary.ActualSalary; + salary.LastMonthSupplement = 0; + salary.MonthlyTotalPayment = 0; + + techTeacherStats[teacherId] = salary; + } + + // 4. 保存数据 + if (techTeacherStats.Any()) + { + // 先删除当月旧数据 (防止重复) + await _db.Deleteable() + .Where(x => x.StatisticsMonth == monthStr) + .ExecuteCommandAsync(); + + await _db.Insertable(techTeacherStats.Values.ToList()).ExecuteCommandAsync(); + } + } + + /// + /// 计算底薪(根据项目数和总业绩) + /// + /// 项目数 + /// 总业绩 + /// 底薪金额和档位 + private (decimal BaseSalary, int Level) CalculateBaseSalary(decimal projectCount, decimal totalPerformance) + { + // 从高到低判断档位,同时满足项目数和总业绩两个条件 + // 第三档:≥ 110个 且 ≥ 100,000元 → 3,500元 + if (projectCount >= 110m && totalPerformance >= 100000m) + { + return (3500m, 3); + } + // 第二档:≥ 95个 且 ≥ 80,000元 → 3,000元 + else if (projectCount >= 95m && totalPerformance >= 80000m) + { + return (3000m, 2); + } + // 第一档:≥ 80个 且 ≥ 40,000元 → 2,500元 + else if (projectCount >= 80m && totalPerformance >= 40000m) + { + return (2500m, 1); + } + // 都不满足:默认第一档 2,500元 + else + { + return (2500m, 1); + } + } + + /// + /// 计算业绩提成(分段累进) + /// + /// 总业绩 + /// 提成比例和金额 + private (decimal Rate, decimal Amount) CalculatePerformanceCommission(decimal totalPerformance) + { + if (totalPerformance < 10000m) + { + // < 10,000元 → 0% + return (0m, 0m); + } + else if (totalPerformance < 70000m) + { + // 10,000-70,000元 → 2% + return (2m, totalPerformance * 0.02m); + } + else if (totalPerformance < 150000m) + { + // 70,000-150,000元 → 2.5% + return (2.5m, totalPerformance * 0.025m); + } + else + { + // > 150,000元 → 3% + return (3m, totalPerformance * 0.03m); + } + } + + /// + /// 计算消耗提成(分段累进,可能为负数) + /// + /// 消耗业绩 + /// 提成比例和金额(金额可能为负数,比例用于显示) + private (decimal Rate, decimal Amount) CalculateConsumeCommission(decimal consumeAchievement) + { + if (consumeAchievement < 80000m) + { + // < 80,000元 → 扣除300元(负数) + // 比例显示为0,金额为-300 + return (0m, -300m); + } + else if (consumeAchievement < 100000m) + { + // 80,000-100,000元 → 0.5% + return (0.5m, consumeAchievement * 0.005m); + } + else if (consumeAchievement < 200000m) + { + // 100,000-200,000元 → 0.5% + return (0.5m, consumeAchievement * 0.005m); + } + else + { + // > 200,000元 → 1% + return (1m, consumeAchievement * 0.01m); + } + } + } +} + diff --git a/sql/创建大项目部老师归属设置表.sql b/sql/创建大项目部老师归属设置表.sql new file mode 100644 index 0000000..1743d03 --- /dev/null +++ b/sql/创建大项目部老师归属设置表.sql @@ -0,0 +1,74 @@ +-- ============================================ +-- 创建大项目部老师归属设置表 +-- 功能:存储各门店每月的大项目部老师归属设置 +-- 创建时间:2025年 +-- ============================================ + +-- 删除表(如果存在) +DROP TABLE IF EXISTS lq_md_major_project_teacher_assignment; + +-- ============================================ +-- 创建大项目部老师归属设置表 +-- ============================================ +CREATE TABLE lq_md_major_project_teacher_assignment ( + -- 主键 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', + -- 基本信息 + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID', + F_Year VARCHAR(4) NOT NULL COMMENT '年份(YYYY格式)', + F_Month VARCHAR(2) NOT NULL COMMENT '月份(MM格式)', + F_TeacherId VARCHAR(50) NOT NULL COMMENT '大项目部老师用户ID', + -- 备注说明 + F_Remark TEXT NULL COMMENT '备注说明', + -- 审计字段 + F_CreateTime DATETIME NULL COMMENT '创建时间', + F_CreateUserId VARCHAR(50) NULL COMMENT '创建人ID', + F_UpdateTime DATETIME NULL COMMENT '更新时间', + F_UpdateUserId VARCHAR(50) NULL COMMENT '更新人ID', + -- 主键约束 + PRIMARY KEY (F_Id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='门店大项目部老师归属设置表'; + +-- ============================================ +-- 创建索引 +-- ============================================ + +-- 门店ID + 年份 + 月份 + 老师ID唯一索引(同一门店同一年份同一月份同一老师只能有一条记录) +CREATE UNIQUE INDEX idx_store_year_month_teacher ON lq_md_major_project_teacher_assignment(F_StoreId, F_Year, F_Month, F_TeacherId); + +-- 门店ID索引(查询某个门店的所有设置) +CREATE INDEX idx_store_id ON lq_md_major_project_teacher_assignment(F_StoreId); + +-- 年份索引(查询某年的所有设置) +CREATE INDEX idx_year ON lq_md_major_project_teacher_assignment(F_Year); +-- 月份索引(查询某个月份的所有设置) +CREATE INDEX idx_month ON lq_md_major_project_teacher_assignment(F_Month); +-- 年份+月份组合索引(查询某年某月的所有设置) +CREATE INDEX idx_year_month ON lq_md_major_project_teacher_assignment(F_Year, F_Month); + +-- 老师ID索引(查询某个老师的所有门店设置) +CREATE INDEX idx_teacher_id ON lq_md_major_project_teacher_assignment(F_TeacherId); + +-- ============================================ +-- 表结构说明 +-- ============================================ +/* +业务规则: +1. 同一门店同一年份同一月份同一老师只能有一条归属设置记录 +2. 年份格式为YYYY(如:2025表示2025年) +3. 月份格式为MM(如:01表示1月) +4. 一个门店在一个月份可以有多个老师(通过多条记录实现) +5. 一个门店在一个月份也可以没有老师(不创建记录即可) +6. 老师ID关联BASE_USER.F_Id +7. 门店ID关联lq_mdxx.F_Id + +字段说明: +- F_Id: 主键,使用YitIdHelper生成 +- F_StoreId: 门店ID,关联lq_mdxx表 +- F_Year: 年份,格式YYYY(如:2025) +- F_Month: 月份,格式MM(如:01表示1月) +- F_TeacherId: 大项目部老师用户ID,关联BASE_USER表 +- F_Remark: 备注说明(可选) +- F_CreateTime/F_CreateUserId: 创建时间和创建人 +- F_UpdateTime/F_UpdateUserId: 更新时间和更新人 +*/ diff --git a/sql/创建科技部老师工资统计表.sql b/sql/创建科技部老师工资统计表.sql new file mode 100644 index 0000000..f16a7ea --- /dev/null +++ b/sql/创建科技部老师工资统计表.sql @@ -0,0 +1,121 @@ +-- ============================================ +-- 创建科技部老师工资统计表 +-- 功能:存储科技部老师每月的工资计算数据,包括底薪、业绩提成、消耗提成、考核扣款、扣款、补贴、奖金、支付等信息 +-- 创建时间:2025年 +-- ============================================ + +-- 删除表(如果存在) +DROP TABLE IF EXISTS lq_tech_teacher_salary_statistics; + +-- ============================================ +-- 创建科技部老师工资统计表 +-- ============================================ +CREATE TABLE lq_tech_teacher_salary_statistics ( + -- 主键 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', + + -- 一、基础信息字段 + F_StatisticsMonth VARCHAR(6) NOT NULL COMMENT '统计月份(YYYYMM格式)', + 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_EmployeeAccount VARCHAR(100) NOT NULL COMMENT '员工账号', + F_StoreType INT NULL COMMENT '门店类型(200平/旗舰店)', + F_StoreCategory INT NULL COMMENT '门店分类(1=A类,2=B类,3=C类)', + F_IsNewStore VARCHAR(10) NULL COMMENT '是否新店(是/否)', + F_NewStoreProtectionStage INT NOT NULL DEFAULT 0 COMMENT '新店保护阶段(0/1/2)', + + -- 二、业绩相关字段 + F_OrderAchievement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '开单业绩(从lq_kd_kjbsyj表统计)', + F_ConsumeAchievement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '消耗业绩(对应规则中的"消耗",从lq_xh_kjbsyj表统计)', + F_RefundAchievement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退卡业绩(从lq_hytk_kjbsyj表统计)', + F_TotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '总业绩(开单业绩 + 消耗业绩 + 退卡业绩,用于底薪和业绩提成计算)', + F_ProjectCount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '项目数(用于底薪计算,主要来源于耗卡品相次数F_hdpxNumber)', + + -- 三、底薪相关字段 + F_BaseSalaryLevel INT NOT NULL DEFAULT 0 COMMENT '底薪档位(1=第一档2500, 2=第二档3000, 3=第三档3500)', + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '底薪金额(根据项目数和业绩计算,2500/3000/3500)', + + -- 四、业绩提成相关字段 + F_PerformanceCommissionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩提成比例(百分比,如2.00表示2%)', + F_PerformanceCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '业绩提成金额(总业绩 × 业绩提成比例)', + + -- 五、消耗提成相关字段 + F_ConsumeCommissionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '消耗提成比例(百分比,如0.50表示0.5%,负数表示扣除,如-300表示扣除300元)', + F_ConsumeCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '消耗提成金额(可能为负数,如扣除300元)', + F_TotalCommission DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成合计(业绩提成 + 消耗提成)', + + -- 六、其他收入字段 + F_HandworkFee DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '手工费', + F_TransportationAllowance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '车补', + F_LessRest DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '少休费', + F_FullAttendance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '全勤奖', + + -- 七、考勤相关字段 + F_WorkingDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '在店天数', + F_LeaveDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假天数', + + -- 八、工资计算字段 + F_CalculatedGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '核算应发工资(底薪 + 提成合计 + 其他收入)', + F_GuaranteedSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底工资', + F_GuaranteedLeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底请假扣款', + F_GuaranteedBaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底底薪', + F_GuaranteedSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底补差', + F_FinalGrossSalary 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_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_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_ActualSalary 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_IsTerminated 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_employee_account` (F_EmployeeAccount), + KEY `idx_store_category` (F_StoreCategory), + KEY `idx_is_terminated` (F_IsTerminated), + KEY `idx_create_time` (F_CreateTime) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='科技部老师工资统计表'; diff --git a/报销退回后重新走流程-接口调用示例.md b/报销退回后重新走流程-接口调用示例.md new file mode 100644 index 0000000..a9f4959 --- /dev/null +++ b/报销退回后重新走流程-接口调用示例.md @@ -0,0 +1,371 @@ +# 报销退回后修改并重新走流程 - 接口调用示例 + +## 前置条件 + +- 已创建报销申请并提交审批 +- 申请已被退回(状态为"已退回") +- 申请ID:`767672243453953285`(示例) +- Token:`Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`(需要替换为实际token) + +--- + +## 完整流程 + +### 步骤1:退回申请(如果还未退回) + +**接口地址:** `POST /api/Extend/LqReimbursementApplication/{id}/Actions/Approve` + +**请求参数:** +- `result`: 审批结果,值为 `退回`(需要URL编码为 `%E9%80%80%E5%9B%9E`) +- `opinion`: 退回原因 + +**cURL 示例:** +```bash +curl -X POST "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285/Actions/Approve?result=%E9%80%80%E5%9B%9E&opinion=%E9%9C%80%E8%A6%81%E4%BF%AE%E6%94%B9%E9%87%91%E9%A2%9D" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \ + -H "Content-Type: application/json" +``` + +**返回示例:** +```json +{ + "code": 200, + "msg": "操作成功", + "data": null +} +``` + +--- + +### 步骤2:查看退回后的状态 + +**接口地址:** `GET /api/Extend/LqReimbursementApplication/{id}` + +**cURL 示例:** +```bash +curl -X GET "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \ + -H "Content-Type: application/json" +``` + +**返回示例:** +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "form": { + "id": "767672243453953285", + "applicationUserId": "admin", + "applicationUserName": "管理员", + "applicationStoreId": "1649328471923847168", + "amount": "200.00", + ... + }, + "approvalStatus": "已退回", + "currentNodeOrder": 0, + "returnedReason": "需要修改金额", + "nodes": [ + { + "nodeOrder": 1, + "nodeName": "部门经理审批", + "approvalRecords": [ + { + "approverName": "管理员", + "approvalResult": "退回", + "approvalOpinion": "需要修改金额", + "approvalTime": 1765192675000 + } + ] + } + ] + } +} +``` + +--- + +### 步骤3:修改报销单 + +**接口地址:** `PUT /api/Extend/LqReimbursementApplication/{id}` + +**请求体参数:** +```json +{ + "id": "767672243453953285", + "applicationUserId": "admin", + "applicationUserName": "管理员", + "applicationStoreId": "1649328471923847168", + "applicationTime": 1765123200000, + "amount": "300.00", + "selectedPurchaseRecordIds": [], + "purchaseRecordsId": null +} +``` + +**参数说明:** +- `id`: 申请编号(必填) +- `applicationUserId`: 申请人编号(可选,不修改可不传) +- `applicationUserName`: 申请人姓名(可选,不修改可不传) +- `applicationStoreId`: 申请门店ID(可选,不修改可不传) +- `applicationTime`: 申请时间(时间戳,可选) +- `amount`: 总金额(可选,要修改的字段) +- `selectedPurchaseRecordIds`: 选中的购买记录ID列表(可选,数组) +- `purchaseRecordsId`: 关联购买编号(可选,字符串,多个用逗号分隔) + +**cURL 示例:** +```bash +curl -X PUT "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \ + -H "Content-Type: application/json" \ + -d '{ + "id": "767672243453953285", + "amount": "300.00" + }' +``` + +**返回示例:** +```json +{ + "code": 200, + "msg": "操作成功", + "data": null +} +``` + +**注意:** +- 只有"待审批"(CurrentNodeOrder=0)或"已退回"状态的申请才能修改 +- 如果状态为"审批中"、"已通过"、"未通过",修改会被拒绝 + +--- + +### 步骤4:重新提交审批 + +**接口地址:** `POST /api/Extend/LqReimbursementApplication/{id}/Actions/SubmitApproval` + +**cURL 示例:** +```bash +curl -X POST "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285/Actions/SubmitApproval" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \ + -H "Content-Type: application/json" +``` + +**返回示例:** +```json +{ + "code": 200, + "msg": "操作成功", + "data": null +} +``` + +**说明:** +- 重新提交审批后,会创建新的"待审批"记录 +- 之前的"退回"记录会被保留,不会删除 +- 申请状态会从"已退回"变为"审批中" + +--- + +### 步骤5:审批通过 + +**接口地址:** `POST /api/Extend/LqReimbursementApplication/{id}/Actions/Approve` + +**请求参数:** +- `result`: 审批结果,值为 `通过`(需要URL编码为 `%E9%80%9A%E8%BF%87`) +- `opinion`: 审批意见(可选) + +**cURL 示例:** +```bash +curl -X POST "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285/Actions/Approve?result=%E9%80%9A%E8%BF%87&opinion=%E4%BF%AE%E6%94%B9%E5%90%8E%E5%AE%A1%E6%89%B9%E9%80%9A%E8%BF%87" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \ + -H "Content-Type: application/json" +``` + +**返回示例:** +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "approvalStatus": "已通过" + } +} +``` + +--- + +### 步骤6:查看最终状态和审批记录 + +**接口地址:** `GET /api/Extend/LqReimbursementApplication/{id}` + +**cURL 示例:** +```bash +curl -X GET "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \ + -H "Content-Type: application/json" +``` + +**返回示例:** +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "form": { + "id": "767672243453953285", + "amount": "300.00", + ... + }, + "approvalStatus": "已通过", + "currentNodeOrder": 2, + "nodes": [ + { + "nodeOrder": 1, + "nodeName": "部门经理审批", + "approvalRecords": [ + { + "approverName": "管理员", + "approvalResult": "退回", + "approvalOpinion": "需要修改金额", + "approvalTime": 1765192675000 + }, + { + "approverName": "管理员", + "approvalResult": "通过", + "approvalOpinion": "修改后审批通过", + "approvalTime": 1765192762000 + } + ] + } + ] + } +} +``` + +**说明:** +- 审批记录中包含完整的审批历史 +- "退回"记录和"通过"记录都会保留 +- 可以清楚地看到退回原因和重新审批的结果 + +--- + +## 完整流程总结 + +1. **退回申请** → 状态变为"已退回",当前节点为0 +2. **修改报销单** → 修改需要调整的字段(金额、购买记录等) +3. **重新提交审批** → 状态变为"审批中",创建新的"待审批"记录 +4. **审批通过** → 状态变为"已通过",审批记录中包含退回和通过两条记录 + +--- + +## 注意事项 + +1. **URL编码**:中文字符需要URL编码 + - `退回` → `%E9%80%80%E5%9B%9E` + - `通过` → `%E9%80%9A%E8%BF%87` + +2. **状态检查**: + - 只有"待审批"或"已退回"状态的申请才能修改 + - 只有"审批中"状态的申请才能进行审批操作 + +3. **审批记录**: + - 退回记录会被保留,不会删除 + - 重新提交审批后会创建新的"待审批"记录 + - 审批记录按时间顺序显示,包含完整的审批历史 + +4. **Token**:所有接口都需要在Header中携带有效的Authorization Token + +--- + +## JavaScript/Axios 示例 + +```javascript +const axios = require('axios'); +const BASE_URL = 'http://localhost:2011'; +const TOKEN = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; +const APPLICATION_ID = '767672243453953285'; + +// 1. 退回申请 +async function returnApplication() { + const response = await axios.post( + `${BASE_URL}/api/Extend/LqReimbursementApplication/${APPLICATION_ID}/Actions/Approve`, + null, + { + headers: { Authorization: TOKEN }, + params: { + result: '退回', + opinion: '需要修改金额' + } + } + ); + return response.data; +} + +// 2. 修改报销单 +async function updateApplication() { + const response = await axios.put( + `${BASE_URL}/api/Extend/LqReimbursementApplication/${APPLICATION_ID}`, + { + id: APPLICATION_ID, + amount: '300.00' + }, + { + headers: { Authorization: TOKEN } + } + ); + return response.data; +} + +// 3. 重新提交审批 +async function resubmitApproval() { + const response = await axios.post( + `${BASE_URL}/api/Extend/LqReimbursementApplication/${APPLICATION_ID}/Actions/SubmitApproval`, + null, + { + headers: { Authorization: TOKEN } + } + ); + return response.data; +} + +// 4. 审批通过 +async function approveApplication() { + const response = await axios.post( + `${BASE_URL}/api/Extend/LqReimbursementApplication/${APPLICATION_ID}/Actions/Approve`, + null, + { + headers: { Authorization: TOKEN }, + params: { + result: '通过', + opinion: '修改后审批通过' + } + } + ); + return response.data; +} + +// 完整流程 +async function completeFlow() { + try { + // 1. 退回 + await returnApplication(); + console.log('✅ 申请已退回'); + + // 2. 修改 + await updateApplication(); + console.log('✅ 申请已修改'); + + // 3. 重新提交 + await resubmitApproval(); + console.log('✅ 已重新提交审批'); + + // 4. 审批通过 + await approveApplication(); + console.log('✅ 审批已通过'); + + } catch (error) { + console.error('❌ 流程执行失败:', error.response?.data || error.message); + } +} +``` + diff --git a/科技部老师工资计算规则.md b/科技部老师工资计算规则.md new file mode 100644 index 0000000..133bfbb --- /dev/null +++ b/科技部老师工资计算规则.md @@ -0,0 +1,308 @@ +# 科技部老师工资计算规则 + +## 📋 目录 +- [计算规则](#计算规则) +- [数据来源](#数据来源) +- [特殊规则](#特殊规则) +- [计算流程](#计算流程) + +--- + +## 💰 计算规则 + +### 1. 底薪规则(分三档) + +底薪根据**项目数**和**总业绩**两个条件同时满足来确定档位: + +| 档位 | 项目数要求 | 总业绩要求 | 底薪金额 | +|------|-----------|-----------|---------| +| **第一档** | ≥ 80个 | ≥ 40,000元 | 2,500元 | +| **第二档** | ≥ 95个 | ≥ 80,000元 | 3,000元 | +| **第三档** | ≥ 110个 | ≥ 100,000元 | 3,500元 | +| **最低档** | 不满足第一档条件 | - | 2,500元(默认第一档) | + +**计算逻辑**: +- 从高到低判断,同时满足项目数和总业绩两个条件才能获得对应档位底薪 +- 如果都不满足,默认使用第一档(2,500元) + +--- + +### 2. 业绩提成规则 + +业绩提成基于**总业绩**计算,采用分段累进方式: + +| 总业绩范围 | 提成比例 | +|-----------|---------| +| < 10,000元 | 0% (无提成) | +| 10,000元 - 70,000元 | 2% | +| 70,000元 - 150,000元 | 2.5% | +| > 150,000元 | 3% | + +**计算说明**: +- 提成金额 = 总业绩 × 对应提成比例 +- 采用分段计算,不同区间按不同比例计算 + +--- + +### 3. 消耗提成规则 + +消耗提成基于**消耗业绩**计算,包含扣除机制: + +| 消耗业绩范围 | 提成规则 | +|------------|---------| +| < 80,000元 | **扣除300元**(负提成) | +| 80,000元 - 100,000元 | 0.5% 提成 | +| 100,000元 - 200,000元 | 0.5% 提成 | +| > 200,000元 | 1% 提成 | + +**重要说明**: +- 当消耗业绩 < 80,000元时,不是无提成,而是**直接扣除300元工资** +- 消耗提成金额可能为负数(扣除情况) + +--- + +## 📊 数据来源 + +### 总业绩(TotalPerformance) + +**定义**:开单业绩 + 消耗业绩 + 退卡业绩 + +**数据来源表及字段**: + +| 业绩类型 | 数据表 | 字段 | 说明 | +|---------|--------|------|------| +| **开单业绩** | `lq_kd_kjbsyj` | `kjblsyj` | 科技部老师开单业绩 | +| **消耗业绩** | `lq_xh_kjbsyj` | `kjblsyj` | 科技部老师消耗业绩 | +| **退卡业绩** | `lq_hytk_kjbsyj` | `kjblsyj` | 科技部老师退卡业绩 | + +**计算方式**: +```sql +总业绩 = SUM(开单业绩) + SUM(消耗业绩) + SUM(退卡业绩) +``` + +**过滤条件**: +- 所有表记录必须满足:`F_IsEffective = 1`(有效记录) +- 按统计月份(YYYYMM格式)过滤时间范围 +- 按科技老师ID(`kjblszh` 或 `kjbls`)关联 + +--- + +### 消耗(ConsumeAchievement) + +**定义**:科技部老师在耗卡记录中的业绩总和 + +**数据来源**: +- **表名**:`lq_xh_kjbsyj`(耗卡科技部老师业绩表) +- **字段**:`kjblsyj`(科技部老师业绩) +- **关联表**:`lq_xh_hyhk`(耗卡主表,用于时间过滤) + +**计算方式**: +```sql +消耗 = SUM(lq_xh_kjbsyj.kjblsyj) +WHERE lq_xh_kjbsyj.F_IsEffective = 1 + AND lq_xh_hyhk.F_IsEffective = 1 + AND DATE_FORMAT(lq_xh_hyhk.hksj, '%Y%m') = @统计月份 + AND lq_xh_kjbsyj.kjblszh = @科技老师账号 +``` + +**对应实体字段**: +- `LqTechTeacherSalaryStatisticsEntity.F_ConsumeAchievement` + +--- + +### 项目数(ProjectCount) + +**定义**:科技部老师在耗卡记录中的项目次数总和 + +**数据来源**: +- **表名**:`lq_xh_kjbsyj`(耗卡科技部老师业绩表) +- **字段**:`F_hdpxNumber`(耗卡品项次数) +- **关联表**:`lq_xh_hyhk`(耗卡主表,用于时间过滤) + +**计算方式**: +```sql +项目数 = SUM(lq_xh_kjbsyj.F_hdpxNumber) +WHERE lq_xh_kjbsyj.F_IsEffective = 1 + AND lq_xh_hyhk.F_IsEffective = 1 + AND DATE_FORMAT(lq_xh_hyhk.hksj, '%Y%m') = @统计月份 + AND lq_xh_kjbsyj.kjblszh = @科技老师账号 +``` + +**对应实体字段**: +- `LqTechTeacherSalaryStatisticsEntity.F_ProjectCount` + +--- + +## ⚠️ 特殊规则 + +### 离职员工规则 + +**适用条件**: +- 员工状态:`BASE_USER.F_IsOnJob = 0`(离职) + +**计算规则**: +- 如果离职员工当月**总业绩 > 30,000元**: + - **提成**:总业绩 × 2% + - **无底薪**:不计算底薪 + - **无奖励**:不计算任何奖励 + - **无消耗提成**:不计算消耗提成 +- 如果离职员工当月总业绩 ≤ 30,000元: + - 无任何工资 + +**记录标识**: +- 在 `LqTechTeacherSalaryStatisticsEntity.F_IsTerminated` 字段中记录: + - `0` = 在职 + - `1` = 离职 + +**重要说明**: +- 离职员工规则优先级最高,一旦判定为离职且业绩>3万,只按2%提成计算,其他规则不适用 + +--- + +## 🔄 计算流程 + +### 步骤1:识别科技部老师 +- 从 `BASE_USER` 表中筛选:`Gw == "科技老师"` 且 `DeleteMark == null` 且 `EnabledMark == 1` + +### 步骤2:判断是否离职 +- 检查 `BASE_USER.F_IsOnJob`: + - `F_IsOnJob == 0` → 离职员工 + - `F_IsOnJob == 1` → 在职员工 + +### 步骤3:数据汇总(在职员工) +- 查询开单业绩:从 `lq_kd_kjbsyj` 表汇总 `kjblsyj` +- 查询消耗业绩:从 `lq_xh_kjbsyj` 表汇总 `kjblsyj` +- 查询退卡业绩:从 `lq_hytk_kjbsyj` 表汇总 `kjblsyj` +- 查询项目数:从 `lq_xh_kjbsyj` 表汇总 `F_hdpxNumber` +- 计算总业绩:开单业绩 + 消耗业绩 + 退卡业绩 + +### 步骤4:工资计算(在职员工) + +#### 4.1 计算底薪 +- 根据项目数和总业绩,从高到低判断档位: + - 同时满足第三档条件 → 底薪 3,500元 + - 同时满足第二档条件 → 底薪 3,000元 + - 同时满足第一档条件 → 底薪 2,500元 + - 都不满足 → 默认第一档 2,500元 + +#### 4.2 计算业绩提成 +- 根据总业绩范围确定提成比例: + - < 10,000元 → 0% + - 10,000-70,000元 → 2% + - 70,000-150,000元 → 2.5% + - > 150,000元 → 3% +- 业绩提成金额 = 总业绩 × 提成比例 + +#### 4.3 计算消耗提成 +- 根据消耗业绩范围确定提成规则: + - < 80,000元 → 扣除300元(负数) + - 80,000-100,000元 → 0.5% + - 100,000-200,000元 → 0.5% + - > 200,000元 → 1% +- 消耗提成金额 = 消耗业绩 × 提成比例(或直接扣除300元) + +### 步骤5:工资计算(离职员工) +- 如果总业绩 > 30,000元: + - 底薪 = 0 + - 业绩提成 = 总业绩 × 2% + - 消耗提成 = 0 + - 奖励 = 0 +- 如果总业绩 ≤ 30,000元: + - 所有工资项 = 0 + +### 步骤6:保存结果 +- 将计算结果保存到 `lq_tech_teacher_salary_statistics` 表 +- 记录 `F_IsTerminated` 字段标识是否离职 + +--- + +## 📝 数据表结构 + +### 主表:`lq_tech_teacher_salary_statistics` + +**关键字段说明**: + +| 字段名 | 说明 | 数据来源 | +|--------|------|---------| +| `F_StatisticsMonth` | 统计月份(YYYYMM) | 计算参数 | +| `F_EmployeeId` | 员工ID | `BASE_USER.F_Id` | +| `F_EmployeeAccount` | 员工账号 | `BASE_USER.F_Account` | +| `F_EmployeeName` | 员工姓名 | `BASE_USER.F_RealName` | +| `F_StoreId` | 门店ID | `lq_mdxx.F_Id` | +| `F_StoreName` | 门店名称 | `lq_mdxx.dm` | +| `F_TotalPerformance` | 总业绩 | 计算得出 | +| `F_ConsumeAchievement` | 消耗业绩 | `lq_xh_kjbsyj.kjblsyj` | +| `F_ProjectCount` | 项目数 | `lq_xh_kjbsyj.F_hdpxNumber` | +| `F_BaseSalary` | 底薪 | 根据规则计算 | +| `F_PerformanceCommissionAmount` | 业绩提成金额 | 根据规则计算 | +| `F_ConsumeCommissionAmount` | 消耗提成金额 | 根据规则计算(可能为负数) | +| `F_IsTerminated` | 是否离职(0=在职,1=离职) | `BASE_USER.F_IsOnJob` | + +--- + +## 🔍 查询示例 + +### 查询科技部老师消耗数据(参考 `LqStatisticsService.GetTechTeacherStatistics`) + +```sql +-- 消耗业绩和项目数查询 +SELECT + kjbsyj.kjblszh as F_UserId, + kjbsyj.kjblsxm as F_UserName, + hyhk.md as F_StoreId, + md.dm as F_StoreName, + @statisticsMonth as F_StatisticsMonth, + COALESCE(SUM(kjbsyj.kjblsyj), 0) as F_ConsumePerformance, -- 消耗业绩 + COALESCE(SUM(kjbsyj.F_hdpxNumber), 0) as F_ConsumeQuantity, -- 项目数 + COALESCE(SUM(kjbsyj.F_LaborCost), 0) as F_ManualFee +FROM lq_xh_kjbsyj kjbsyj +INNER JOIN lq_xh_hyhk hyhk ON kjbsyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1 +LEFT JOIN lq_mdxx md ON hyhk.md = md.F_Id +WHERE kjbsyj.F_IsEffective = 1 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @statisticsMonth +GROUP BY kjbsyj.kjblszh, kjbsyj.kjblsxm, hyhk.md, md.mdbm, md.dm +``` + +--- + +## ✅ 验证要点 + +1. **数据准确性**: + - 确保所有业绩数据来自有效记录(`F_IsEffective = 1`) + - 确保时间范围正确(按月份YYYYMM格式) + +2. **计算逻辑**: + - 底薪档位判断必须同时满足项目数和总业绩两个条件 + - 消耗提成 < 80,000元时是扣除300元,不是0 + - 离职员工规则优先级最高 + +3. **数据一致性**: + - 总业绩 = 开单业绩 + 消耗业绩 + 退卡业绩 + - 消耗和项目数必须来自同一数据源(`lq_xh_kjbsyj`) + +--- + +## 📌 注意事项 + +1. **离职员工处理**: + - 必须先判断 `F_IsOnJob`,离职员工走特殊逻辑 + - 离职员工不计算底薪和消耗提成 + +2. **消耗提成扣除**: + - 消耗 < 80,000元时,`F_ConsumeCommissionAmount` 字段值为 `-300`(负数) + +3. **数据汇总**: + - 同一科技部老师可能在不同门店有数据,需要按门店分别计算 + - 或者按科技部老师汇总所有门店数据(根据业务需求) + +4. **月份格式**: + - 统计月份统一使用 `YYYYMM` 格式(如:202501) + +--- + +## 🔗 相关文件 + +- **实体类**:`LqTechTeacherSalaryStatisticsEntity.cs` +- **薪酬规则文档**:`项目信息-薪酬规则与名词解释.md` +- **参考服务**:`LqSalaryService.cs`(健康师工资计算)、`LqAssistantSalaryService.cs`(店助工资计算) +- **统计数据服务**:`LqStatisticsService.GetTechTeacherStatistics`(科技部老师业绩统计) diff --git a/项目信息-薪酬规则与名词解释.md b/项目信息-薪酬规则与名词解释.md index e7b69cf..434162c 100644 --- a/项目信息-薪酬规则与名词解释.md +++ b/项目信息-薪酬规则与名词解释.md @@ -68,6 +68,7 @@ ### 通用规则 +1. **离职员工规则**:离职员工当月业绩大于30000元,按照2%提成,且无奖励 2. **金三角(战队)标准**:组员当月考勤天数 ≥ 20天,则算战队组成成功,可按战队提成计算 ### 健康师薪酬规则