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天,则算战队组成成功,可按战队提成计算
### 健康师薪酬规则