diff --git a/PROJECT_RULES.md b/PROJECT_RULES.md
index 908782f..7e26d21 100644
--- a/PROJECT_RULES.md
+++ b/PROJECT_RULES.md
@@ -143,6 +143,44 @@ Id = Guid.NewGuid().ToString()
- 必须包含所有可能的HTTP状态码响应说明
- 复杂接口必须提供完整的请求示例
+### 接口测试规范
+- **必须测试**: 所有新开发的接口或修改的接口都必须进行测试
+- **测试要求**:
+ - 使用实际数据测试接口功能
+ - 验证接口返回数据的正确性
+ - 测试边界情况和异常情况
+ - 验证接口性能和响应时间
+ - 确保接口符合业务逻辑要求
+- **测试方式**: 可以使用 curl、Postman、Swagger 等工具进行接口测试
+- **测试通过**: 只有测试通过的接口才能提交代码
+- **测试token获取**:
+ - **接口地址**: `/api/oauth/Login`
+ - **请求方式**: POST
+ - **Content-Type**: `application/x-www-form-urlencoded`
+ - **请求参数**:
+ - `account`: `admin`
+ - `password`: `e10adc3949ba59abbe56e057f20f883e`
+ - **curl示例**:
+ ```bash
+ curl -X POST "http://localhost:2011/api/oauth/Login" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e"
+ ```
+ - **返回格式**:
+ ```json
+ {
+ "code": 200,
+ "msg": "操作成功",
+ "data": {
+ "theme": "functional",
+ "token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "user": null
+ }
+ }
+ ```
+ - **token使用**: 返回的token已包含"Bearer "前缀,可直接在请求头中使用:`Authorization: {data.token}`
+
+
## 📊 数据一致性规范
### 统计与列表数据
diff --git a/excel/健康师额外数据模板_测试.xlsx b/excel/健康师额外数据模板_测试.xlsx
new file mode 100644
index 0000000..7afe8fd
--- /dev/null
+++ b/excel/健康师额外数据模板_测试.xlsx
diff --git a/excel/健康师额外数据模板_测试导入.xlsx b/excel/健康师额外数据模板_测试导入.xlsx
new file mode 100644
index 0000000..7afe8fd
--- /dev/null
+++ b/excel/健康师额外数据模板_测试导入.xlsx
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys.Dto/LqTechTeacherSalary/TechTeacherStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys.Dto/LqTechTeacherSalary/TechTeacherStatisticsOutput.cs
deleted file mode 100644
index c50954a..0000000
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys.Dto/LqTechTeacherSalary/TechTeacherStatisticsOutput.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-
-namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary
-{
- ///
- /// 科技部老师统计数据输出
- ///
- public class TechTeacherStatisticsOutput
- {
- ///
- /// 员工ID
- ///
- public string EmployeeId { get; set; }
-
- ///
- /// 员工姓名
- ///
- public string EmployeeName { get; set; }
-
- ///
- /// 开单业绩
- ///
- public decimal OrderAchievement { get; set; }
-
- ///
- /// 消耗业绩
- ///
- public decimal ConsumeAchievement { get; set; }
-
- ///
- /// 退卡业绩
- ///
- public decimal RefundAchievement { get; set; }
-
- ///
- /// 人头(按月份+客户去重统计)
- ///
- public int PersonCount { get; set; }
-
- ///
- /// 人次(按日期+客户去重统计)
- ///
- public decimal PersonTimes { get; set; }
-
- ///
- /// 手工费
- ///
- public decimal LaborCost { get; set; }
- }
-}
-
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs
index f3501d1..a6d3f91 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs
@@ -33,6 +33,11 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
public decimal productPrice { get; set; }
///
+ /// 产品归属仓库
+ ///
+ public string productWarehouse { get; set; }
+
+ ///
/// 门店ID
///
public string storeId { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs
index d3b0432..f570665 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs
@@ -76,6 +76,16 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
/// 门店名称
///
public string storeName { get; set; }
+
+ ///
+ /// 合作机构
+ ///
+ public string hgjg { get; set; }
+
+ ///
+ /// 付款医院
+ ///
+ public string fkyy { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowListOutput.cs
index e0176a3..863b92a 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowListOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowListOutput.cs
@@ -109,6 +109,18 @@ namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
///
[Display(Name = "创建时间")]
public DateTime createTime { get; set; }
+
+ ///
+ /// 送出时间(流水类型为0时使用)
+ ///
+ [Display(Name = "送出时间")]
+ public DateTime? sendTime { get; set; }
+
+ ///
+ /// 送回时间(流水类型为1时使用)
+ ///
+ [Display(Name = "送回时间")]
+ public DateTime? returnTime { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowUpdateInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowUpdateInput.cs
new file mode 100644
index 0000000..7cc4772
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowUpdateInput.cs
@@ -0,0 +1,47 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
+{
+ ///
+ /// 清洗流水修改输入
+ ///
+ public class LqLaundryFlowUpdateInput
+ {
+ ///
+ /// 记录ID
+ ///
+ [Required(ErrorMessage = "记录ID不能为空")]
+ [StringLength(50, ErrorMessage = "记录ID长度不能超过50个字符")]
+ [Display(Name = "记录ID")]
+ public string Id { get; set; }
+
+ ///
+ /// 数量
+ ///
+ [Range(0, int.MaxValue, ErrorMessage = "数量不能小于0")]
+ [Display(Name = "数量")]
+ public int? Quantity { get; set; }
+
+ ///
+ /// 送出时间(流水类型为0时使用)
+ ///
+ [Display(Name = "送出时间")]
+ public DateTime? SendTime { get; set; }
+
+ ///
+ /// 送回时间(流水类型为1时使用)
+ ///
+ [Display(Name = "送回时间")]
+ public DateTime? ReturnTime { get; set; }
+
+ ///
+ /// 备注
+ ///
+ [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
+ [Display(Name = "备注")]
+ public string Remark { get; set; }
+ }
+}
+
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqGenerateInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqGenerateInput.cs
new file mode 100644
index 0000000..0d93d4c
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqGenerateInput.cs
@@ -0,0 +1,13 @@
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsHq
+{
+ ///
+ /// 总部股份统计生成输入
+ ///
+ public class ShareStatisticsHqGenerateInput
+ {
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ public string StatisticsMonth { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqOutput.cs
new file mode 100644
index 0000000..9a41bbd
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqOutput.cs
@@ -0,0 +1,70 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsHq
+{
+ ///
+ /// 总部股份统计输出
+ ///
+ public class ShareStatisticsHqOutput
+ {
+ ///
+ /// 主键ID
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// 统计月份
+ ///
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 收入-全部
+ ///
+ public decimal IncomeGeneral { get; set; }
+
+ ///
+ /// 收入-科技部
+ ///
+ public decimal IncomeTechDept { get; set; }
+
+ ///
+ /// 成本-报销
+ ///
+ public decimal CostReimbursement { get; set; }
+
+ ///
+ /// 成本-人工
+ ///
+ public decimal CostLabor { get; set; }
+
+ ///
+ /// 成本-教育部房租
+ ///
+ public decimal CostEducationRent { get; set; }
+
+ ///
+ /// 成本-仓库房租
+ ///
+ public decimal CostWarehouseRent { get; set; }
+
+ ///
+ /// 成本-总部房租
+ ///
+ public decimal CostHQRent { get; set; }
+
+ ///
+ /// 运营利润
+ ///
+ public decimal OperationalProfit { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreateTime { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? UpdateTime { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqQueryInput.cs
new file mode 100644
index 0000000..c099f26
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqQueryInput.cs
@@ -0,0 +1,13 @@
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsHq
+{
+ ///
+ /// 总部股份统计查询输入
+ ///
+ public class ShareStatisticsHqQueryInput
+ {
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ public string StatisticsMonth { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreGenerateInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreGenerateInput.cs
new file mode 100644
index 0000000..abb1f72
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreGenerateInput.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsStore
+{
+ ///
+ /// 门店股份统计生成输入
+ ///
+ public class ShareStatisticsStoreGenerateInput
+ {
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 门店ID(可选,为空则生成所有门店)
+ ///
+ public string StoreId { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreOutput.cs
new file mode 100644
index 0000000..683e514
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreOutput.cs
@@ -0,0 +1,298 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsStore
+{
+ ///
+ /// 门店股份统计输出
+ ///
+ public class ShareStatisticsStoreOutput
+ {
+ ///
+ /// 主键ID
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// 门店ID
+ ///
+ public string StoreId { get; set; }
+
+ ///
+ /// 门店名称
+ ///
+ public string StoreName { get; set; }
+
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ public string StatisticsMonth { get; set; }
+
+ // 收入部分
+ ///
+ /// 主营收入(消耗总业绩, 不扣减退款)
+ ///
+ public decimal MainIncome { get; set; }
+
+ ///
+ /// 生美消耗业绩
+ ///
+ public decimal ConsumeLifeBeauty { get; set; }
+
+ ///
+ /// 科美消耗业绩
+ ///
+ public decimal ConsumeTechBeauty { get; set; }
+
+ ///
+ /// 医美消耗业绩
+ ///
+ public decimal ConsumeMedicalBeauty { get; set; }
+
+ ///
+ /// 合作消费业绩
+ ///
+ public decimal ConsumeCooperation { get; set; }
+
+ ///
+ /// 产品消耗业绩
+ ///
+ public decimal ConsumeProduct { get; set; }
+
+ ///
+ /// 其他消耗业绩
+ ///
+ public decimal ConsumeOther { get; set; }
+
+ ///
+ /// 其他收入(退款差额>0)
+ ///
+ public decimal OtherIncome { get; set; }
+
+ ///
+ /// 预收款(开单实付)
+ ///
+ public decimal AdvanceReceipt { get; set; }
+
+ ///
+ /// 实际退款(实退业绩)
+ ///
+ public decimal Refund { get; set; }
+
+ ///
+ /// 应收(合作医院)
+ ///
+ public decimal Receivables { get; set; }
+
+ ///
+ /// 银行存款(开单实付-应收)
+ ///
+ public decimal BankDeposit { get; set; }
+
+ // 成本部分
+ ///
+ /// 主营成本-产品(仓库领取)
+ ///
+ public decimal CostProduct { get; set; }
+
+ ///
+ /// 主营成本-福田(福田仓库领取)
+ ///
+ public decimal CostFutian { get; set; }
+
+ ///
+ /// 主营成本-毛巾(清洗送出)
+ ///
+ public decimal CostTowel { get; set; }
+
+ ///
+ /// 主营成本-科技部(科美业绩30%)
+ ///
+ public decimal CostTechDept { get; set; }
+
+ ///
+ /// 主营成本-管理费(总业绩9%)
+ ///
+ public decimal CostManagementFee { get; set; }
+
+ ///
+ /// 主营成本-合作(保留)
+ ///
+ public decimal CostCooperation { get; set; }
+
+ ///
+ /// 主营成本-大项目(保留)
+ ///
+ public decimal CostMajorProject { get; set; }
+
+ ///
+ /// 其他成本(退款差额<0)
+ ///
+ public decimal CostOther { get; set; }
+
+ // 人工工资部分
+ ///
+ /// 人工工资-健康师底薪
+ ///
+ public decimal SalaryBaseHealthCoach { get; set; }
+
+ ///
+ /// 人工工资-店助底薪
+ ///
+ public decimal SalaryBaseAssistant { get; set; }
+
+ ///
+ /// 人工工资-店助主任底薪
+ ///
+ public decimal SalaryBaseDirector { get; set; }
+
+ ///
+ /// 人工工资-店长底薪
+ ///
+ public decimal SalaryBaseStoreManager { get; set; }
+
+ ///
+ /// 人工工资-总经理/经理底薪
+ ///
+ public decimal SalaryBaseGeneralManager { get; set; }
+
+ ///
+ /// 人工工资-健康师提成
+ ///
+ public decimal SalaryCommissionHealthCoach { get; set; }
+
+ ///
+ /// 人工工资-店助提成
+ ///
+ public decimal SalaryCommissionAssistant { get; set; }
+
+ ///
+ /// 人工工资-店助主任提成
+ ///
+ public decimal SalaryCommissionDirector { get; set; }
+
+ ///
+ /// 人工工资-店长提成
+ ///
+ public decimal SalaryCommissionStoreManager { get; set; }
+
+ ///
+ /// 人工工资-总经理/经理提成
+ ///
+ public decimal SalaryCommissionGeneralManager { get; set; }
+
+ ///
+ /// 人工工资-手工
+ ///
+ public decimal SalaryManual { get; set; }
+
+ ///
+ /// 人工工资-出勤(保留)
+ ///
+ public decimal SalaryAttendance { get; set; }
+
+ ///
+ /// 人工工资-岗位-手机保管费
+ ///
+ public decimal SalaryPhoneCustody { get; set; }
+
+ ///
+ /// 人工工资-岗位-人头
+ ///
+ public decimal SalaryHeadcountReward { get; set; }
+
+ ///
+ /// 人工工资-门店T区
+ ///
+ public decimal SalaryTZone { get; set; }
+
+ // 费用与其他
+ ///
+ /// 社保(保留)
+ ///
+ public decimal SocialSecurity { get; set; }
+
+ ///
+ /// 门店房租
+ ///
+ public decimal StoreRent { get; set; }
+
+ ///
+ /// 宿舍房租(保留)
+ ///
+ public decimal DormRent { get; set; }
+
+ ///
+ /// 当期费用(报销)
+ ///
+ public decimal CurrentPeriodExpense { get; set; }
+
+ ///
+ /// 当前费用-秦董(保留)
+ ///
+ public decimal CurrentPeriodExpenseQin { get; set; }
+
+ ///
+ /// 财务费用(保留)
+ ///
+ public decimal FinancialFee { get; set; }
+
+ // 奖励
+ ///
+ /// 奖励-医美(保留)
+ ///
+ public decimal RewardMedicalBeauty { get; set; }
+
+ ///
+ /// 奖励-其他(保留)
+ ///
+ public decimal RewardOther { get; set; }
+
+ ///
+ /// 奖励-大单奖(保留)
+ ///
+ public decimal RewardLargeOrder { get; set; }
+
+ ///
+ /// 奖励-首单奖(保留)
+ ///
+ public decimal RewardFirstOrder { get; set; }
+
+ ///
+ /// 奖励(保留)
+ ///
+ public decimal RewardGeneral { get; set; }
+
+ // 其他保留字段
+ ///
+ /// 其他1(保留)
+ ///
+ public decimal Other1 { get; set; }
+
+ ///
+ /// 其他2(保留)
+ ///
+ public decimal Other2 { get; set; }
+
+ // 利润结果
+ ///
+ /// 利润(预收-实退-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)
+ ///
+ public decimal Profit { get; set; }
+
+ ///
+ /// 财务利润(主营-其他收入-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)
+ ///
+ public decimal FinancialProfit { get; set; }
+
+ // 基础字段
+ ///
+ /// 创建时间
+ ///
+ public DateTime? CreateTime { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? UpdateTime { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreQueryInput.cs
new file mode 100644
index 0000000..9ad52ee
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreQueryInput.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsStore
+{
+ ///
+ /// 门店股份统计查询输入
+ ///
+ public class ShareStatisticsStoreQueryInput
+ {
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 门店ID(可选)
+ ///
+ public string StoreId { get; set; }
+
+ ///
+ /// 门店名称(可选,模糊查询)
+ ///
+ public string StoreName { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptGenerateInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptGenerateInput.cs
new file mode 100644
index 0000000..e0b29ed
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptGenerateInput.cs
@@ -0,0 +1,18 @@
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsTechDept
+{
+ ///
+ /// 科技部股份统计生成输入
+ ///
+ public class ShareStatisticsTechDeptGenerateInput
+ {
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 部门名称(科技一部/科技二部,为空则生成两个部门)
+ ///
+ public string DepartmentName { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptOutput.cs
new file mode 100644
index 0000000..6674655
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptOutput.cs
@@ -0,0 +1,105 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsTechDept
+{
+ ///
+ /// 科技部股份统计输出
+ ///
+ public class ShareStatisticsTechDeptOutput
+ {
+ ///
+ /// 主键ID
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// 部门名称
+ ///
+ public string DepartmentName { get; set; }
+
+ ///
+ /// 统计月份
+ ///
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 收入
+ ///
+ public decimal Income { get; set; }
+
+ ///
+ /// 成本-报销
+ ///
+ public decimal CostReimbursement { get; set; }
+
+ ///
+ /// 成本-人工-老师底薪
+ ///
+ public decimal CostTeacherBase { get; set; }
+
+ ///
+ /// 成本-人工-手工费
+ ///
+ public decimal CostTeacherManual { get; set; }
+
+ ///
+ /// 成本-人工-开单提成
+ ///
+ public decimal CostTeacherBillingComm { get; set; }
+
+ ///
+ /// 成本-人工-消耗提成
+ ///
+ public decimal CostTeacherConsumeComm { get; set; }
+
+ ///
+ /// 成本-人工-专家提成
+ ///
+ public decimal CostTeacherExpertComm { get; set; }
+
+ ///
+ /// 成本-人工-加班
+ ///
+ public decimal CostTeacherOvertime { get; set; }
+
+ ///
+ /// 成本-人工-总经理底薪
+ ///
+ public decimal CostGMBase { get; set; }
+
+ ///
+ /// 成本-人工-总经理提成
+ ///
+ public decimal CostGMComm { get; set; }
+
+ ///
+ /// 奖励-科技部
+ ///
+ public decimal RewardTechDept { get; set; }
+
+ ///
+ /// 成本-其他1
+ ///
+ public decimal CostOther1 { get; set; }
+
+ ///
+ /// 成本-其他2
+ ///
+ public decimal CostOther2 { get; set; }
+
+ ///
+ /// 利润
+ ///
+ public decimal Profit { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreateTime { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? UpdateTime { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptQueryInput.cs
new file mode 100644
index 0000000..573e7be
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptQueryInput.cs
@@ -0,0 +1,18 @@
+namespace NCC.Extend.Entitys.Dto.LqShareStatisticsTechDept
+{
+ ///
+ /// 科技部股份统计查询输入
+ ///
+ public class ShareStatisticsTechDeptQueryInput
+ {
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 部门名称
+ ///
+ public string DepartmentName { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_contract_monthly_cost/LqContractMonthlyCostEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_contract_monthly_cost/LqContractMonthlyCostEntity.cs
new file mode 100644
index 0000000..e8939d7
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_contract_monthly_cost/LqContractMonthlyCostEntity.cs
@@ -0,0 +1,93 @@
+using System;
+using NCC.Common.Const;
+using SqlSugar;
+
+namespace NCC.Extend.Entitys.lq_contract_monthly_cost
+{
+ ///
+ /// 合同成本按月统计表
+ ///
+ [SugarTable("lq_contract_monthly_cost")]
+ [Tenant(ClaimConst.TENANT_ID)]
+ public class LqContractMonthlyCostEntity
+ {
+ ///
+ /// 主键ID
+ ///
+ [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
+ public string Id { get; set; }
+
+ ///
+ /// 合同ID(关联lq_contract.F_Id)
+ ///
+ [SugarColumn(ColumnName = "F_ContractId")]
+ public string ContractId { get; set; }
+
+ ///
+ /// 门店ID(关联lq_mdxx.F_Id,冗余字段便于查询)
+ ///
+ [SugarColumn(ColumnName = "F_StoreId")]
+ public string StoreId { get; set; }
+
+ ///
+ /// 店名(冗余字段,便于查询)
+ ///
+ [SugarColumn(ColumnName = "F_StoreName")]
+ public string StoreName { get; set; }
+
+ ///
+ /// 分类(冗余字段,便于按分类统计)
+ ///
+ [SugarColumn(ColumnName = "F_Category")]
+ public string Category { get; set; }
+
+ ///
+ /// 统计月份(格式:YYYY-MM-01,表示该月的第一天)
+ ///
+ [SugarColumn(ColumnName = "F_Month")]
+ public DateTime Month { get; set; }
+
+ ///
+ /// 该月的合同成本(缴租金额 / 交租周期)
+ ///
+ [SugarColumn(ColumnName = "F_MonthlyCost", DecimalDigits = 2)]
+ public decimal MonthlyCost { get; set; }
+
+ ///
+ /// 交租周期(冗余字段,便于查询)
+ ///
+ [SugarColumn(ColumnName = "F_PaymentCycle")]
+ public int PaymentCycle { get; set; }
+
+ ///
+ /// 缴租金额(冗余字段,便于查询)
+ ///
+ [SugarColumn(ColumnName = "F_PaymentAmount", DecimalDigits = 2)]
+ public decimal PaymentAmount { get; set; }
+
+ ///
+ /// 是否有效(1-有效,0-无效)
+ ///
+ [SugarColumn(ColumnName = "F_IsEffective")]
+ public int IsEffective { get; set; } = 1;
+
+ ///
+ /// 创建人ID
+ ///
+ [SugarColumn(ColumnName = "F_CreateUser")]
+ public string CreateUser { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ [SugarColumn(ColumnName = "F_CreateTime")]
+ public DateTime CreateTime { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ [SugarColumn(ColumnName = "F_UpdateTime")]
+ public DateTime? UpdateTime { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_deductinfo/LqKdDeductinfoEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_deductinfo/LqKdDeductinfoEntity.cs
index ea559d0..df8a633 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_deductinfo/LqKdDeductinfoEntity.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_deductinfo/LqKdDeductinfoEntity.cs
@@ -36,6 +36,12 @@ namespace NCC.Extend.Entitys.lq_kd_deductinfo
public string BillingId { get; set; }
///
+ /// 开单时间(来源:lq_kd_kdjlb.kdrq)
+ ///
+ [SugarColumn(ColumnName = "F_BillingTime")]
+ public DateTime? BillingTime { get; set; }
+
+ ///
/// 合计金额
///
[SugarColumn(ColumnName = "F_Amount")]
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_laundry_flow/LqLaundryFlowEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_laundry_flow/LqLaundryFlowEntity.cs
index 3fc4e2a..6d8dbf1 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_laundry_flow/LqLaundryFlowEntity.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_laundry_flow/LqLaundryFlowEntity.cs
@@ -88,6 +88,18 @@ namespace NCC.Extend.Entitys.lq_laundry_flow
///
[SugarColumn(ColumnName = "F_CreateTime")]
public DateTime CreateTime { get; set; }
+
+ ///
+ /// 送出时间(流水类型为0时使用)
+ ///
+ [SugarColumn(ColumnName = "F_SendTime")]
+ public DateTime? SendTime { get; set; }
+
+ ///
+ /// 送回时间(流水类型为1时使用)
+ ///
+ [SugarColumn(ColumnName = "F_ReturnTime")]
+ public DateTime? ReturnTime { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_hq/LqShareStatisticsHqEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_hq/LqShareStatisticsHqEntity.cs
new file mode 100644
index 0000000..b7c81e4
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_hq/LqShareStatisticsHqEntity.cs
@@ -0,0 +1,104 @@
+using System;
+using NCC.Common.Const;
+using SqlSugar;
+
+namespace NCC.Extend.Entitys.lq_share_statistics_hq
+{
+ ///
+ /// 总部股份统计表
+ ///
+ [SugarTable("lq_share_statistics_hq")]
+ [Tenant(ClaimConst.TENANT_ID)]
+ public class LqShareStatisticsHqEntity
+ {
+ ///
+ /// 主键ID
+ ///
+ [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
+ public string Id { get; set; }
+
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ [SugarColumn(ColumnName = "F_StatisticsMonth")]
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 收入-全部(开单业绩9%)
+ ///
+ [SugarColumn(ColumnName = "F_IncomeGeneral")]
+ public decimal IncomeGeneral { get; set; }
+
+ ///
+ /// 收入-科技部(科美30%的9%)
+ ///
+ [SugarColumn(ColumnName = "F_IncomeTechDept")]
+ public decimal IncomeTechDept { get; set; }
+
+ ///
+ /// 成本-报销(总部费用)
+ ///
+ [SugarColumn(ColumnName = "F_CostReimbursement")]
+ public decimal CostReimbursement { get; set; }
+
+ ///
+ /// 成本-人工(保留)
+ ///
+ [SugarColumn(ColumnName = "F_CostLabor")]
+ public decimal CostLabor { get; set; }
+
+ ///
+ /// 成本-教育部房租
+ ///
+ [SugarColumn(ColumnName = "F_CostEducationRent")]
+ public decimal CostEducationRent { get; set; }
+
+ ///
+ /// 成本-仓库房租
+ ///
+ [SugarColumn(ColumnName = "F_CostWarehouseRent")]
+ public decimal CostWarehouseRent { get; set; }
+
+ ///
+ /// 成本-总部房租
+ ///
+ [SugarColumn(ColumnName = "F_CostHQRent")]
+ public decimal CostHQRent { get; set; }
+
+ ///
+ /// 总部运营利润
+ ///
+ [SugarColumn(ColumnName = "F_OperationalProfit")]
+ public decimal OperationalProfit { 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; }
+
+ ///
+ /// 是否有效(1:有效 0:无效)
+ ///
+ [SugarColumn(ColumnName = "F_IsEffective")]
+ public int IsEffective { get; set; } = 1;
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs
new file mode 100644
index 0000000..aaf45fe
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs
@@ -0,0 +1,376 @@
+using System;
+using NCC.Common.Const;
+using SqlSugar;
+
+namespace NCC.Extend.Entitys.lq_share_statistics_store
+{
+ ///
+ /// 门店股份统计表
+ ///
+ [SugarTable("lq_share_statistics_store")]
+ [Tenant(ClaimConst.TENANT_ID)]
+ public class LqShareStatisticsStoreEntity
+ {
+ ///
+ /// 主键ID
+ ///
+ [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
+ public string Id { get; set; }
+
+ ///
+ /// 门店ID
+ ///
+ [SugarColumn(ColumnName = "F_StoreId")]
+ public string StoreId { get; set; }
+
+ ///
+ /// 门店名称
+ ///
+ [SugarColumn(ColumnName = "F_StoreName")]
+ public string StoreName { get; set; }
+
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ [SugarColumn(ColumnName = "F_StatisticsMonth")]
+ public string StatisticsMonth { get; set; }
+
+ // 收入部分
+ ///
+ /// 主营收入(消耗总业绩, 不扣减退款)
+ ///
+ [SugarColumn(ColumnName = "F_MainIncome")]
+ public decimal MainIncome { get; set; }
+
+ ///
+ /// 生美消耗业绩
+ ///
+ [SugarColumn(ColumnName = "F_ConsumeLifeBeauty")]
+ public decimal ConsumeLifeBeauty { get; set; }
+
+ ///
+ /// 科美消耗业绩
+ ///
+ [SugarColumn(ColumnName = "F_ConsumeTechBeauty")]
+ public decimal ConsumeTechBeauty { get; set; }
+
+ ///
+ /// 医美消耗业绩
+ ///
+ [SugarColumn(ColumnName = "F_ConsumeMedicalBeauty")]
+ public decimal ConsumeMedicalBeauty { get; set; }
+
+ ///
+ /// 合作消费业绩
+ ///
+ [SugarColumn(ColumnName = "F_ConsumeCooperation")]
+ public decimal ConsumeCooperation { get; set; }
+
+ ///
+ /// 产品消耗业绩
+ ///
+ [SugarColumn(ColumnName = "F_ConsumeProduct")]
+ public decimal ConsumeProduct { get; set; }
+
+ ///
+ /// 其他消耗业绩
+ ///
+ [SugarColumn(ColumnName = "F_ConsumeOther")]
+ public decimal ConsumeOther { get; set; }
+
+ ///
+ /// 其他收入(退款差额>0)
+ ///
+ [SugarColumn(ColumnName = "F_OtherIncome")]
+ public decimal OtherIncome { get; set; }
+
+ ///
+ /// 预收款(开单实付)
+ ///
+ [SugarColumn(ColumnName = "F_AdvanceReceipt")]
+ public decimal AdvanceReceipt { get; set; }
+
+ ///
+ /// 实际退款(实退业绩)
+ ///
+ [SugarColumn(ColumnName = "F_Refund")]
+ public decimal Refund { get; set; }
+
+ ///
+ /// 应收(合作医院)
+ ///
+ [SugarColumn(ColumnName = "F_Receivables")]
+ public decimal Receivables { get; set; }
+
+ ///
+ /// 银行存款(开单实付-应收)
+ ///
+ [SugarColumn(ColumnName = "F_BankDeposit")]
+ public decimal BankDeposit { get; set; }
+
+ // 成本部分
+ ///
+ /// 主营成本-产品(仓库领取)
+ ///
+ [SugarColumn(ColumnName = "F_CostProduct")]
+ public decimal CostProduct { get; set; }
+
+ ///
+ /// 主营成本-福田(福田仓库领取)
+ ///
+ [SugarColumn(ColumnName = "F_CostFutian")]
+ public decimal CostFutian { get; set; }
+
+ ///
+ /// 主营成本-毛巾(清洗送出)
+ ///
+ [SugarColumn(ColumnName = "F_CostTowel")]
+ public decimal CostTowel { get; set; }
+
+ ///
+ /// 主营成本-科技部(科美业绩30%)
+ ///
+ [SugarColumn(ColumnName = "F_CostTechDept")]
+ public decimal CostTechDept { get; set; }
+
+ ///
+ /// 主营成本-管理费(总业绩9%)
+ ///
+ [SugarColumn(ColumnName = "F_CostManagementFee")]
+ public decimal CostManagementFee { get; set; }
+
+ ///
+ /// 主营成本-合作(保留)
+ ///
+ [SugarColumn(ColumnName = "F_CostCooperation")]
+ public decimal CostCooperation { get; set; }
+
+ ///
+ /// 主营成本-大项目(保留)
+ ///
+ [SugarColumn(ColumnName = "F_CostMajorProject")]
+ public decimal CostMajorProject { get; set; }
+
+ ///
+ /// 其他成本(退款差额<0)
+ ///
+ [SugarColumn(ColumnName = "F_CostOther")]
+ public decimal CostOther { get; set; }
+
+ // 人工工资部分
+ ///
+ /// 人工工资-健康师底薪
+ ///
+ [SugarColumn(ColumnName = "F_SalaryBaseHealthCoach")]
+ public decimal SalaryBaseHealthCoach { get; set; }
+
+ ///
+ /// 人工工资-店助底薪
+ ///
+ [SugarColumn(ColumnName = "F_SalaryBaseAssistant")]
+ public decimal SalaryBaseAssistant { get; set; }
+
+ ///
+ /// 人工工资-店助主任底薪
+ ///
+ [SugarColumn(ColumnName = "F_SalaryBaseDirector")]
+ public decimal SalaryBaseDirector { get; set; }
+
+ ///
+ /// 人工工资-店长底薪
+ ///
+ [SugarColumn(ColumnName = "F_SalaryBaseStoreManager")]
+ public decimal SalaryBaseStoreManager { get; set; }
+
+ ///
+ /// 人工工资-总经理/经理底薪
+ ///
+ [SugarColumn(ColumnName = "F_SalaryBaseGeneralManager")]
+ public decimal SalaryBaseGeneralManager { get; set; }
+
+ ///
+ /// 人工工资-健康师提成
+ ///
+ [SugarColumn(ColumnName = "F_SalaryCommissionHealthCoach")]
+ public decimal SalaryCommissionHealthCoach { get; set; }
+
+ ///
+ /// 人工工资-店助提成
+ ///
+ [SugarColumn(ColumnName = "F_SalaryCommissionAssistant")]
+ public decimal SalaryCommissionAssistant { get; set; }
+
+ ///
+ /// 人工工资-店助主任提成
+ ///
+ [SugarColumn(ColumnName = "F_SalaryCommissionDirector")]
+ public decimal SalaryCommissionDirector { get; set; }
+
+ ///
+ /// 人工工资-店长提成
+ ///
+ [SugarColumn(ColumnName = "F_SalaryCommissionStoreManager")]
+ public decimal SalaryCommissionStoreManager { get; set; }
+
+ ///
+ /// 人工工资-总经理/经理提成
+ ///
+ [SugarColumn(ColumnName = "F_SalaryCommissionGeneralManager")]
+ public decimal SalaryCommissionGeneralManager { get; set; }
+
+ ///
+ /// 人工工资-手工
+ ///
+ [SugarColumn(ColumnName = "F_SalaryManual")]
+ public decimal SalaryManual { get; set; }
+
+ ///
+ /// 人工工资-出勤(保留)
+ ///
+ [SugarColumn(ColumnName = "F_SalaryAttendance")]
+ public decimal SalaryAttendance { get; set; }
+
+ ///
+ /// 人工工资-岗位-手机保管费
+ ///
+ [SugarColumn(ColumnName = "F_SalaryPhoneCustody")]
+ public decimal SalaryPhoneCustody { get; set; }
+
+ ///
+ /// 人工工资-岗位-人头
+ ///
+ [SugarColumn(ColumnName = "F_SalaryHeadcountReward")]
+ public decimal SalaryHeadcountReward { get; set; }
+
+ ///
+ /// 人工工资-门店T区
+ ///
+ [SugarColumn(ColumnName = "F_SalaryTZone")]
+ public decimal SalaryTZone { get; set; }
+
+ // 费用与其他
+ ///
+ /// 社保(保留)
+ ///
+ [SugarColumn(ColumnName = "F_SocialSecurity")]
+ public decimal SocialSecurity { get; set; }
+
+ ///
+ /// 门店房租
+ ///
+ [SugarColumn(ColumnName = "F_StoreRent")]
+ public decimal StoreRent { get; set; }
+
+ ///
+ /// 宿舍房租(保留)
+ ///
+ [SugarColumn(ColumnName = "F_DormRent")]
+ public decimal DormRent { get; set; }
+
+ ///
+ /// 当期费用(报销)
+ ///
+ [SugarColumn(ColumnName = "F_CurrentPeriodExpense")]
+ public decimal CurrentPeriodExpense { get; set; }
+
+ ///
+ /// 当前费用-秦董(保留)
+ ///
+ [SugarColumn(ColumnName = "F_CurrentPeriodExpenseQin")]
+ public decimal CurrentPeriodExpenseQin { get; set; }
+
+ ///
+ /// 财务费用(保留)
+ ///
+ [SugarColumn(ColumnName = "F_FinancialFee")]
+ public decimal FinancialFee { get; set; }
+
+ // 奖励
+ ///
+ /// 奖励-医美(保留)
+ ///
+ [SugarColumn(ColumnName = "F_RewardMedicalBeauty")]
+ public decimal RewardMedicalBeauty { get; set; }
+
+ ///
+ /// 奖励-其他(保留)
+ ///
+ [SugarColumn(ColumnName = "F_RewardOther")]
+ public decimal RewardOther { get; set; }
+
+ ///
+ /// 奖励-大单奖(保留)
+ ///
+ [SugarColumn(ColumnName = "F_RewardLargeOrder")]
+ public decimal RewardLargeOrder { get; set; }
+
+ ///
+ /// 奖励-首单奖(保留)
+ ///
+ [SugarColumn(ColumnName = "F_RewardFirstOrder")]
+ public decimal RewardFirstOrder { get; set; }
+
+ ///
+ /// 奖励(保留)
+ ///
+ [SugarColumn(ColumnName = "F_RewardGeneral")]
+ public decimal RewardGeneral { get; set; }
+
+ // 其他保留字段
+ ///
+ /// 其他1(保留)
+ ///
+ [SugarColumn(ColumnName = "F_Other1")]
+ public decimal Other1 { get; set; }
+
+ ///
+ /// 其他2(保留)
+ ///
+ [SugarColumn(ColumnName = "F_Other2")]
+ public decimal Other2 { get; set; }
+
+ // 利润结果
+ ///
+ /// 利润(预收-实退-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)
+ ///
+ [SugarColumn(ColumnName = "F_Profit")]
+ public decimal Profit { get; set; }
+
+ ///
+ /// 财务利润(主营-其他收入-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)
+ ///
+ [SugarColumn(ColumnName = "F_FinancialProfit")]
+ public decimal FinancialProfit { 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; }
+
+ ///
+ /// 是否有效(1:有效 0:无效)
+ ///
+ [SugarColumn(ColumnName = "F_IsEffective")]
+ public int IsEffective { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_tech_dept/LqShareStatisticsTechDeptEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_tech_dept/LqShareStatisticsTechDeptEntity.cs
new file mode 100644
index 0000000..96821f3
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_tech_dept/LqShareStatisticsTechDeptEntity.cs
@@ -0,0 +1,146 @@
+using System;
+using NCC.Common.Const;
+using SqlSugar;
+
+namespace NCC.Extend.Entitys.lq_share_statistics_tech_dept
+{
+ ///
+ /// 科技部股份统计表
+ ///
+ [SugarTable("lq_share_statistics_tech_dept")]
+ [Tenant(ClaimConst.TENANT_ID)]
+ public class LqShareStatisticsTechDeptEntity
+ {
+ ///
+ /// 主键ID
+ ///
+ [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
+ public string Id { get; set; }
+
+ ///
+ /// 部门名称(科技一部/二部)
+ ///
+ [SugarColumn(ColumnName = "F_DepartmentName")]
+ public string DepartmentName { get; set; }
+
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ [SugarColumn(ColumnName = "F_StatisticsMonth")]
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 收入(门店科美开单30%)
+ ///
+ [SugarColumn(ColumnName = "F_Income")]
+ public decimal Income { get; set; }
+
+ ///
+ /// 成本-报销
+ ///
+ [SugarColumn(ColumnName = "F_CostReimbursement")]
+ public decimal CostReimbursement { get; set; }
+
+ ///
+ /// 成本-人工-科技部老师底薪
+ ///
+ [SugarColumn(ColumnName = "F_CostTeacherBase")]
+ public decimal CostTeacherBase { get; set; }
+
+ ///
+ /// 成本-人工-科技部手工费
+ ///
+ [SugarColumn(ColumnName = "F_CostTeacherManual")]
+ public decimal CostTeacherManual { get; set; }
+
+ ///
+ /// 成本-人工-科技部开单提成
+ ///
+ [SugarColumn(ColumnName = "F_CostTeacherBillingComm")]
+ public decimal CostTeacherBillingComm { get; set; }
+
+ ///
+ /// 成本-人工-科技部消耗提成
+ ///
+ [SugarColumn(ColumnName = "F_CostTeacherConsumeComm")]
+ public decimal CostTeacherConsumeComm { get; set; }
+
+ ///
+ /// 成本-人工-科技部专家提成(保留)
+ ///
+ [SugarColumn(ColumnName = "F_CostTeacherExpertComm")]
+ public decimal CostTeacherExpertComm { get; set; }
+
+ ///
+ /// 成本-人工-科技部加班(保留)
+ ///
+ [SugarColumn(ColumnName = "F_CostTeacherOvertime")]
+ public decimal CostTeacherOvertime { get; set; }
+
+ ///
+ /// 成本-人工-科技部总经理底薪
+ ///
+ [SugarColumn(ColumnName = "F_CostGMBase")]
+ public decimal CostGMBase { get; set; }
+
+ ///
+ /// 成本-人工-科技部总经理提成
+ ///
+ [SugarColumn(ColumnName = "F_CostGMComm")]
+ public decimal CostGMComm { get; set; }
+
+ ///
+ /// 奖励-科技部(保留)
+ ///
+ [SugarColumn(ColumnName = "F_RewardTechDept")]
+ public decimal RewardTechDept { get; set; }
+
+ ///
+ /// 成本-其他1(保留)
+ ///
+ [SugarColumn(ColumnName = "F_CostOther1")]
+ public decimal CostOther1 { get; set; }
+
+ ///
+ /// 成本-其他2(保留)
+ ///
+ [SugarColumn(ColumnName = "F_CostOther2")]
+ public decimal CostOther2 { get; set; }
+
+ ///
+ /// 科技部利润
+ ///
+ [SugarColumn(ColumnName = "F_Profit")]
+ public decimal Profit { 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; }
+
+ ///
+ /// 是否有效(1:有效 0:无效)
+ ///
+ [SugarColumn(ColumnName = "F_IsEffective")]
+ public int IsEffective { get; set; } = 1;
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs
index 9a85e27..87e4070 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
@@ -12,6 +13,7 @@ using NCC.Extend.Entitys.Dto.LqContract;
using NCC.Extend.Entitys.Enum;
using NCC.Extend.Entitys.lq_contract;
using NCC.Extend.Entitys.lq_contract_rent_detail;
+using NCC.Extend.Entitys.lq_contract_monthly_cost;
using NCC.Extend.Entitys.lq_mdxx;
using NCC.FriendlyException;
using NCC.System.Entitys.Permission;
@@ -147,6 +149,9 @@ namespace NCC.Extend
// 自动生成月租明细
await GenerateRentDetailsAsync(contractEntity.Id, contractEntity.ContractStartDate, contractEntity.ContractEndDate, contractEntity.PaymentCycle, contractEntity.PaymentAmount);
+ // 自动生成按月成本记录
+ await GenerateMonthlyCostAsync(contractEntity.Id, contractEntity.StoreId, contractEntity.StoreName, contractEntity.Category, contractEntity.ContractStartDate, contractEntity.ContractEndDate, contractEntity.PaymentCycle, contractEntity.PaymentAmount);
+
// 计算下次应交时间
await CalculateNextPaymentDate(contractEntity.Id);
@@ -249,6 +254,9 @@ namespace NCC.Extend
contract.PaymentCycle != input.PaymentCycle ||
contract.PaymentAmount != input.PaymentAmount;
+ // 判断是否需要更新成本记录的分类(如果只修改了分类,不需要重新生成,只需更新分类字段)
+ bool needUpdateCategory = contract.Category != input.Category;
+
// 更新合同信息
contract.StoreId = input.StoreId;
contract.StoreName = store.Dm ?? "";
@@ -287,8 +295,33 @@ namespace NCC.Extend
.Where(x => x.ContractId == contract.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
.ExecuteCommandAsync();
+ // 删除旧的成本记录(逻辑删除)
+ await _db.Updateable()
+ .SetColumns(x => new LqContractMonthlyCostEntity
+ {
+ IsEffective = StatusEnum.无效.GetHashCode(),
+ UpdateTime = DateTime.Now
+ })
+ .Where(x => x.ContractId == contract.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .ExecuteCommandAsync();
+
// 生成新的明细
await GenerateRentDetailsAsync(contract.Id, contract.ContractStartDate, contract.ContractEndDate, contract.PaymentCycle, contract.PaymentAmount);
+
+ // 生成新的成本记录
+ await GenerateMonthlyCostAsync(contract.Id, contract.StoreId, contract.StoreName, contract.Category, contract.ContractStartDate, contract.ContractEndDate, contract.PaymentCycle, contract.PaymentAmount);
+ }
+ else if (needUpdateCategory)
+ {
+ // 如果只修改了分类,只需更新成本记录的分类字段
+ await _db.Updateable()
+ .SetColumns(x => new LqContractMonthlyCostEntity
+ {
+ Category = contract.Category,
+ UpdateTime = DateTime.Now
+ })
+ .Where(x => x.ContractId == contract.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .ExecuteCommandAsync();
}
// 重新计算下次应交时间
@@ -365,6 +398,16 @@ namespace NCC.Extend
.Where(x => x.ContractId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
.ExecuteCommandAsync();
+ // 删除该合同的所有成本记录(逻辑删除)
+ await _db.Updateable()
+ .SetColumns(x => new LqContractMonthlyCostEntity
+ {
+ IsEffective = StatusEnum.无效.GetHashCode(),
+ UpdateTime = DateTime.Now
+ })
+ .Where(x => x.ContractId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .ExecuteCommandAsync();
+
// 删除合同(逻辑删除)
contract.IsEffective = StatusEnum.无效.GetHashCode();
contract.UpdateUser = _userManager.UserId;
@@ -792,6 +835,35 @@ namespace NCC.Extend
#endregion
+ #region 根据合同id获取合同成本列表
+ ///
+ /// 根据合同id获取合同成本列表
+ ///
+ ///
+ ///
+ [HttpGet("GetContractCostList")]
+ public async Task GetContractCostListAsync([FromQuery] string contractId)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(contractId))
+ {
+ throw NCCException.Oh("合同ID不能为空");
+ }
+
+ var costList = await _db.Queryable()
+ .Where(x => x.ContractId == contractId && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .ToListAsync();
+ return costList;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取合同成本列表失败");
+ throw NCCException.Oh($"获取合同成本列表失败:{ex.Message}");
+ }
+ }
+ #endregion
+
#region 私有方法
///
@@ -842,6 +914,57 @@ namespace NCC.Extend
}
///
+ /// 生成按月成本记录
+ ///
+ /// 合同ID
+ /// 门店ID
+ /// 店名
+ /// 分类
+ /// 合同起始日期
+ /// 合同结束日期
+ /// 交租周期(月)
+ /// 缴租金额
+ private async Task GenerateMonthlyCostAsync(string contractId, string storeId, string storeName, string category, DateTime contractStartDate, DateTime contractEndDate, int paymentCycle, decimal paymentAmount)
+ {
+ // 计算每个月成本 = 缴租金额 / 交租周期
+ var monthlyCost = paymentAmount / paymentCycle;
+
+ var costRecords = new List();
+ var currentMonth = new DateTime(contractStartDate.Year, contractStartDate.Month, 1);
+
+ // 从合同起始月份开始,逐月生成成本记录,直到合同结束月份
+ while (currentMonth <= contractEndDate)
+ {
+ var costRecord = new LqContractMonthlyCostEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ ContractId = contractId,
+ StoreId = storeId,
+ StoreName = storeName,
+ Category = category,
+ Month = currentMonth,
+ MonthlyCost = monthlyCost,
+ PaymentCycle = paymentCycle,
+ PaymentAmount = paymentAmount,
+ IsEffective = StatusEnum.有效.GetHashCode(),
+ CreateUser = _userManager.UserId,
+ CreateTime = DateTime.Now
+ };
+
+ costRecords.Add(costRecord);
+
+ // 计算下一个月
+ currentMonth = currentMonth.AddMonths(1);
+ }
+
+ // 批量插入
+ if (costRecords.Any())
+ {
+ await _db.Insertable(costRecords).ExecuteCommandAsync();
+ }
+ }
+
+ ///
/// 计算下次应交时间(匿名方法)
///
/// 合同ID
@@ -888,6 +1011,152 @@ namespace NCC.Extend
#endregion
+ #region 获取合同成本按月统计
+
+ ///
+ /// 获取合同成本按月统计
+ ///
+ ///
+ /// 根据合同ID、门店ID、月份等条件查询合同成本按月统计数据
+ ///
+ /// 示例请求:
+ /// ```
+ /// GET /api/Extend/LqContract/GetMonthlyCost?contractId=合同ID&storeId=门店ID&startMonth=2025-01&endMonth=2025-12
+ /// ```
+ ///
+ /// 参数说明:
+ /// - contractId: 合同ID(可选)
+ /// - storeId: 门店ID(可选)
+ /// - startMonth: 开始月份(格式:YYYY-MM,可选)
+ /// - endMonth: 结束月份(格式:YYYY-MM,可选)
+ ///
+ /// 合同ID(可选)
+ /// 门店ID(可选)
+ /// 分类(可选)
+ /// 开始月份(格式:YYYY-MM,可选)
+ /// 结束月份(格式:YYYY-MM,可选)
+ /// 合同成本按月统计列表
+ /// 查询成功
+ /// 服务器错误
+ [HttpGet("GetMonthlyCost")]
+ public async Task GetMonthlyCostAsync(
+ [FromQuery] string contractId = null,
+ [FromQuery] string storeId = null,
+ [FromQuery] string category = null,
+ [FromQuery] string startMonth = null,
+ [FromQuery] string endMonth = null)
+ {
+ try
+ {
+ var query = _db.Queryable()
+ .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
+ .WhereIF(!string.IsNullOrWhiteSpace(contractId), x => x.ContractId == contractId)
+ .WhereIF(!string.IsNullOrWhiteSpace(storeId), x => x.StoreId == storeId)
+ .WhereIF(!string.IsNullOrWhiteSpace(category), x => x.Category == category);
+
+ // 处理月份范围筛选
+ if (!string.IsNullOrWhiteSpace(startMonth))
+ {
+ if (DateTime.TryParseExact(startMonth, "yyyy-MM", null, DateTimeStyles.None, out DateTime startDate))
+ {
+ var startMonthDate = new DateTime(startDate.Year, startDate.Month, 1);
+ query = query.Where(x => x.Month >= startMonthDate);
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(endMonth))
+ {
+ if (DateTime.TryParseExact(endMonth, "yyyy-MM", null, DateTimeStyles.None, out DateTime endDate))
+ {
+ var endMonthDate = new DateTime(endDate.Year, endDate.Month, DateTime.DaysInMonth(endDate.Year, endDate.Month));
+ query = query.Where(x => x.Month <= endMonthDate);
+ }
+ }
+
+ var data = await query
+ .OrderBy(x => x.Month)
+ .Select(x => new
+ {
+ id = x.Id,
+ contractId = x.ContractId,
+ storeId = x.StoreId,
+ storeName = x.StoreName,
+ category = x.Category,
+ month = x.Month.ToString("yyyy-MM"),
+ monthlyCost = x.MonthlyCost,
+ paymentCycle = x.PaymentCycle,
+ paymentAmount = x.PaymentAmount,
+ createTime = x.CreateTime
+ })
+ .ToListAsync();
+
+ return new { code = 200, msg = "查询成功", data = data };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取合同成本按月统计失败");
+ throw NCCException.Oh($"查询失败:{ex.Message}");
+ }
+ }
+
+ ///
+ /// 按门店和月份统计合同成本
+ ///
+ ///
+ /// 根据门店ID和月份统计该门店的合同成本总和
+ ///
+ /// 示例请求:
+ /// ```
+ /// GET /api/Extend/LqContract/GetStoreMonthlyCost?storeId=门店ID&month=2025-11
+ /// ```
+ ///
+ /// 门店ID
+ /// 月份(格式:YYYY-MM)
+ /// 该门店该月的合同成本总和
+ /// 查询成功
+ /// 参数错误
+ /// 服务器错误
+ [HttpGet("GetStoreMonthlyCost")]
+ public async Task GetStoreMonthlyCostAsync(
+ [FromQuery] string storeId,
+ [FromQuery] string month)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(storeId))
+ {
+ throw NCCException.Oh("门店ID不能为空");
+ }
+
+ if (string.IsNullOrWhiteSpace(month))
+ {
+ throw NCCException.Oh("月份不能为空");
+ }
+
+ if (!DateTime.TryParseExact(month, "yyyy-MM", null, DateTimeStyles.None, out DateTime monthDate))
+ {
+ throw NCCException.Oh("月份格式错误,应为 YYYY-MM");
+ }
+
+ var monthStart = new DateTime(monthDate.Year, monthDate.Month, 1);
+
+ var totalCost = await _db.Queryable()
+ .Where(x => x.StoreId == storeId
+ && x.Month == monthStart
+ && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => x.MonthlyCost);
+
+ return new { code = 200, msg = "查询成功", data = new { storeId = storeId, month = month, totalCost = totalCost } };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "按门店和月份统计合同成本失败");
+ throw NCCException.Oh($"查询失败:{ex.Message}");
+ }
+ }
+
+ #endregion
+
#region 统计门店合同费用
///
@@ -960,13 +1229,13 @@ namespace NCC.Extend
var details = await _db.Queryable(
(detail, contract) => new JoinQueryInfos(
JoinType.Inner, detail.ContractId == contract.Id))
- .Where((detail, contract) =>
+ .Where((detail, contract) =>
contract.StoreId == input.StoreId &&
contract.IsEffective == StatusEnum.有效.GetHashCode() &&
detail.IsEffective == StatusEnum.有效.GetHashCode() &&
detail.PaymentMonth >= monthStart &&
detail.PaymentMonth <= monthEnd)
- .WhereIF(input.Categories != null && input.Categories.Length > 0,
+ .WhereIF(input.Categories != null && input.Categories.Length > 0,
(detail, contract) => contract.Category != null && input.Categories.Contains(contract.Category))
.Select((detail, contract) => new
{
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
index e1cad62..ef3bad1 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
@@ -706,14 +706,15 @@ namespace NCC.Extend
}
// 2.3.1 查询开单业绩(按品项类型)
+ // 使用targetDict来关联,确保每个门店只关联一条目标记录,避免重复统计
+ // 先按门店统计业绩,再按部门汇总
var billingSql = $@"
SELECT
- target.{deptField} as TargetDeptId,
+ billing.djmd as StoreId,
COALESCE(SUM(pxmx.F_ActualPrice), 0) as StoreAmount
FROM lq_kd_pxmx pxmx
INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
- INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}'
WHERE pxmx.F_IsEffective = 1
AND billing.F_IsEffective = 1
AND item.F_IsEffective = 1
@@ -721,35 +722,48 @@ namespace NCC.Extend
AND billing.djmd IN ('{storeIdsStr}')
AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
- GROUP BY target.{deptField}";
+ GROUP BY billing.djmd";
var billingData = await _db.Ado.SqlQueryAsync(billingSql);
- // 分配开单业绩到对应部门
+ // 分配开单业绩到对应部门(使用targetDict确保每个门店只关联一条目标记录)
foreach (var billing in billingData ?? Enumerable.Empty())
{
var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m;
- var targetDeptId = billing?.TargetDeptId?.ToString();
+ var storeId = billing?.StoreId?.ToString();
- if (storeAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
+ if (storeAmount <= 0 || string.IsNullOrEmpty(storeId))
continue;
- // 只分配给在查询列表中的部门,避免重复统计
- if (departmentDict.ContainsKey(targetDeptId))
+ // 从targetDict中获取门店对应的部门信息
+ if (targetDict.ContainsKey(storeId))
{
- departmentDict[targetDeptId].BillingPerformance += storeAmount;
+ var target = targetDict[storeId];
+ // 动态获取部门字段值
+ string targetDeptId = null;
+ if (deptField == "F_EducationDepartment")
+ targetDeptId = target?.F_EducationDepartment?.ToString();
+ else if (deptField == "F_TechDepartment")
+ targetDeptId = target?.F_TechDepartment?.ToString();
+ else if (deptField == "F_MajorProjectDepartment")
+ targetDeptId = target?.F_MajorProjectDepartment?.ToString();
+
+ if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
+ {
+ departmentDict[targetDeptId].BillingPerformance += storeAmount;
+ }
}
}
// 2.3.2 查询退卡业绩(按品项类型)
+ // 使用targetDict来关联,确保每个门店只关联一条目标记录,避免重复统计
var refundSql = $@"
SELECT
- target.{deptField} as TargetDeptId,
+ refund.md as StoreId,
COALESCE(SUM(refund_mx.tkje), 0) as StoreRefundAmount
FROM lq_hytk_mx refund_mx
INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id
INNER JOIN lq_xmzl item ON refund_mx.px = item.F_Id
- INNER JOIN lq_md_target target ON refund.md = target.F_StoreId AND target.F_Month = '{month}'
WHERE refund_mx.F_IsEffective = 1
AND refund.F_IsEffective = 1
AND item.F_IsEffective = 1
@@ -757,59 +771,86 @@ namespace NCC.Extend
AND refund.md IN ('{storeIdsStr}')
AND refund.tksj >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
AND refund.tksj < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
- GROUP BY target.{deptField}";
+ GROUP BY refund.md";
var refundData = await _db.Ado.SqlQueryAsync(refundSql);
- // 分配退卡业绩到对应部门
+ // 分配退卡业绩到对应部门(使用targetDict确保每个门店只关联一条目标记录)
foreach (var refund in refundData ?? Enumerable.Empty())
{
var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m;
- var targetDeptId = refund?.TargetDeptId?.ToString();
+ var storeId = refund?.StoreId?.ToString();
- if (storeRefundAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
+ if (storeRefundAmount <= 0 || string.IsNullOrEmpty(storeId))
continue;
- // 只分配给在查询列表中的部门,避免重复统计
- if (departmentDict.ContainsKey(targetDeptId))
+ // 从targetDict中获取门店对应的部门信息
+ if (targetDict.ContainsKey(storeId))
{
- departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
+ var target = targetDict[storeId];
+ // 动态获取部门字段值
+ string targetDeptId = null;
+ if (deptField == "F_EducationDepartment")
+ targetDeptId = target?.F_EducationDepartment?.ToString();
+ else if (deptField == "F_TechDepartment")
+ targetDeptId = target?.F_TechDepartment?.ToString();
+ else if (deptField == "F_MajorProjectDepartment")
+ targetDeptId = target?.F_MajorProjectDepartment?.ToString();
+
+ if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
+ {
+ departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
+ }
}
}
// 2.3.3 查询储扣金额(按品项类型)
+ // 使用储扣记录表中的开单时间(F_BillingTime)进行时间过滤,如果为空则使用开单记录表的开单时间(kdrq)
+ // 使用targetDict来关联,确保每个门店只关联一条目标记录,避免重复统计
var deductSql = $@"
SELECT
- target.{deptField} as TargetDeptId,
+ billing.djmd as StoreId,
COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount
FROM lq_kd_deductinfo deduct
INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
- INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}'
WHERE deduct.F_IsEffective = 1
AND billing.F_IsEffective = 1
AND item.F_IsEffective = 1
AND item.qt2 = '{itemType}'
AND billing.djmd IN ('{storeIdsStr}')
- AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
- AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
- GROUP BY target.{deptField}";
+ AND COALESCE(deduct.F_BillingTime, billing.kdrq) >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
+ AND COALESCE(deduct.F_BillingTime, billing.kdrq) < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
+ GROUP BY billing.djmd";
var deductData = await _db.Ado.SqlQueryAsync(deductSql);
- // 分配储扣金额到对应部门
+ // 分配储扣金额到对应部门(使用targetDict确保每个门店只关联一条目标记录)
foreach (var deduct in deductData ?? Enumerable.Empty())
{
var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m;
- var targetDeptId = deduct?.TargetDeptId?.ToString();
+ var storeId = deduct?.StoreId?.ToString();
- if (storeDeductAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
+ if (storeDeductAmount <= 0 || string.IsNullOrEmpty(storeId))
continue;
- // 只累加到在查询列表中的部门,避免重复统计
- if (departmentDict.ContainsKey(targetDeptId))
+ // 从targetDict中获取门店对应的部门信息
+ if (targetDict.ContainsKey(storeId))
{
- departmentDict[targetDeptId].DeductAmount += storeDeductAmount;
+ var target = targetDict[storeId];
+ // 动态获取部门字段值
+ string targetDeptId = null;
+ if (deptField == "F_EducationDepartment")
+ targetDeptId = target?.F_EducationDepartment?.ToString();
+ else if (deptField == "F_TechDepartment")
+ targetDeptId = target?.F_TechDepartment?.ToString();
+ else if (deptField == "F_MajorProjectDepartment")
+ targetDeptId = target?.F_MajorProjectDepartment?.ToString();
+
+ if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
+ {
+ departmentDict[targetDeptId].DeductAmount += storeDeductAmount;
+ }
}
}
}
@@ -1801,6 +1842,7 @@ namespace NCC.Extend
var (startDate, endDate) = GetTimeRange(input.StartTime, input.EndTime);
// 统计储扣金额,按品项分类分组
+ // 使用储扣记录表中的开单时间(F_BillingTime)进行时间过滤,如果为空则使用开单记录表的开单时间(kdrq)
var sql = $@"
SELECT
COALESCE(SUM(CASE WHEN item.qt2 = '医美' THEN deduct.F_Amount ELSE 0 END), 0) as YiMeiAmount,
@@ -1813,8 +1855,8 @@ namespace NCC.Extend
LEFT JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id AND item.F_IsEffective = 1
WHERE deduct.F_IsEffective = 1
AND billing.F_IsEffective = 1
- AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00'
- AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'";
+ AND COALESCE(deduct.F_BillingTime, billing.kdrq) >= '{startDate:yyyy-MM-dd} 00:00:00'
+ AND COALESCE(deduct.F_BillingTime, billing.kdrq) < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'";
var result = await _db.Ado.SqlQueryAsync(sql);
var data = result.FirstOrDefault();
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
index f136964..446ba3b 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
@@ -296,6 +296,7 @@ namespace NCC.Extend
.ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ExpenseAmount ?? 0));
// 1.12 洗毛巾费用统计(只统计送出的记录,F_FlowType = 0)
+ // 优先使用送出时间(F_SendTime),如果为空则使用创建时间(F_CreateTime)
var laundryCostSql = $@"
SELECT
F_StoreId as StoreId,
@@ -303,7 +304,7 @@ namespace NCC.Extend
FROM lq_laundry_flow
WHERE F_IsEffective = 1
AND F_FlowType = 0
- AND DATE_FORMAT(F_CreateTime, '%Y%m') = @monthStr
+ AND DATE_FORMAT(COALESCE(F_SendTime, F_CreateTime), '%Y%m') = @monthStr
GROUP BY F_StoreId";
var laundryCostData = await _db.Ado.SqlQueryAsync(laundryCostSql, new { monthStr });
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
index bf5d3d2..c1e24fd 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
@@ -186,8 +186,8 @@ namespace NCC.Extend
_db.Ado.BeginTran();
try
{
- var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync();
- if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
+ var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync();
+ if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
// 计算并更新产品的平均单价(加权平均成本法)
// 普通入库和采购入库都需要更新平均单价
@@ -398,8 +398,8 @@ namespace NCC.Extend
_db.Ado.BeginTran();
try
{
- var isOk = await _db.Updateable(existingInventory).ExecuteCommandAsync();
- if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
+ var isOk = await _db.Updateable(existingInventory).ExecuteCommandAsync();
+ if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
// 如果数量或单价发生变化,需要重新计算平均单价
// 注意:更新库存时,如果数量或单价变化,需要重新计算整个产品的平均单价
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
index 6f7d9d1..ebc1b01 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
@@ -503,6 +503,7 @@ namespace NCC.Extend
productName = product.ProductName,
productCategory = product.ProductCategory,
productPrice = product.Price,
+ productWarehouse = product.Warehouse, // 产品归属仓库
storeId = u.StoreId,
storeName = SqlFunc.Subqueryable().Where(store => store.Id == u.StoreId).Select(store => store.Dm),
usageTime = u.UsageTime,
@@ -648,6 +649,7 @@ namespace NCC.Extend
productName = product.ProductName,
productCategory = product.ProductCategory,
productPrice = product.Price,
+ productWarehouse = product.Warehouse, // 产品归属仓库
storeId = u.StoreId,
storeName = SqlFunc.Subqueryable().Where(store => store.Id == u.StoreId).Select(store => store.Dm),
usageTime = u.UsageTime,
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
index b4733cf..241d593 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -897,6 +897,7 @@ namespace NCC.Extend.LqKdKdjlb
{
Id = YitIdHelper.NextId().ToString(),
BillingId = newEntity.Id,
+ BillingTime = newEntity.Kdrq, // 设置开单时间
DeductId = item.DeductId,
DeductType = item.DeductType,
Amount = item.Amount,
@@ -1243,6 +1244,7 @@ namespace NCC.Extend.LqKdKdjlb
{
Id = YitIdHelper.NextId().ToString(),
BillingId = id,
+ BillingTime = entity.Kdrq, // 设置开单时间
DeductId = item.DeductId,
DeductType = item.DeductType,
Amount = item.Amount,
@@ -1252,7 +1254,7 @@ namespace NCC.Extend.LqKdKdjlb
ItemId = item.ItemId,
IsEffective = StatusEnum.有效.GetHashCode(), // 设置为有效
CreateTime = DateTime.Now, // 设置创建时间
- ItemCategory = await _db.Queryable().Where(x => x.Id == item.DeductId).Select(x => x.Qt2).FirstAsync()
+ ItemCategory = await _db.Queryable().Where(x => x.Id == item.ItemId).Select(x => x.Qt2).FirstAsync() // 修复:使用 ItemId 而不是 DeductId
};
allDeductEntities.Add(lqKdDeductEntity);
}
@@ -2292,6 +2294,7 @@ namespace NCC.Extend.LqKdKdjlb
Sfyj = input.Sfyj,
DeductAmount = input.DeductAmount,
Qk = input.Qk,
+ Bz = input.Remark,
UpdateTime = DateTime.Now
}).Where(w => w.Id == input.BillingId).ExecuteCommandAsync();
@@ -3791,11 +3794,19 @@ namespace NCC.Extend.LqKdKdjlb
}
// 批量查询开单记录,获取门店ID
+ // 批量查询开单记录,获取门店ID及扩展信息
var billingStoreDict = new Dictionary();
+ var billingExtraInfoDict = new Dictionary();
+
if (billingIds.Any())
{
- var billings = await _db.Queryable().Where(x => billingIds.Contains(x.Id)).Select(x => new { x.Id, x.Djmd }).ToListAsync();
+ var billings = await _db.Queryable()
+ .Where(x => billingIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.Djmd, x.Hgjg, x.Fkyy })
+ .ToListAsync();
+
billingStoreDict = billings.ToDictionary(x => x.Id, x => x.Djmd ?? "");
+ billingExtraInfoDict = billings.ToDictionary(x => x.Id, x => (dynamic)new { Hgjg = x.Hgjg, Fkyy = x.Fkyy });
}
// 批量查询门店信息
@@ -3823,7 +3834,9 @@ namespace NCC.Extend.LqKdKdjlb
sourceType = pxmx.SourceType,
remark = pxmx.Remark,
storeId = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) ? billingStoreDict[pxmx.Glkdbh] : "",
- storeName = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) && !string.IsNullOrEmpty(billingStoreDict[pxmx.Glkdbh]) && storeDict.ContainsKey(billingStoreDict[pxmx.Glkdbh]) ? storeDict[billingStoreDict[pxmx.Glkdbh]] : ""
+ storeName = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) && !string.IsNullOrEmpty(billingStoreDict[pxmx.Glkdbh]) && storeDict.ContainsKey(billingStoreDict[pxmx.Glkdbh]) ? storeDict[billingStoreDict[pxmx.Glkdbh]] : "",
+ hgjg = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Hgjg : "",
+ fkyy = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Fkyy : ""
}).ToList();
// 6. 返回分页结果
@@ -3915,6 +3928,7 @@ namespace NCC.Extend.LqKdKdjlb
// 查询并分页,使用子查询获取开单类型
+ // 优先使用储扣记录表中的开单时间,如果为空则使用开单记录表中的开单时间
var data = await baseQuery.Select((deduct, billing, member, store) => new LqKdDeductinfoListOutput
{
Id = deduct.Id ?? "",
@@ -3929,13 +3943,13 @@ namespace NCC.Extend.LqKdKdjlb
CreateTime = deduct.CreateTime,
ProjectNumber = deduct.ProjectNumber,
ItemCategory = deduct.ItemCategory ?? "",
- BillingDate = billing.Kdrq,
+ BillingDate = deduct.BillingTime ?? billing.Kdrq, // 优先使用储扣记录表中的开单时间
MemberId = billing.Kdhy ?? "",
MemberName = member.Khmc ?? "",
MemberPhone = member.Sjh ?? "",
StoreId = billing.Djmd ?? "",
StoreName = store.Dm ?? "",
- TimePeriod = billing.Kdrq,
+ TimePeriod = deduct.BillingTime ?? billing.Kdrq, // 优先使用储扣记录表中的开单时间
BillingType = SqlFunc.Subqueryable()
.Where(pxmx => pxmx.Id == deduct.DeductId && pxmx.Px == deduct.ItemId)
.Select(pxmx => pxmx.SourceType),
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs
index fa8160e..a17ec7d 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs
@@ -115,7 +115,8 @@ namespace NCC.Extend
Remark = input.Remark,
IsEffective = StatusEnum.有效.GetHashCode(),
CreateUser = _userManager.UserId,
- CreateTime = DateTime.Now
+ CreateTime = DateTime.Now,
+ SendTime = DateTime.Now // 设置送出时间
};
var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
@@ -212,7 +213,8 @@ namespace NCC.Extend
Remark = input.Remark,
IsEffective = StatusEnum.有效.GetHashCode(),
CreateUser = _userManager.UserId,
- CreateTime = DateTime.Now
+ CreateTime = DateTime.Now,
+ ReturnTime = DateTime.Now // 设置送回时间
};
var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
@@ -228,6 +230,109 @@ namespace NCC.Extend
}
#endregion
+ #region 修改送出/送回记录
+ ///
+ /// 修改送出/送回记录
+ ///
+ ///
+ /// 修改清洗流水记录的数量、送出时间、送回时间和备注
+ ///
+ /// 示例请求:
+ /// ```json
+ /// {
+ /// "id": "记录ID",
+ /// "quantity": 95,
+ /// "sendTime": "2025-11-01 10:00:00",
+ /// "returnTime": "2025-11-05 15:00:00",
+ /// "remark": "修改备注"
+ /// }
+ /// ```
+ ///
+ /// 参数说明:
+ /// - id: 记录ID(必填)
+ /// - quantity: 数量(可选,修改时需重新计算总费用)
+ /// - sendTime: 送出时间(可选,仅流水类型为0时有效)
+ /// - returnTime: 送回时间(可选,仅流水类型为1时有效)
+ /// - remark: 备注(可选)
+ ///
+ /// 修改输入
+ /// 修改结果
+ /// 修改成功
+ /// 记录不存在或参数错误
+ /// 服务器错误
+ [HttpPost("Update")]
+ public async Task UpdateAsync([FromBody] LqLaundryFlowUpdateInput input)
+ {
+ try
+ {
+ // 查询记录是否存在
+ var entity = await _db.Queryable()
+ .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .FirstAsync();
+
+ if (entity == null)
+ {
+ throw NCCException.Oh("记录不存在或已失效");
+ }
+
+ // 更新数量(如果提供)
+ if (input.Quantity.HasValue)
+ {
+ entity.Quantity = input.Quantity.Value;
+ // 重新计算总费用
+ entity.TotalPrice = entity.Quantity * entity.LaundryPrice;
+ }
+
+ // 更新送出时间(仅流水类型为0时有效)
+ if (input.SendTime.HasValue)
+ {
+ if (entity.FlowType != 0)
+ {
+ throw NCCException.Oh("只有送出记录才能修改送出时间");
+ }
+ entity.SendTime = input.SendTime.Value;
+ }
+
+ // 更新送回时间(仅流水类型为1时有效)
+ if (input.ReturnTime.HasValue)
+ {
+ if (entity.FlowType != 1)
+ {
+ throw NCCException.Oh("只有送回记录才能修改送回时间");
+ }
+ entity.ReturnTime = input.ReturnTime.Value;
+ }
+
+ // 更新备注(如果提供)
+ if (input.Remark != null)
+ {
+ entity.Remark = input.Remark;
+ }
+
+ // 执行更新
+ var isOk = await _db.Updateable(entity)
+ .UpdateColumns(it => new
+ {
+ it.Quantity,
+ it.TotalPrice,
+ it.SendTime,
+ it.ReturnTime,
+ it.Remark
+ })
+ .ExecuteCommandAsync();
+
+ if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
+
+ return new { message = "修改成功", totalPrice = entity.TotalPrice };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "修改清洗流水记录失败");
+ throw NCCException.Oh($"修改失败:{ex.Message}");
+ }
+ }
+ #endregion
+
#region 获取清洗流水列表
///
/// 获取清洗流水列表
@@ -275,7 +380,9 @@ namespace NCC.Extend
isEffective = flow.IsEffective,
createUser = flow.CreateUser,
createUserName = "",
- createTime = flow.CreateTime
+ createTime = flow.CreateTime,
+ sendTime = flow.SendTime,
+ returnTime = flow.ReturnTime
})
.MergeTable()
.OrderBy(sidx + " " + sort)
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
index 7b51811..44858f6 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
@@ -1515,8 +1515,8 @@ namespace NCC.Extend.LqReimbursementApplication
// 查询本月已审核通过的报销申请
var applications = await _db.Queryable()
.Where(x => (x.ApprovalStatus ?? x.ApproveStatus) == "已通过")
- .Where(x => x.ApplicationTime.HasValue &&
- x.ApplicationTime.Value.Year == queryYear &&
+ .Where(x => x.ApplicationTime.HasValue &&
+ x.ApplicationTime.Value.Year == queryYear &&
x.ApplicationTime.Value.Month == int.Parse(queryMonth))
.ToListAsync();
@@ -1552,7 +1552,7 @@ namespace NCC.Extend.LqReimbursementApplication
foreach (var app in applications)
{
var appPurchaseRecords = purchaseRecords.Where(x => x.ApplicationId == app.Id).ToList();
-
+
if (appPurchaseRecords.Any())
{
// 每个购买记录作为一行
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
index 7e15db7..d59f08a 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
@@ -211,16 +211,31 @@ namespace NCC.Extend
throw new Exception($"第{i + 1}行:月份必须在1-12之间");
}
+ // 辅助方法:清理数值字符串(去除千分位分隔符、货币符号等)
+ Func CleanNumericString = (str) =>
+ {
+ if (string.IsNullOrWhiteSpace(str))
+ return "0";
+ // 去除常见的非数字字符(保留小数点和负号)
+ return str.Trim()
+ .Replace(",", "") // 去除千分位分隔符
+ .Replace(",", "") // 去除中文逗号
+ .Replace("¥", "") // 去除人民币符号
+ .Replace("$", "") // 去除美元符号
+ .Replace("元", "") // 去除"元"字
+ .Replace(" ", ""); // 去除空格
+ };
+
// 解析数值字段(允许为空,默认为0)
- decimal.TryParse(baseRewardPerformanceText, out decimal baseRewardPerformance);
- decimal.TryParse(cooperationRewardPerformanceText, out decimal cooperationRewardPerformance);
- decimal.TryParse(newCustomerPerformanceText, out decimal newCustomerPerformance);
- decimal.TryParse(newCustomerConversionRateText, out decimal newCustomerConversionRate);
- decimal.TryParse(upgradePerformanceText, out decimal upgradePerformance);
- decimal.TryParse(upgradeConversionRateText, out decimal upgradeConversionRate);
- decimal.TryParse(upgradeCustomerCountText, out decimal upgradeCustomerCount);
- decimal.TryParse(otherPerformanceAddText, out decimal otherPerformanceAdd);
- decimal.TryParse(otherPerformanceSubtractText, out decimal otherPerformanceSubtract);
+ decimal.TryParse(CleanNumericString(baseRewardPerformanceText), out decimal baseRewardPerformance);
+ decimal.TryParse(CleanNumericString(cooperationRewardPerformanceText), out decimal cooperationRewardPerformance);
+ decimal.TryParse(CleanNumericString(newCustomerPerformanceText), out decimal newCustomerPerformance);
+ decimal.TryParse(CleanNumericString(newCustomerConversionRateText), out decimal newCustomerConversionRate);
+ decimal.TryParse(CleanNumericString(upgradePerformanceText), out decimal upgradePerformance);
+ decimal.TryParse(CleanNumericString(upgradeConversionRateText), out decimal upgradeConversionRate);
+ decimal.TryParse(CleanNumericString(upgradeCustomerCountText), out decimal upgradeCustomerCount);
+ decimal.TryParse(CleanNumericString(otherPerformanceAddText), out decimal otherPerformanceAdd);
+ decimal.TryParse(CleanNumericString(otherPerformanceSubtractText), out decimal otherPerformanceSubtract);
var item = new SalaryExtraCalculationImportInput
{
@@ -411,7 +426,22 @@ namespace NCC.Extend
// 批量更新现有记录
if (entitiesToUpdate.Any())
{
- await _db.Updateable(entitiesToUpdate).ExecuteCommandAsync();
+ // 明确指定要更新的字段,确保所有字段都被更新(包括升单业绩)
+ // 注意:Updateable接收实体列表时,会自动根据主键更新,不需要Where条件
+ await _db.Updateable(entitiesToUpdate)
+ .UpdateColumns(it => new
+ {
+ it.BaseRewardPerformance,
+ it.CooperationRewardPerformance,
+ it.NewCustomerPerformance,
+ it.NewCustomerConversionRate,
+ it.UpgradePerformance,
+ it.UpgradeConversionRate,
+ it.UpgradeCustomerCount,
+ it.OtherPerformanceAdd,
+ it.OtherPerformanceSubtract
+ })
+ .ExecuteCommandAsync();
}
var result = new
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
index a68a498..6f412e9 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
@@ -623,8 +623,15 @@ namespace NCC.Extend
isNewStore);
// 4.2 提成计算
- // 业绩门槛: 战队成员个人总业绩 <= 6000 无提成
- if (!string.IsNullOrEmpty(salary.GoldTriangleId) && salary.TotalPerformance <= 6000)
+ // 业绩门槛: 战队成员个人总业绩 <= 6000 无提成 (需按日均计算)
+ // 规则:战队成员日均业绩 <= 6000 / 当月天数 -> 无提成
+ decimal memberThreshold = 6000m;
+ if (daysInMonth > 0 && salary.WorkingDays > 0)
+ {
+ memberThreshold = (6000m / daysInMonth) * salary.WorkingDays;
+ }
+
+ if (!string.IsNullOrEmpty(salary.GoldTriangleId) && salary.TotalPerformance < memberThreshold) // 修正为小于校验
{
salary.TotalCommission = 0;
salary.BasePerformanceCommission = 0;
@@ -644,13 +651,15 @@ namespace NCC.Extend
// 获取战队人数 (注意:这里应该是有效战队人数)
var teamMemberCount = employeeStats.Values.Count(x => x.GoldTriangleId == salary.GoldTriangleId);
// 注意:提成点按原始基础业绩计算,不是实际基础业绩
- commissionPoint = GetTeamCommissionPoint(teamMemberCount, salary.TeamPerformance);
+ // 战队成员不按日均考核提成点,只考核个人门槛
+ commissionPoint = GetTeamCommissionPoint(teamMemberCount, salary.TeamPerformance, daysInMonth, salary.WorkingDays);
}
else
{
// 单人 (或被剔除出战队)
// 注意:提成点按原始总业绩计算
- commissionPoint = GetTeamCommissionPoint(1, salary.TotalPerformance);
+ // 单人按日均考核提成点
+ commissionPoint = GetTeamCommissionPoint(1, salary.TotalPerformance, daysInMonth, salary.WorkingDays);
}
salary.CommissionPoint = commissionPoint;
@@ -697,6 +706,17 @@ namespace NCC.Extend
if (!string.IsNullOrEmpty(salary.EmployeeName) && salary.EmployeeName.Contains("T区"))
{
salary.StoreTZoneCommission = salary.StoreTotalPerformance * 0.05m * 0.05m;
+
+ // T区人员仅核算提成,其他项(底薪、手工、社保等)归零
+ salary.HealthCoachBaseSalary = 0;
+ salary.HandworkFee = 0;
+ salary.BasePerformanceCommission = 0;
+ salary.CooperationPerformanceCommission = 0;
+ salary.ConsultantCommission = 0;
+ salary.NewCustomerPerformanceCommission = 0;
+ salary.UpgradePerformanceCommission = 0;
+ salary.TotalSubsidy = 0;
+ salary.TotalDeduction = 0;
}
salary.TotalCommission = salary.BasePerformanceCommission
@@ -801,7 +821,7 @@ namespace NCC.Extend
///
/// 获取战队提成点
///
- private decimal GetTeamCommissionPoint(int memberCount, decimal teamPerformance)
+ private decimal GetTeamCommissionPoint(int memberCount, decimal teamPerformance, int daysInMonth, decimal workingDays)
{
if (memberCount >= 3)
{
@@ -820,10 +840,24 @@ namespace NCC.Extend
}
else // 1人
{
- if (teamPerformance >= 60000) return 0.06m;
- if (teamPerformance >= 40000) return 0.05m;
- if (teamPerformance >= 20000) return 0.04m;
- if (teamPerformance >= 10000) return 0.03m;
+ // 单人按照日均考核
+ decimal p1 = 60000m;
+ decimal p2 = 40000m;
+ decimal p3 = 20000m;
+ decimal p4 = 10000m;
+
+ if (daysInMonth > 0 && workingDays > 0)
+ {
+ p1 = (p1 / daysInMonth) * workingDays;
+ p2 = (p2 / daysInMonth) * workingDays;
+ p3 = (p3 / daysInMonth) * workingDays;
+ p4 = (p4 / daysInMonth) * workingDays;
+ }
+
+ if (teamPerformance >= p1) return 0.06m;
+ if (teamPerformance >= p2) return 0.05m;
+ if (teamPerformance >= p3) return 0.04m;
+ if (teamPerformance >= p4) return 0.03m;
}
return 0;
}
@@ -839,21 +873,23 @@ namespace NCC.Extend
// 注意:
// 1. "组员业绩"指除顾问外的其他成员业绩总和
- // 2. 只统计有效战队成员(考勤≥21天,未被剔除的成员)
+ // 2. 只统计有效战队成员(考勤≥20天,未被剔除的成员)
// 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X%
// 4. 新店顾问不考核消耗
+ // 5. 消耗达标:高级顾问整组消耗>=6万,普通顾问整组消耗>=4万
- // 使用传入的 teamMembers 计算总消耗,或者直接使用 TeamTotalConsumption (如果已计算)
- // 但为了保险起见,这里重新计算或使用已有的逻辑
- // 注意:CalculateConsultantCommission 方法签名未变,但逻辑需确认 teamConsumption 来源
- // 在调用此方法前,teamMembers 已经有了 Consumption 数据
+ // 使用传入的 teamMembers 计算总消耗
var teamConsumption = teamMembers.Sum(x => x.Consumption);
// 计算组员(非顾问)业绩总和
- // teamMembers 已经是过滤后的有效成员列表(GoldTriangleId 相同且未被剔除)
var memberPerformance = teamMembers.Where(x => x.Position != "顾问").Sum(x => x.TotalPerformance);
- // 高级顾问:业绩≥6万 且 组员业绩≥40% 且 (新店 或 消耗≥6万)
+ // 高级顾问:业绩≥6万 且 组员业绩≥40% 且 (新店(第1,2阶段) 或 消耗≥6万)
+ // 注意:isNewStore 仅代表是否为新店,具体免考核阶段需确认。假设新店前两个阶段免考核,第三阶段需考核。
+ // 这里暂且沿用 isNewStore 逻辑,如果需要更细粒度控制,应传入 NewStoreProtectionStage
+ // 如果 isNewStore 为 true,则默认免考核消耗(根据原需求描述:新店第3个阶段时,有金三角,但是不考核消耗)
+ // 用户最新指示:新店第3个阶段时,有金三角,但是不考核消耗,默认达标 -> 意味着只要是新店,不管阶段,都不考核消耗
+
if (teamPerformance >= 60000 && memberPerformance >= teamPerformance * 0.4m)
{
if (isNewStore || teamConsumption >= 60000)
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs
new file mode 100644
index 0000000..1addbd2
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs
@@ -0,0 +1,191 @@
+using Microsoft.AspNetCore.Mvc;
+using NCC.Common.Filter;
+using NCC.Dependency;
+using NCC.DynamicApiController;
+using NCC.Extend.Entitys.Dto.LqShareStatisticsHq;
+using NCC.Extend.Entitys.lq_share_statistics_hq;
+using NCC.Extend.Entitys.lq_kd_kdjlb;
+using NCC.Extend.Entitys.lq_hytk_hytk;
+using NCC.Extend.Entitys.lq_kd_pxmx;
+using NCC.Extend.Entitys.lq_contract_rent_detail;
+using SqlSugar;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Yitter.IdGenerator;
+
+namespace NCC.Extend
+{
+ ///
+ /// 总部股份统计服务
+ ///
+ [ApiDescriptionSettings(Tag = "总部股份统计服务", Name = "LqShareStatisticsHq", Order = 402)]
+ [Route("api/Extend/[controller]")]
+ public class LqShareStatisticsHqService : IDynamicApiController, ITransient
+ {
+ private readonly ISqlSugarClient _db;
+
+ public LqShareStatisticsHqService(ISqlSugarClient db)
+ {
+ _db = db;
+ }
+
+ ///
+ /// 生成总部股份统计数据
+ ///
+ /// 生成参数
+ /// 生成结果
+ [HttpPost("generate")]
+ public async Task GenerateStatistics([FromBody] ShareStatisticsHqGenerateInput input)
+ {
+ if (string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
+ {
+ return new { code = 500, msg = "统计月份格式错误,应为 YYYYMM" };
+ }
+
+ var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
+ var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
+ var startDate = new DateTime(year, month, 1);
+ var endDate = startDate.AddMonths(1).AddDays(-1);
+
+ // 检查是否已存在
+ var existing = await _db.Queryable()
+ .FirstAsync(x => x.StatisticsMonth == input.StatisticsMonth);
+
+ var entity = existing ?? new LqShareStatisticsHqEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ StatisticsMonth = input.StatisticsMonth,
+ IsEffective = 1,
+ CreateTime = DateTime.Now,
+ CreateUser = "System"
+ };
+
+ // 计算各项数据
+ await CalculateIncome(entity, startDate, endDate);
+ await CalculateCost(entity, startDate, endDate, input.StatisticsMonth);
+ CalculateProfit(entity);
+
+ entity.UpdateTime = DateTime.Now;
+ entity.UpdateUser = "System";
+
+ if (existing == null)
+ {
+ await _db.Insertable(entity).ExecuteCommandAsync();
+ return new { code = 200, msg = "生成成功", data = new { generated = true } };
+ }
+ else
+ {
+ await _db.Updateable(entity).ExecuteCommandAsync();
+ return new { code = 200, msg = "更新成功", data = new { updated = true } };
+ }
+ }
+
+ ///
+ /// 查询总部股份统计列表
+ ///
+ /// 查询参数
+ /// 统计列表
+ [HttpGet("list")]
+ public async Task GetList([FromQuery] ShareStatisticsHqQueryInput input)
+ {
+ var query = _db.Queryable()
+ .Where(x => x.IsEffective == 1);
+
+ if (!string.IsNullOrEmpty(input.StatisticsMonth))
+ {
+ query = query.Where(x => x.StatisticsMonth == input.StatisticsMonth);
+ }
+
+ var list = await query.OrderBy(x => x.StatisticsMonth, OrderByType.Desc)
+ .Select(x => new ShareStatisticsHqOutput
+ {
+ Id = x.Id,
+ StatisticsMonth = x.StatisticsMonth,
+ IncomeGeneral = x.IncomeGeneral,
+ IncomeTechDept = x.IncomeTechDept,
+ CostReimbursement = x.CostReimbursement,
+ CostLabor = x.CostLabor,
+ CostEducationRent = x.CostEducationRent,
+ CostWarehouseRent = x.CostWarehouseRent,
+ CostHQRent = x.CostHQRent,
+ OperationalProfit = x.OperationalProfit,
+ CreateTime = x.CreateTime,
+ UpdateTime = x.UpdateTime
+ })
+ .ToListAsync();
+
+ return new { code = 200, msg = "查询成功", data = list };
+ }
+
+ #region 私有计算方法
+
+ ///
+ /// 计算收入部分
+ ///
+ private async Task CalculateIncome(LqShareStatisticsHqEntity entity, DateTime startDate, DateTime endDate)
+ {
+ // 1. 收入-全部 = (所有门店的总开单实付业绩 - 所有门店的总实退金额) * 9%
+ var totalBilling = await _db.Queryable()
+ .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate && x.IsEffective == 1)
+ .SumAsync(x => x.Sfyj);
+
+ var totalRefund = await _db.Queryable()
+ .Where(x => x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
+ .SumAsync(x => x.Tkje ?? 0);
+
+ entity.IncomeGeneral = (totalBilling - totalRefund) * 0.09m;
+
+ // 2. 收入-科技部 = (总科美业绩 - 总科美退款) * 0.3 * 0.09
+ var kemeiPerformance = await _db.Queryable()
+ .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "科美")
+ .SumAsync(x => x.TotalPrice);
+
+ // TODO: 需要确认科美退款的统计方式
+ entity.IncomeTechDept = kemeiPerformance * 0.3m * 0.09m;
+ }
+
+ ///
+ /// 计算成本部分
+ ///
+ private async Task CalculateCost(LqShareStatisticsHqEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth)
+ {
+ // 1. 成本-报销 (TODO: 需要确认总部报销的判定方式)
+ entity.CostReimbursement = 0;
+
+ // 2. 成本-人工 (保留)
+ entity.CostLabor = 0;
+
+ // 3. 成本-教育部房租
+ // TODO: 需要确认如何识别教育部合同
+ entity.CostEducationRent = 0;
+
+ // 4. 成本-仓库房租
+ // TODO: 需要确认如何识别仓库合同
+ entity.CostWarehouseRent = 0;
+
+ // 5. 成本-总部房租
+ // TODO: 需要确认如何识别总部合同
+ entity.CostHQRent = 0;
+ }
+
+ ///
+ /// 计算利润
+ ///
+ private void CalculateProfit(LqShareStatisticsHqEntity entity)
+ {
+ // 总部运营利润 = 收入(门店9%) + 收入(科技部9%) - 成本(报销) - 成本(人工) - 成本(房租)
+ var totalIncome = entity.IncomeGeneral + entity.IncomeTechDept;
+ var totalCost = entity.CostReimbursement
+ + entity.CostLabor
+ + entity.CostEducationRent
+ + entity.CostWarehouseRent
+ + entity.CostHQRent;
+
+ entity.OperationalProfit = totalIncome - totalCost;
+ }
+
+ #endregion
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs
new file mode 100644
index 0000000..33c4540
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs
@@ -0,0 +1,574 @@
+using Microsoft.AspNetCore.Mvc;
+using NCC.Common.Filter;
+using NCC.Dependency;
+using NCC.DynamicApiController;
+using NCC.Extend.Entitys.Dto.LqShareStatisticsStore;
+using NCC.Extend.Entitys.lq_share_statistics_store;
+using NCC.Extend.Entitys.lq_xh_jksyj;
+using NCC.Extend.Entitys.lq_kd_kdjlb;
+using NCC.Extend.Entitys.lq_hytk_hytk;
+using NCC.Extend.Entitys.lq_hytk_jksyj;
+using NCC.Extend.Entitys.lq_inventory_usage;
+using NCC.Extend.Entitys.lq_laundry_flow;
+using NCC.Extend.Entitys.lq_salary_statistics;
+using NCC.Extend.Entitys.lq_assistant_salary_statistics;
+using NCC.Extend.Entitys.lq_director_salary_statistics;
+using NCC.Extend.Entitys.lq_store_manager_salary_statistics;
+using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics;
+using NCC.Extend.Entitys.lq_contract_rent_detail;
+using NCC.Extend.Entitys.lq_mdxx;
+using NCC.Extend.Entitys.lq_kd_pxmx;
+using NCC.Extend.Entitys.lq_product;
+using NCC.Extend.Entitys.lq_inventory_usage_application;
+using NCC.Extend.Entitys;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Yitter.IdGenerator;
+
+namespace NCC.Extend
+{
+ ///
+ /// 门店股份统计服务
+ ///
+ [ApiDescriptionSettings(Tag = "门店股份统计服务", Name = "LqShareStatisticsStore", Order = 400)]
+ [Route("api/Extend/[controller]")]
+ public class LqShareStatisticsStoreService : IDynamicApiController, ITransient
+ {
+ private readonly ISqlSugarClient _db;
+
+ public LqShareStatisticsStoreService(ISqlSugarClient db)
+ {
+ _db = db;
+ }
+
+ ///
+ /// 生成门店股份统计数据
+ ///
+ /// 生成参数
+ /// 生成结果
+ [HttpPost("generate")]
+ public async Task GenerateStatistics([FromBody] ShareStatisticsStoreGenerateInput input)
+ {
+ if (string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
+ {
+ return new { code = 500, msg = "统计月份格式错误,应为 YYYYMM" };
+ }
+
+ var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
+ var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
+ var startDate = new DateTime(year, month, 1);
+ var endDate = startDate.AddMonths(1).AddDays(-1);
+
+ // 获取门店列表
+ var storeQuery = _db.Queryable();
+ if (!string.IsNullOrEmpty(input.StoreId))
+ {
+ storeQuery = storeQuery.Where(x => x.Id == input.StoreId);
+ }
+ var stores = await storeQuery.ToListAsync();
+
+ if (stores.Count == 0)
+ {
+ return new { code = 500, msg = "未找到有效门店" };
+ }
+
+ var generatedCount = 0;
+ var updatedCount = 0;
+
+ foreach (var store in stores)
+ {
+ try
+ {
+ // 检查是否已存在
+ var existing = await _db.Queryable()
+ .FirstAsync(x => x.StoreId == store.Id && x.StatisticsMonth == input.StatisticsMonth);
+
+ var entity = existing ?? new LqShareStatisticsStoreEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ StoreId = store.Id,
+ StoreName = store.Dm,
+ StatisticsMonth = input.StatisticsMonth,
+ IsEffective = 1,
+ CreateTime = DateTime.Now,
+ CreateUser = "System"
+ };
+
+ // 计算各项数据
+ await CalculateIncome(entity, startDate, endDate);
+ await CalculateCost(entity, startDate, endDate);
+ await CalculateSalary(entity, input.StatisticsMonth);
+ await CalculateExpense(entity, startDate, endDate, input.StatisticsMonth);
+ CalculateProfit(entity);
+
+ entity.UpdateTime = DateTime.Now;
+ entity.UpdateUser = "System";
+
+ if (existing == null)
+ {
+ await _db.Insertable(entity).ExecuteCommandAsync();
+ generatedCount++;
+ }
+ else
+ {
+ await _db.Updateable(entity).ExecuteCommandAsync();
+ updatedCount++;
+ }
+ }
+ catch (Exception ex)
+ {
+ return new { code = 500, msg = $"处理门店 {store.Dm} 时出错: {ex.Message}, StackTrace: {ex.StackTrace}" };
+ }
+ }
+
+ return new
+ {
+ code = 200,
+ msg = "生成成功",
+ data = new
+ {
+ generatedCount,
+ updatedCount,
+ totalCount = stores.Count
+ }
+ };
+ }
+
+ ///
+ /// 查询门店股份统计列表
+ ///
+ /// 查询参数
+ /// 统计列表
+ [HttpGet("list")]
+ public async Task GetList([FromQuery] ShareStatisticsStoreQueryInput input)
+ {
+ var query = _db.Queryable()
+ .Where(x => x.IsEffective == 1);
+
+ if (!string.IsNullOrEmpty(input.StatisticsMonth))
+ {
+ query = query.Where(x => x.StatisticsMonth == input.StatisticsMonth);
+ }
+
+ if (!string.IsNullOrEmpty(input.StoreId))
+ {
+ query = query.Where(x => x.StoreId == input.StoreId);
+ }
+
+ if (!string.IsNullOrEmpty(input.StoreName))
+ {
+ query = query.Where(x => x.StoreName.Contains(input.StoreName));
+ }
+
+ var list = await query.OrderBy(x => x.StatisticsMonth, OrderByType.Desc)
+ .OrderBy(x => x.StoreName)
+ .Select(x => new ShareStatisticsStoreOutput
+ {
+ Id = x.Id,
+ StoreId = x.StoreId,
+ StoreName = x.StoreName,
+ StatisticsMonth = x.StatisticsMonth,
+ MainIncome = x.MainIncome,
+ ConsumeLifeBeauty = x.ConsumeLifeBeauty,
+ ConsumeTechBeauty = x.ConsumeTechBeauty,
+ ConsumeMedicalBeauty = x.ConsumeMedicalBeauty,
+ ConsumeCooperation = x.ConsumeCooperation,
+ ConsumeProduct = x.ConsumeProduct,
+ ConsumeOther = x.ConsumeOther,
+ OtherIncome = x.OtherIncome,
+ AdvanceReceipt = x.AdvanceReceipt,
+ Refund = x.Refund,
+ Receivables = x.Receivables,
+ BankDeposit = x.BankDeposit,
+ CostProduct = x.CostProduct,
+ CostFutian = x.CostFutian,
+ CostTowel = x.CostTowel,
+ CostTechDept = x.CostTechDept,
+ CostManagementFee = x.CostManagementFee,
+ CostCooperation = x.CostCooperation,
+ CostMajorProject = x.CostMajorProject,
+ CostOther = x.CostOther,
+ SalaryBaseHealthCoach = x.SalaryBaseHealthCoach,
+ SalaryBaseAssistant = x.SalaryBaseAssistant,
+ SalaryBaseDirector = x.SalaryBaseDirector,
+ SalaryBaseStoreManager = x.SalaryBaseStoreManager,
+ SalaryBaseGeneralManager = x.SalaryBaseGeneralManager,
+ SalaryCommissionHealthCoach = x.SalaryCommissionHealthCoach,
+ SalaryCommissionAssistant = x.SalaryCommissionAssistant,
+ SalaryCommissionDirector = x.SalaryCommissionDirector,
+ SalaryCommissionStoreManager = x.SalaryCommissionStoreManager,
+ SalaryCommissionGeneralManager = x.SalaryCommissionGeneralManager,
+ SalaryManual = x.SalaryManual,
+ SalaryAttendance = x.SalaryAttendance,
+ SalaryPhoneCustody = x.SalaryPhoneCustody,
+ SalaryHeadcountReward = x.SalaryHeadcountReward,
+ SalaryTZone = x.SalaryTZone,
+ SocialSecurity = x.SocialSecurity,
+ StoreRent = x.StoreRent,
+ DormRent = x.DormRent,
+ CurrentPeriodExpense = x.CurrentPeriodExpense,
+ CurrentPeriodExpenseQin = x.CurrentPeriodExpenseQin,
+ FinancialFee = x.FinancialFee,
+ RewardMedicalBeauty = x.RewardMedicalBeauty,
+ RewardOther = x.RewardOther,
+ RewardLargeOrder = x.RewardLargeOrder,
+ RewardFirstOrder = x.RewardFirstOrder,
+ RewardGeneral = x.RewardGeneral,
+ Other1 = x.Other1,
+ Other2 = x.Other2,
+ Profit = x.Profit,
+ FinancialProfit = x.FinancialProfit,
+ CreateTime = x.CreateTime,
+ UpdateTime = x.UpdateTime
+ })
+ .ToListAsync();
+
+ return new { code = 200, msg = "查询成功", data = list };
+ }
+
+ #region 私有计算方法
+
+ ///
+ /// 计算收入部分
+ ///
+ private async Task CalculateIncome(LqShareStatisticsStoreEntity entity, DateTime startDate, DateTime endDate)
+ {
+ // 1. 主营收入 = 消耗总业绩(不扣减退款),并按分类拆分
+ var consumePerformance = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .SumAsync(x => x.Jksyj);
+
+ entity.MainIncome = consumePerformance ?? 0;
+
+ // 分项消耗业绩
+ var consumeLifeBeauty = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "生美")
+ .SumAsync(x => x.Jksyj);
+
+ var consumeTechBeauty = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "科美")
+ .SumAsync(x => x.Jksyj);
+
+ var consumeMedicalBeauty = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "医美")
+ .SumAsync(x => x.Jksyj);
+
+ var consumeCooperation = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "合作")
+ .SumAsync(x => x.Jksyj);
+
+ var consumeProduct = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "产品")
+ .SumAsync(x => x.Jksyj);
+
+ var consumeOther = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "其他" || x.ItemCategory == null || x.ItemCategory == "")
+ .SumAsync(x => x.Jksyj);
+
+ entity.ConsumeLifeBeauty = consumeLifeBeauty ?? 0;
+ entity.ConsumeTechBeauty = consumeTechBeauty ?? 0;
+ entity.ConsumeMedicalBeauty = consumeMedicalBeauty ?? 0;
+ entity.ConsumeCooperation = consumeCooperation ?? 0;
+ entity.ConsumeProduct = consumeProduct ?? 0;
+ entity.ConsumeOther = consumeOther ?? 0;
+
+ // 2. 预收款 = 开单实付
+ entity.AdvanceReceipt = await _db.Queryable()
+ .Where(x => x.Djmd == entity.StoreId && x.Kdrq >= startDate && x.Kdrq <= endDate && x.IsEffective == 1)
+ .SumAsync(x => x.Sfyj);
+
+ // 3. 实际退款 = 退卡实退金额
+ entity.Refund = await _db.Queryable()
+ .Where(x => x.Md == entity.StoreId && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
+ .SumAsync(x => x.ActualRefundAmount ?? 0);
+
+ // 4. 应收 = 合作医院开单金额
+ entity.Receivables = await _db.Queryable()
+ .Where(x => x.Djmd == entity.StoreId && x.Kdrq >= startDate && x.Kdrq <= endDate
+ && x.IsEffective == 1 && !string.IsNullOrEmpty(x.Fkyy))
+ .SumAsync(x => x.Sfyj);
+
+ // 5. 银行存款 = 开单实付 - 应收
+ entity.BankDeposit = entity.AdvanceReceipt - entity.Receivables;
+
+ // 6. 其他收入 = 退款差额 > 0 的部分(按照退款单应退金额-实退金额)
+ // 退款差额 = tkje - ActualRefundAmount
+ var positiveRefundDiff = await _db.Queryable()
+ .Where(x => x.Md == entity.StoreId && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
+ .SumAsync(x => SqlFunc.IIF((x.Tkje - (x.ActualRefundAmount ?? 0)) > 0,
+ x.Tkje - (x.ActualRefundAmount ?? 0),
+ 0));
+
+ entity.OtherIncome = positiveRefundDiff ?? 0;
+ }
+
+ ///
+ /// 计算成本部分
+ ///
+ private async Task CalculateCost(LqShareStatisticsStoreEntity entity, DateTime startDate, DateTime endDate)
+ {
+ // 1. 产品成本 = 仓库领用金额
+ entity.CostProduct = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.UsageTime >= startDate && x.UsageTime <= endDate && x.IsEffective == 1)
+ .SumAsync(x => x.TotalAmount);
+
+ // 2. 福田成本 = 福田仓库领用(上一个月的成本)
+ // 读取产品归属仓库为“福田仓库”的库存领用记录,
+ // 并且仅统计已审批通过且已领取的使用申请(领用完成后才纳入统计)
+ var prevMonthStart = startDate.AddMonths(-1);
+ var prevMonthEnd = startDate.AddDays(-1);
+
+ entity.CostFutian = await _db.Queryable((u, p, a) => new JoinQueryInfos(
+ JoinType.Inner, u.ProductId == p.Id,
+ JoinType.Inner, u.UsageBatchId == a.UsageBatchId))
+ .Where((u, p, a) => p.Warehouse == "福田仓库"
+ && u.StoreId == entity.StoreId
+ && u.UsageTime >= prevMonthStart && u.UsageTime <= prevMonthEnd
+ && u.IsEffective == 1
+ && a.IsEffective == 1
+ && a.ApprovalStatus == "已通过"
+ && a.IsReceived == 1)
+ .SumAsync((u, p, a) => u.TotalAmount);
+
+ // 3. 毛巾成本 = 洗毛巾费用
+ entity.CostTowel = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId
+ && x.SendTime >= startDate && x.SendTime <= endDate
+ && x.IsEffective == 1)
+ .SumAsync(x => x.TotalPrice);
+
+ // 4. 科技部成本 = 科美业绩 * 30%
+ var kemeiPerformance = await _db.Queryable()
+ .Where(x => x.Glkdbh.StartsWith(entity.StoreId) && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "科美")
+ .SumAsync(x => x.TotalPrice);
+
+ entity.CostTechDept = kemeiPerformance * 0.3m;
+
+ // 5. 管理费 = 总业绩 * 9%
+ var totalPerformance = await _db.Queryable()
+ .Where(x => x.Djmd == entity.StoreId && x.Kdrq >= startDate && x.Kdrq <= endDate && x.IsEffective == 1)
+ .SumAsync(x => x.Sfyj);
+
+ entity.CostManagementFee = totalPerformance * 0.09m;
+
+ // 6. 其他成本 = 退款差额 < 0 的部分(按照退款单应退金额-实退金额)
+ var negativeRefundDiffAbs = await _db.Queryable()
+ .Where(x => x.Md == entity.StoreId && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
+ .SumAsync(x => SqlFunc.IIF((x.Tkje - (x.ActualRefundAmount ?? 0)) < 0,
+ (x.ActualRefundAmount ?? 0) - x.Tkje,
+ 0));
+
+ entity.CostOther = negativeRefundDiffAbs ?? 0;
+
+ // 保留字段暂时为0
+ entity.CostCooperation = 0;
+ entity.CostMajorProject = 0;
+ }
+
+ ///
+ /// 计算人工工资部分
+ ///
+ private async Task CalculateSalary(LqShareStatisticsStoreEntity entity, string statisticsMonth)
+ {
+ // 1. 健康师底薪和提成
+ var healthCoachSalary = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.StatisticsMonth == statisticsMonth)
+ .Select(x => new
+ {
+ BaseSalary = SqlFunc.AggregateSum(x.HealthCoachBaseSalary),
+ Commission = SqlFunc.AggregateSum(x.TotalCommission),
+ Manual = SqlFunc.AggregateSum(x.HandworkFee),
+ TZone = SqlFunc.AggregateSum(x.StoreTZoneCommission)
+ })
+ .FirstAsync();
+
+ entity.SalaryBaseHealthCoach = healthCoachSalary?.BaseSalary ?? 0;
+ entity.SalaryCommissionHealthCoach = healthCoachSalary?.Commission ?? 0;
+ entity.SalaryManual = healthCoachSalary?.Manual ?? 0;
+ entity.SalaryTZone = healthCoachSalary?.TZone ?? 0;
+
+ // 2. 店助底薪和提成
+ var assistantSalary = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.StatisticsMonth == statisticsMonth && x.Position == "店助")
+ .Select(x => new
+ {
+ BaseSalary = SqlFunc.AggregateSum(x.BaseSalary),
+ Commission = SqlFunc.AggregateSum(x.CommissionAmount),
+ PhoneCustody = SqlFunc.AggregateSum(x.PhoneManagementFee),
+ HeadcountReward = SqlFunc.AggregateSum(x.Stage1Reward + x.Stage2Reward)
+ })
+ .FirstAsync();
+
+ entity.SalaryBaseAssistant = assistantSalary?.BaseSalary ?? 0;
+ entity.SalaryCommissionAssistant = assistantSalary?.Commission ?? 0;
+ entity.SalaryPhoneCustody = assistantSalary?.PhoneCustody ?? 0;
+ entity.SalaryHeadcountReward = assistantSalary?.HeadcountReward ?? 0;
+
+ // 3. 店助主任底薪和提成
+ var directorSalary = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.StatisticsMonth == statisticsMonth && x.Position == "店助主任")
+ .Select(x => new
+ {
+ BaseSalary = SqlFunc.AggregateSum(x.BaseSalary),
+ Commission = SqlFunc.AggregateSum(x.CommissionAmount),
+ PhoneCustody = SqlFunc.AggregateSum(x.PhoneManagementFee),
+ HeadcountReward = SqlFunc.AggregateSum(x.Stage1Reward + x.Stage2Reward)
+ })
+ .FirstAsync();
+
+ entity.SalaryBaseDirector = directorSalary?.BaseSalary ?? 0;
+ entity.SalaryCommissionDirector = directorSalary?.Commission ?? 0;
+
+ // 4. 店长底薪和提成
+ var storeManagerSalary = await _db.Queryable()
+ .Where(x => x.StoreId == entity.StoreId && x.StatisticsMonth == statisticsMonth)
+ .Select(x => new
+ {
+ BaseSalary = SqlFunc.AggregateSum(x.ActualBaseSalary),
+ Commission = SqlFunc.AggregateSum(x.CommissionAmount)
+ })
+ .FirstAsync();
+
+ entity.SalaryBaseStoreManager = storeManagerSalary?.BaseSalary ?? 0;
+ entity.SalaryCommissionStoreManager = storeManagerSalary?.Commission ?? 0;
+
+ // 5. 总经理/经理底薪和提成
+ // 底薪按门店数平均分摊,提成需要从 F_StorePerformanceDetail JSON 中提取该门店的提成
+ var gmSalary = await _db.Queryable()
+ .Where(x => x.StatisticsMonth == statisticsMonth)
+ .Where(x => x.StorePerformanceDetail.Contains(entity.StoreId))
+ .ToListAsync();
+
+ decimal gmBaseSalary = 0;
+ decimal gmCommission = 0;
+
+ foreach (var gm in gmSalary)
+ {
+ // 底薪平均分摊
+ if (!string.IsNullOrEmpty(gm.StorePerformanceDetail))
+ {
+ try
+ {
+ var storeDetails = Newtonsoft.Json.JsonConvert.DeserializeObject>>(gm.StorePerformanceDetail);
+ var storeCount = storeDetails?.Count ?? 1;
+ gmBaseSalary += gm.BaseSalary / storeCount;
+
+ // 提取该门店的提成
+ var storeDetail = storeDetails?.FirstOrDefault(s => s.ContainsKey("StoreId") && s["StoreId"].ToString() == entity.StoreId);
+ if (storeDetail != null && storeDetail.ContainsKey("Commission"))
+ {
+ gmCommission += Convert.ToDecimal(storeDetail["Commission"]);
+ }
+ }
+ catch
+ {
+ // JSON 解析失败,使用默认分摊
+ gmBaseSalary += gm.BaseSalary;
+ }
+ }
+ }
+
+ entity.SalaryBaseGeneralManager = gmBaseSalary;
+ entity.SalaryCommissionGeneralManager = gmCommission;
+
+ // 保留字段
+ entity.SalaryAttendance = 0;
+ }
+
+ ///
+ /// 计算费用部分
+ ///
+ private async Task CalculateExpense(LqShareStatisticsStoreEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth)
+ {
+ // 1. 门店房租 - 需要通过合同关联门店
+ // 从合同表关联租金明细
+ // 解析统计月份
+ var year = int.Parse(statisticsMonth.Substring(0, 4));
+ var month = int.Parse(statisticsMonth.Substring(4, 2));
+
+ var rentDetail = await _db.Queryable((rd, c) => new JoinQueryInfos(
+ JoinType.Inner, rd.ContractId == c.Id))
+ .Where((rd, c) => c.StoreId == entity.StoreId
+ && rd.PaymentMonth.Year == year
+ && rd.PaymentMonth.Month == month
+ && rd.IsEffective == 1)
+ .Select((rd, c) => rd.DueAmount)
+ .ToListAsync();
+
+ entity.StoreRent = rentDetail.Sum();
+
+ // 2. 当期费用 = 报销费用中一级分类为“当期费用”的已审核通过记录
+ // 只统计:报销申请审批通过 + 申请门店=当前门店 + 申请时间在当月
+ var currentPeriodExpense = await _db.Queryable((app, pr, cat) => new JoinQueryInfos(
+ JoinType.Inner, pr.ApplicationId == app.Id,
+ JoinType.Inner, pr.ReimbursementCategoryId == cat.Id))
+ .Where((app, pr, cat) => app.ApplicationStoreId == entity.StoreId
+ && app.ApplicationTime >= startDate && app.ApplicationTime <= endDate
+ && (app.ApprovalStatus ?? app.ApproveStatus) == "已通过"
+ && cat.Level1Name == "当期费用")
+ .SumAsync((app, pr, cat) => pr.Amount);
+
+ entity.CurrentPeriodExpense = currentPeriodExpense;
+
+ // 保留字段
+ entity.SocialSecurity = 0;
+ entity.DormRent = 0;
+ entity.CurrentPeriodExpenseQin = 0;
+ entity.FinancialFee = 0;
+ entity.RewardMedicalBeauty = 0;
+ entity.RewardOther = 0;
+ entity.RewardLargeOrder = 0;
+ entity.RewardFirstOrder = 0;
+ entity.RewardGeneral = 0;
+ entity.Other1 = 0;
+ entity.Other2 = 0;
+ }
+
+ ///
+ /// 计算利润
+ ///
+ private void CalculateProfit(LqShareStatisticsStoreEntity entity)
+ {
+ // 人工工资汇总
+ var totalSalary = entity.SalaryBaseHealthCoach + entity.SalaryBaseAssistant + entity.SalaryBaseDirector
+ + entity.SalaryBaseStoreManager + entity.SalaryBaseGeneralManager
+ + entity.SalaryCommissionHealthCoach + entity.SalaryCommissionAssistant + entity.SalaryCommissionDirector
+ + entity.SalaryCommissionStoreManager + entity.SalaryCommissionGeneralManager
+ + entity.SalaryManual + entity.SalaryAttendance + entity.SalaryPhoneCustody
+ + entity.SalaryHeadcountReward + entity.SalaryTZone;
+
+ // 主营成本汇总
+ var totalCost = entity.CostProduct + entity.CostFutian + entity.CostTowel + entity.CostTechDept
+ + entity.CostManagementFee + entity.CostCooperation + entity.CostMajorProject + entity.CostOther;
+
+ // 费用汇总
+ var totalExpense = entity.SocialSecurity + entity.StoreRent + entity.DormRent + entity.CurrentPeriodExpense
+ + entity.CurrentPeriodExpenseQin + entity.FinancialFee;
+
+ // 奖励汇总
+ var totalReward = entity.RewardMedicalBeauty + entity.RewardOther + entity.RewardLargeOrder
+ + entity.RewardFirstOrder + entity.RewardGeneral;
+
+ // 其他汇总
+ var totalOther = entity.Other1 + entity.Other2;
+
+ // 利润 = 预收 - 实退 - 主营成本 - 人工工资 - 房租 - 费用 - 奖励 - 其他
+ entity.Profit = entity.AdvanceReceipt - entity.Refund - totalCost - totalSalary - totalExpense - totalReward - totalOther;
+
+ // 财务利润 = 主营收入 + 其他收入 - 主营成本 - 人工工资 - 房租 - 费用 - 奖励 - 其他
+ entity.FinancialProfit = entity.MainIncome + entity.OtherIncome - totalCost - totalSalary - totalExpense - totalReward - totalOther;
+ }
+
+ #endregion
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs
new file mode 100644
index 0000000..974123a
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs
@@ -0,0 +1,262 @@
+using Microsoft.AspNetCore.Mvc;
+using NCC.Common.Filter;
+using NCC.Dependency;
+using NCC.DynamicApiController;
+using NCC.Extend.Entitys.Dto.LqShareStatisticsTechDept;
+using NCC.Extend.Entitys.lq_share_statistics_tech_dept;
+using NCC.Extend.Entitys.lq_mdxx;
+using NCC.Extend.Entitys.lq_kd_pxmx;
+using NCC.Extend.Entitys.lq_kd_kdjlb;
+using NCC.Extend.Entitys.lq_hytk_jksyj;
+using NCC.Extend.Entitys.lq_xh_jksyj;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Yitter.IdGenerator;
+
+namespace NCC.Extend
+{
+ ///
+ /// 科技部股份统计服务
+ ///
+ [ApiDescriptionSettings(Tag = "科技部股份统计服务", Name = "LqShareStatisticsTechDept", Order = 401)]
+ [Route("api/Extend/[controller]")]
+ public class LqShareStatisticsTechDeptService : IDynamicApiController, ITransient
+ {
+ private readonly ISqlSugarClient _db;
+
+ public LqShareStatisticsTechDeptService(ISqlSugarClient db)
+ {
+ _db = db;
+ }
+
+ ///
+ /// 生成科技部股份统计数据
+ ///
+ /// 生成参数
+ /// 生成结果
+ [HttpPost("generate")]
+ public async Task GenerateStatistics([FromBody] ShareStatisticsTechDeptGenerateInput input)
+ {
+ if (string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
+ {
+ return new { code = 500, msg = "统计月份格式错误,应为 YYYYMM" };
+ }
+
+ var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
+ var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
+ var startDate = new DateTime(year, month, 1);
+ var endDate = startDate.AddMonths(1).AddDays(-1);
+
+ // 确定要生成的部门列表
+ var departments = new List();
+ if (!string.IsNullOrEmpty(input.DepartmentName))
+ {
+ departments.Add(input.DepartmentName);
+ }
+ else
+ {
+ departments.Add("科技一部");
+ departments.Add("科技二部");
+ }
+
+ var generatedCount = 0;
+ var updatedCount = 0;
+
+ foreach (var deptName in departments)
+ {
+ // 检查是否已存在
+ var existing = await _db.Queryable()
+ .FirstAsync(x => x.DepartmentName == deptName && x.StatisticsMonth == input.StatisticsMonth);
+
+ var entity = existing ?? new LqShareStatisticsTechDeptEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ DepartmentName = deptName,
+ StatisticsMonth = input.StatisticsMonth,
+ IsEffective = 1,
+ CreateTime = DateTime.Now,
+ CreateUser = "System"
+ };
+
+ // 计算各项数据
+ await CalculateIncome(entity, deptName, startDate, endDate);
+ await CalculateCost(entity, deptName, startDate, endDate, input.StatisticsMonth);
+ CalculateProfit(entity);
+
+ entity.UpdateTime = DateTime.Now;
+ entity.UpdateUser = "System";
+
+ if (existing == null)
+ {
+ await _db.Insertable(entity).ExecuteCommandAsync();
+ generatedCount++;
+ }
+ else
+ {
+ await _db.Updateable(entity).ExecuteCommandAsync();
+ updatedCount++;
+ }
+ }
+
+ return new
+ {
+ code = 200,
+ msg = "生成成功",
+ data = new
+ {
+ generatedCount,
+ updatedCount,
+ totalCount = departments.Count
+ }
+ };
+ }
+
+ ///
+ /// 查询科技部股份统计列表
+ ///
+ /// 查询参数
+ /// 统计列表
+ [HttpGet("list")]
+ public async Task GetList([FromQuery] ShareStatisticsTechDeptQueryInput input)
+ {
+ var query = _db.Queryable()
+ .Where(x => x.IsEffective == 1);
+
+ if (!string.IsNullOrEmpty(input.StatisticsMonth))
+ {
+ query = query.Where(x => x.StatisticsMonth == input.StatisticsMonth);
+ }
+
+ if (!string.IsNullOrEmpty(input.DepartmentName))
+ {
+ query = query.Where(x => x.DepartmentName == input.DepartmentName);
+ }
+
+ var list = await query.OrderBy(x => x.StatisticsMonth, OrderByType.Desc)
+ .OrderBy(x => x.DepartmentName)
+ .Select(x => new ShareStatisticsTechDeptOutput
+ {
+ Id = x.Id,
+ DepartmentName = x.DepartmentName,
+ StatisticsMonth = x.StatisticsMonth,
+ Income = x.Income,
+ CostReimbursement = x.CostReimbursement,
+ CostTeacherBase = x.CostTeacherBase,
+ CostTeacherManual = x.CostTeacherManual,
+ CostTeacherBillingComm = x.CostTeacherBillingComm,
+ CostTeacherConsumeComm = x.CostTeacherConsumeComm,
+ CostTeacherExpertComm = x.CostTeacherExpertComm,
+ CostTeacherOvertime = x.CostTeacherOvertime,
+ CostGMBase = x.CostGMBase,
+ CostGMComm = x.CostGMComm,
+ RewardTechDept = x.RewardTechDept,
+ CostOther1 = x.CostOther1,
+ CostOther2 = x.CostOther2,
+ Profit = x.Profit,
+ CreateTime = x.CreateTime,
+ UpdateTime = x.UpdateTime
+ })
+ .ToListAsync();
+
+ return new { code = 200, msg = "查询成功", data = list };
+ }
+
+ #region 私有计算方法
+
+ ///
+ /// 计算收入部分
+ ///
+ private async Task CalculateIncome(LqShareStatisticsTechDeptEntity entity, string deptName, DateTime startDate, DateTime endDate)
+ {
+ // 1. 找到该科技部管辖的所有门店
+ var stores = await _db.Queryable()
+ .Where(x => x.Kjb == deptName)
+ .Select(x => x.Id)
+ .ToListAsync();
+
+ if (stores.Count == 0)
+ {
+ entity.Income = 0;
+ return;
+ }
+
+ // 2. 统计这些门店的科美项目开单实付业绩
+ // 需要关联 lq_kd_kdjlb 来获取门店信息
+ var kemeiIncome = await _db.Queryable((px, kd) => new JoinQueryInfos(
+ JoinType.Inner, px.Glkdbh == kd.Id))
+ .Where((px, kd) => stores.Contains(kd.Djmd) && px.Yjsj >= startDate && px.Yjsj <= endDate && px.IsEffective == 1)
+ .Where((px, kd) => px.ItemCategory == "科美")
+ .SumAsync((px, kd) => px.TotalPrice);
+
+ // 3. 减去对应的科美项目实退金额
+ var kemeiRefund = await _db.Queryable()
+ .Where(x => stores.Contains(x.StoreId) && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
+ .Where(x => x.ItemCategory == "科美")
+ .SumAsync(x => x.Jksyj ?? 0);
+
+ // 4. 结果 * 30%
+ entity.Income = (kemeiIncome - kemeiRefund) * 0.3m;
+ }
+
+ ///
+ /// 计算成本部分
+ ///
+ private async Task CalculateCost(LqShareStatisticsTechDeptEntity entity, string deptName, DateTime startDate, DateTime endDate, string statisticsMonth)
+ {
+ // 1. 成本-报销 (TODO: 需要确认报销分类的具体判定方式)
+ entity.CostReimbursement = 0;
+
+ // 2. 成本-人工-科技部老师底薪 (TODO: 需要确认科技部老师工资表名和字段)
+ entity.CostTeacherBase = 0;
+
+ // 3. 成本-人工-科技部手工费
+ // 从消耗表中统计科技部老师的手工费
+ // TODO: 需要确认如何识别科技部老师
+ entity.CostTeacherManual = 0;
+
+ // 4. 成本-人工-科技部开单提成 (TODO: 需要确认字段名)
+ entity.CostTeacherBillingComm = 0;
+
+ // 5. 成本-人工-科技部消耗提成 (TODO: 需要确认字段名)
+ entity.CostTeacherConsumeComm = 0;
+
+ // 6. 成本-人工-科技部总经理 (TODO: 需要确认科技部总经理工资表)
+ entity.CostGMBase = 0;
+ entity.CostGMComm = 0;
+
+ // 保留字段
+ entity.CostTeacherExpertComm = 0;
+ entity.CostTeacherOvertime = 0;
+ entity.RewardTechDept = 0;
+ entity.CostOther1 = 0;
+ entity.CostOther2 = 0;
+ }
+
+ ///
+ /// 计算利润
+ ///
+ private void CalculateProfit(LqShareStatisticsTechDeptEntity entity)
+ {
+ // 科技部利润 = 收入 - 成本报销 - 成本人工 - 其他
+ var totalCost = entity.CostReimbursement
+ + entity.CostTeacherBase
+ + entity.CostTeacherManual
+ + entity.CostTeacherBillingComm
+ + entity.CostTeacherConsumeComm
+ + entity.CostTeacherExpertComm
+ + entity.CostTeacherOvertime
+ + entity.CostGMBase
+ + entity.CostGMComm
+ + entity.RewardTechDept
+ + entity.CostOther1
+ + entity.CostOther2;
+
+ entity.Profit = entity.Income - totalCost;
+ }
+
+ #endregion
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs
index e9228df..29892be 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs
@@ -66,10 +66,36 @@ namespace NCC.Extend.LqStoreExpense
.Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
.FirstAsync();
_ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
- var output = entity.Adapt();
+ var output = new LqStoreExpenseInfoOutput
+ {
+ id = entity.Id,
+ storeId = entity.StoreId,
+ storeName = entity.StoreName,
+ expenseCategoryId = entity.ExpenseCategoryId,
+ expenseCategoryName = entity.ExpenseCategoryName,
+ expenseDate = entity.ExpenseDate,
+ unitPrice = entity.UnitPrice,
+ quantity = entity.Quantity,
+ amount = entity.Amount,
+ memo = entity.Memo,
+ relatedReimbursementId = entity.RelatedReimbursementId,
+ relatedPurchaseRecordId = entity.RelatedPurchaseRecordId,
+ createUser = entity.CreateUser,
+ createTime = entity.CreateTime,
+ updateUser = entity.UpdateUser,
+ updateTime = entity.UpdateTime
+ };
+
if (!string.IsNullOrEmpty(entity.Attachment))
{
- output.attachment = entity.Attachment.ToObject>();
+ try
+ {
+ output.attachment = entity.Attachment.ToObject>();
+ }
+ catch
+ {
+ output.attachment = new List();
+ }
}
return output;
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs
index e09be64..c2cd884 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs
@@ -294,6 +294,7 @@ namespace NCC.Extend
.ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ExpenseAmount ?? 0));
// 1.12 洗毛巾费用统计(只统计送出的记录,F_FlowType = 0)
+ // 优先使用送出时间(F_SendTime),如果为空则使用创建时间(F_CreateTime)
var laundryCostSql = $@"
SELECT
F_StoreId as StoreId,
@@ -301,7 +302,7 @@ namespace NCC.Extend
FROM lq_laundry_flow
WHERE F_IsEffective = 1
AND F_FlowType = 0
- AND DATE_FORMAT(F_CreateTime, '%Y%m') = @monthStr
+ AND DATE_FORMAT(COALESCE(F_SendTime, F_CreateTime), '%Y%m') = @monthStr
GROUP BY F_StoreId";
var laundryCostData = await _db.Ado.SqlQueryAsync(laundryCostSql, new { monthStr });
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqYcsdJsjService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqYcsdJsjService.cs
index 97773cc..bc24942 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqYcsdJsjService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqYcsdJsjService.cs
@@ -347,6 +347,18 @@ namespace NCC.Extend.LqYcsdJsj
}
}
+ // 验证成员 UserID 不能为空
+ if (input.members != null && input.members.Count > 0)
+ {
+ foreach (var member in input.members)
+ {
+ if (string.IsNullOrEmpty(member.userId))
+ {
+ throw NCCException.Oh(ErrorCode.COM1000, $"成员【{member.userName}】的 UserID 不能为空");
+ }
+ }
+ }
+
// 验证金三角名称是否已存在
var existingJsj = await _db.Queryable().Where(x => x.Yf == input.yf && x.Md == input.md && x.Jsj == input.jsj).FirstAsync();
if (existingJsj != null)
@@ -434,7 +446,7 @@ namespace NCC.Extend.LqYcsdJsj
if (string.IsNullOrEmpty(input.jsjId))
throw NCCException.Oh("金三角ID不能为空");
if (string.IsNullOrEmpty(input.userId))
- throw NCCException.Oh("用户ID不能为空");
+ throw NCCException.Oh("用户ID不能为空(请确保选择了有效的系统用户)");
if (string.IsNullOrEmpty(input.userName))
throw NCCException.Oh("用户姓名不能为空");
diff --git a/sql/主任工资表新增毛利相关字段.sql b/sql/主任工资表新增毛利相关字段.sql
index 84e6c8e..1cd594a 100644
--- a/sql/主任工资表新增毛利相关字段.sql
+++ b/sql/主任工资表新增毛利相关字段.sql
@@ -31,3 +31,4 @@ ADD COLUMN F_GrossProfit DECIMAL(18,2) DEFAULT 0.00 COMMENT '毛利(销售业
ALTER TABLE lq_director_salary_statistics
MODIFY COLUMN F_StoreTotalPerformance DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店总业绩(毛利,用于提成计算)';
+
diff --git a/sql/修复储扣表ItemCategory字段为空的数据.sql b/sql/修复储扣表ItemCategory字段为空的数据.sql
new file mode 100644
index 0000000..7058405
--- /dev/null
+++ b/sql/修复储扣表ItemCategory字段为空的数据.sql
@@ -0,0 +1,68 @@
+-- ============================================
+-- 修复储扣表(lq_kd_deductinfo)中 F_ItemCategory 字段为空的数据
+-- ============================================
+-- 说明:此脚本用于修复储扣表中品项分类字段为空的历史数据
+--
+-- 问题原因:
+-- 1. 更新开单时使用了错误的字段(item.DeductId 而不是 item.ItemId)来查询品项分类
+-- 2. 如果 item.ItemId 为空或无效,查询会返回 null
+--
+-- 修复逻辑:
+-- 通过 F_ItemId 关联 lq_xmzl 表,获取品项分类(qt2)并更新到 F_ItemCategory 字段
+--
+-- 注意事项:
+-- - 只更新有效记录(F_IsEffective = 1)
+-- - 只更新分类字段为空的记录
+-- - 只更新品项分类存在且不为空的记录
+
+-- ============================================
+-- 1. 查看需要修复的记录数
+-- ============================================
+SELECT
+ COUNT(*) as TotalNullCount,
+ COUNT(CASE WHEN item.qt2 IS NOT NULL THEN 1 END) as CanFixCount
+FROM lq_kd_deductinfo deduct
+LEFT JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
+WHERE deduct.F_IsEffective = 1
+ AND deduct.F_ItemCategory IS NULL;
+
+-- ============================================
+-- 2. 修复历史数据:从项目资料表中获取品项分类
+-- ============================================
+UPDATE lq_kd_deductinfo deduct
+INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
+SET deduct.F_ItemCategory = item.qt2
+WHERE deduct.F_IsEffective = 1
+ AND deduct.F_ItemCategory IS NULL
+ AND item.qt2 IS NOT NULL;
+
+-- ============================================
+-- 3. 验证修复结果
+-- ============================================
+-- 查看修复后的统计信息
+SELECT
+ COUNT(*) as TotalCount,
+ COUNT(F_ItemCategory) as HasCategoryCount,
+ COUNT(*) - COUNT(F_ItemCategory) as NullCategoryCount
+FROM lq_kd_deductinfo
+WHERE F_IsEffective = 1;
+
+-- 查看仍然为空的记录(需要人工处理)
+SELECT
+ deduct.F_Id,
+ deduct.F_BillingId,
+ deduct.F_ItemId,
+ deduct.F_ItemName,
+ deduct.F_ItemCategory,
+ item.qt2 as ItemQt2,
+ CASE
+ WHEN item.F_Id IS NULL THEN '品项不存在'
+ WHEN item.qt2 IS NULL THEN '品项分类为空'
+ ELSE '其他原因'
+ END as Reason
+FROM lq_kd_deductinfo deduct
+LEFT JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
+WHERE deduct.F_IsEffective = 1
+ AND deduct.F_ItemCategory IS NULL
+LIMIT 10;
+
diff --git a/sql/创建合同成本按月统计表.sql b/sql/创建合同成本按月统计表.sql
new file mode 100644
index 0000000..0edbd26
--- /dev/null
+++ b/sql/创建合同成本按月统计表.sql
@@ -0,0 +1,71 @@
+-- ============================================
+-- 创建合同成本按月统计表(lq_contract_monthly_cost)
+-- ============================================
+-- 说明:用于按月统计每个合同的成本,便于后续统计和查询
+--
+-- 业务逻辑:
+-- 1. 根据合同起始日期、结束日期,按月生成成本记录
+-- 2. 每个月成本 = 缴租金额 / 交租周期
+-- 3. 例如:合同一年,交费周期3个月,缴租金额3000元
+-- - 每个月成本 = 3000 / 3 = 1000元
+-- - 生成12个月的记录,每个月都是1000元
+--
+-- 字段说明:
+-- F_Month:统计月份(格式:YYYY-MM-01,表示该月的第一天)
+-- F_MonthlyCost:该月的合同成本(缴租金额 / 交租周期)
+--
+-- 索引设计:
+-- - 主键:F_Id
+-- - 合同ID索引:F_ContractId(用于查询某个合同的所有月份成本)
+-- - 月份索引:F_Month(用于按月份统计)
+-- - 门店ID索引:F_StoreId(用于按门店统计)
+-- - 联合索引:(F_StoreId, F_Month) - 用于查询某个门店某个月的成本
+-- - 联合索引:(F_ContractId, F_Month) - 用于查询某个合同某个月的成本
+
+CREATE TABLE IF NOT EXISTS `lq_contract_monthly_cost` (
+ `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID',
+ `F_ContractId` VARCHAR(50) NOT NULL COMMENT '合同ID(关联lq_contract.F_Id)',
+ `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id,冗余字段便于查询)',
+ `F_StoreName` VARCHAR(200) NOT NULL COMMENT '店名(冗余字段,便于查询)',
+ `F_Category` VARCHAR(100) DEFAULT NULL COMMENT '分类(冗余字段,便于按分类统计)',
+ `F_Month` DATETIME NOT NULL COMMENT '统计月份(格式:YYYY-MM-01,表示该月的第一天)',
+ `F_MonthlyCost` DECIMAL(18,2) NOT NULL COMMENT '该月的合同成本(缴租金额 / 交租周期)',
+ `F_PaymentCycle` INT NOT NULL COMMENT '交租周期(冗余字段,便于查询)',
+ `F_PaymentAmount` DECIMAL(18,2) NOT NULL COMMENT '缴租金额(冗余字段,便于查询)',
+ `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1-有效,0-无效)',
+ `F_CreateUser` VARCHAR(50) NOT NULL COMMENT '创建人ID',
+ `F_CreateTime` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `F_UpdateTime` DATETIME DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`F_Id`),
+ KEY `idx_contract_id` (`F_ContractId`) COMMENT '合同ID索引',
+ KEY `idx_month` (`F_Month`) COMMENT '月份索引',
+ KEY `idx_store_id` (`F_StoreId`) COMMENT '门店ID索引',
+ KEY `idx_category` (`F_Category`) COMMENT '分类索引',
+ KEY `idx_store_month` (`F_StoreId`, `F_Month`) COMMENT '门店+月份联合索引',
+ KEY `idx_contract_month` (`F_ContractId`, `F_Month`) COMMENT '合同+月份联合索引',
+ KEY `idx_category_month` (`F_Category`, `F_Month`) COMMENT '分类+月份联合索引',
+ KEY `idx_is_effective` (`F_IsEffective`) COMMENT '是否有效索引',
+ KEY `idx_create_time` (`F_CreateTime`) COMMENT '创建时间索引'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同成本按月统计表';
+
+-- ============================================
+-- 数据示例说明
+-- ============================================
+-- 合同示例:
+-- F_ContractId: '768041985045955845'
+-- F_StoreId: '1649328471923847168'
+-- F_StoreName: '绿纤总部'
+-- F_ContractStartDate: '2025-01-01 00:00:00'
+-- F_ContractEndDate: '2025-12-31 23:59:59'
+-- F_PaymentAmount: 3000.00
+-- F_PaymentCycle: 3
+--
+-- 每个月成本 = 3000 / 3 = 1000元
+--
+-- 对应的成本记录(自动生成):
+-- 记录1: F_Month='2025-01-01', F_MonthlyCost=1000.00
+-- 记录2: F_Month='2025-02-01', F_MonthlyCost=1000.00
+-- 记录3: F_Month='2025-03-01', F_MonthlyCost=1000.00
+-- 记录4: F_Month='2025-04-01', F_MonthlyCost=1000.00
+-- ...(共12条记录)
+
diff --git a/sql/合同成本表添加分类字段.sql b/sql/合同成本表添加分类字段.sql
new file mode 100644
index 0000000..8e26385
--- /dev/null
+++ b/sql/合同成本表添加分类字段.sql
@@ -0,0 +1,66 @@
+-- ============================================
+-- 为合同成本按月统计表(lq_contract_monthly_cost)添加分类字段
+-- ============================================
+-- 说明:此脚本为合同成本表添加分类字段,用于按分类统计合同成本
+--
+-- 字段说明:
+-- F_Category:分类(冗余字段,便于按分类统计)
+--
+-- 业务含义:
+-- - 分类字段用于区分不同类型的合同(如:租门店、员工宿舍、车辆、场所等)
+-- - 便于后续按分类统计合同成本
+--
+-- 注意事项:
+-- - 字段类型为VARCHAR(100),允许为NULL(历史数据可能没有分类)
+-- - 字段位置:放在 F_StoreName 字段之后
+-- - 创建后需要更新历史数据,从合同表中获取对应的分类
+
+-- ============================================
+-- 1. 添加分类字段
+-- ============================================
+ALTER TABLE `lq_contract_monthly_cost`
+ADD COLUMN `F_Category` VARCHAR(100) NULL COMMENT '分类(冗余字段,便于按分类统计)' AFTER `F_StoreName`;
+
+-- ============================================
+-- 2. 添加分类索引
+-- ============================================
+ALTER TABLE `lq_contract_monthly_cost`
+ADD INDEX `idx_category` (`F_Category`) COMMENT '分类索引';
+
+-- ============================================
+-- 3. 添加分类+月份联合索引
+-- ============================================
+ALTER TABLE `lq_contract_monthly_cost`
+ADD INDEX `idx_category_month` (`F_Category`, `F_Month`) COMMENT '分类+月份联合索引';
+
+-- ============================================
+-- 4. 更新历史数据:从合同表中获取分类
+-- ============================================
+UPDATE `lq_contract_monthly_cost` cost
+INNER JOIN `lq_contract` contract ON cost.F_ContractId = contract.F_Id
+SET cost.F_Category = contract.F_Category
+WHERE cost.F_Category IS NULL
+ AND contract.F_Category IS NOT NULL;
+
+-- ============================================
+-- 5. 验证更新结果
+-- ============================================
+-- 查看更新后的统计信息
+SELECT
+ COUNT(*) as TotalCount,
+ COUNT(F_Category) as HasCategoryCount,
+ COUNT(*) - COUNT(F_Category) as NullCategoryCount
+FROM lq_contract_monthly_cost
+WHERE F_IsEffective = 1;
+
+-- 查看各分类的统计信息
+SELECT
+ F_Category,
+ COUNT(*) as RecordCount,
+ SUM(F_MonthlyCost) as TotalCost
+FROM lq_contract_monthly_cost
+WHERE F_IsEffective = 1
+GROUP BY F_Category
+ORDER BY TotalCost DESC;
+
+
diff --git a/sql/开单扣减信息表添加开单时间字段.sql b/sql/开单扣减信息表添加开单时间字段.sql
new file mode 100644
index 0000000..9d480fe
--- /dev/null
+++ b/sql/开单扣减信息表添加开单时间字段.sql
@@ -0,0 +1,53 @@
+-- ============================================
+-- 为开单扣减信息表(lq_kd_deductinfo)添加开单时间字段
+-- ============================================
+-- 说明:此脚本为开单扣减信息表添加开单时间字段,用于存储对应的开单时间
+--
+-- 字段说明:
+-- F_BillingTime:开单时间,用于存储对应的开单记录的开单时间(kdrq)
+--
+-- 业务含义:
+-- - 开单时间用于记录储扣对应的开单时间,便于统计和查询
+-- - 开单时间来源于开单记录表(lq_kd_kdjlb)的 kdrq 字段
+--
+-- 注意事项:
+-- - 字段类型为DATETIME,允许为NULL(历史数据可能没有开单时间)
+-- - 字段位置:放在 F_BillingId 字段之后
+-- - 创建后需要更新历史数据,从开单记录表中获取对应的开单时间
+
+-- ============================================
+-- 1. 添加开单时间字段
+-- ============================================
+ALTER TABLE `lq_kd_deductinfo`
+ADD COLUMN `F_BillingTime` DATETIME NULL COMMENT '开单时间' AFTER `F_BillingId`;
+
+-- ============================================
+-- 2. 更新历史数据:从开单记录表中获取开单时间
+-- ============================================
+UPDATE `lq_kd_deductinfo` deduct
+INNER JOIN `lq_kd_kdjlb` billing ON deduct.F_BillingId = billing.F_Id
+SET deduct.F_BillingTime = billing.kdrq
+WHERE deduct.F_BillingTime IS NULL
+ AND billing.kdrq IS NOT NULL;
+
+-- ============================================
+-- 3. 验证更新结果
+-- ============================================
+-- 查看更新后的统计信息
+SELECT
+ COUNT(*) as TotalCount,
+ COUNT(F_BillingTime) as HasBillingTimeCount,
+ COUNT(*) - COUNT(F_BillingTime) as NullBillingTimeCount
+FROM lq_kd_deductinfo;
+
+-- 查看有开单时间但开单记录不存在的记录(数据异常检查)
+SELECT
+ deduct.F_Id,
+ deduct.F_BillingId,
+ deduct.F_BillingTime
+FROM lq_kd_deductinfo deduct
+LEFT JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
+WHERE deduct.F_BillingTime IS NOT NULL
+ AND billing.F_Id IS NULL
+LIMIT 10;
+
diff --git a/sql/排查生美业绩统计差异-简化版.sql b/sql/排查生美业绩统计差异-简化版.sql
new file mode 100644
index 0000000..20bf328
--- /dev/null
+++ b/sql/排查生美业绩统计差异-简化版.sql
@@ -0,0 +1,122 @@
+-- ============================================
+-- 排查生美业绩统计差异 - 简化版
+-- ============================================
+-- 问题:品项明细表统计生美数据是1869781.81,但日报天王团统计教育一部+教育二部合计是1876061.21,差异6279.40
+--
+-- 分析思路:
+-- 1. 检查是否有门店在lq_md_target表中有多条记录(同一月份)
+-- 2. 对比品项明细表统计和日报天王团统计的差异
+-- 3. 检查是否有数据被重复统计
+
+-- ============================================
+-- 1. 品项明细表统计生美业绩(所有门店,不限制部门归属)
+-- ============================================
+SELECT
+ '品项明细表统计' AS 统计来源,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH);
+
+-- ============================================
+-- 2. 日报天王团统计生美业绩(只统计有部门归属的门店,按部门分组)
+-- ============================================
+SELECT
+ '日报天王团统计' AS 统计来源,
+ target.F_EducationDepartment as 部门ID,
+ dept.F_FullName as 部门名称,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND target.F_EducationDepartment IS NOT NULL
+ AND target.F_EducationDepartment != ''
+GROUP BY target.F_EducationDepartment, dept.F_FullName;
+
+-- ============================================
+-- 3. 检查是否有门店在lq_md_target表中有多条记录(同一月份)
+-- ============================================
+SELECT
+ F_StoreId,
+ F_Month,
+ COUNT(*) as record_count
+FROM lq_md_target
+WHERE F_Month = DATE_FORMAT(NOW(), '%Y%m')
+ AND (F_EducationDepartment IS NOT NULL AND F_EducationDepartment != '')
+GROUP BY F_StoreId, F_Month
+HAVING COUNT(*) > 1;
+
+-- ============================================
+-- 4. 检查是否有生美品项的开单记录,但门店在lq_md_target表中没有设置F_EducationDepartment
+-- ============================================
+SELECT
+ '未归属门店的生美业绩' AS 统计来源,
+ COUNT(DISTINCT billing.djmd) as 门店数量,
+ COUNT(*) as 开单记录数,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+LEFT JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND (target.F_StoreId IS NULL
+ OR target.F_EducationDepartment IS NULL
+ OR target.F_EducationDepartment = '');
+
+-- ============================================
+-- 5. 关键检查:查看每个门店的生美业绩,看看是否有重复统计
+-- ============================================
+SELECT
+ billing.djmd as 门店ID,
+ md.Dm as 门店名称,
+ target.F_EducationDepartment as 教育部门ID,
+ dept.F_FullName as 教育部门名称,
+ COUNT(*) as 开单记录数,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩,
+ COUNT(DISTINCT target.F_Id) as 目标表记录数
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+LEFT JOIN lq_mdxx md ON billing.djmd = md.F_Id
+LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND target.F_EducationDepartment IS NOT NULL
+ AND target.F_EducationDepartment != ''
+GROUP BY billing.djmd, md.Dm, target.F_EducationDepartment, dept.F_FullName
+HAVING COUNT(DISTINCT target.F_Id) > 1
+ORDER BY 生美业绩 DESC;
+
+
+
+
+
+
+
diff --git a/sql/排查生美业绩统计差异详细.sql b/sql/排查生美业绩统计差异详细.sql
new file mode 100644
index 0000000..6039721
--- /dev/null
+++ b/sql/排查生美业绩统计差异详细.sql
@@ -0,0 +1,174 @@
+-- ============================================
+-- 排查生美业绩统计差异详细分析
+-- ============================================
+-- 问题:品项明细表统计生美数据是1869781.81,但日报天王团统计教育一部+教育二部合计是1876061.21,差异6279.40
+--
+-- 分析思路:
+-- 1. 检查是否有门店在lq_md_target表中有多条记录(同一月份)
+-- 2. 对比品项明细表统计和日报天王团统计的差异
+-- 3. 检查是否有数据被重复统计
+
+-- ============================================
+-- 1. 检查lq_md_target表中是否有重复记录(同一门店同一月份多条记录)
+-- ============================================
+SELECT
+ F_StoreId,
+ F_Month,
+ COUNT(*) as record_count,
+ GROUP_CONCAT(DISTINCT F_EducationDepartment ORDER BY F_EducationDepartment) as education_depts
+FROM lq_md_target
+WHERE F_Month = DATE_FORMAT(NOW(), '%Y%m')
+ AND (F_EducationDepartment IS NOT NULL AND F_EducationDepartment != '')
+GROUP BY F_StoreId, F_Month
+HAVING COUNT(*) > 1;
+
+-- ============================================
+-- 2. 品项明细表统计生美业绩(所有门店,不限制部门归属)
+-- ============================================
+SELECT
+ '品项明细表统计' AS 统计来源,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH);
+
+-- ============================================
+-- 3. 日报天王团统计生美业绩(只统计有部门归属的门店,按部门分组)
+-- ============================================
+SELECT
+ '日报天王团统计' AS 统计来源,
+ target.F_EducationDepartment as 部门ID,
+ dept.F_FullName as 部门名称,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND target.F_EducationDepartment IS NOT NULL
+ AND target.F_EducationDepartment != ''
+GROUP BY target.F_EducationDepartment, dept.F_FullName;
+
+-- ============================================
+-- 4. 检查是否有门店在lq_md_target表中有多条记录,导致重复统计
+-- ============================================
+SELECT
+ billing.djmd as 门店ID,
+ md.Dm as 门店名称,
+ COUNT(DISTINCT target.F_Id) as 目标表记录数,
+ COUNT(*) as 开单记录数,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额,
+ GROUP_CONCAT(DISTINCT target.F_EducationDepartment ORDER BY target.F_EducationDepartment) as 教育部门列表
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+LEFT JOIN lq_mdxx md ON billing.djmd = md.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND target.F_EducationDepartment IS NOT NULL
+ AND target.F_EducationDepartment != ''
+GROUP BY billing.djmd, md.Dm
+HAVING COUNT(DISTINCT target.F_Id) > 1;
+
+-- ============================================
+-- 5. 检查是否有生美品项的开单记录,但门店在lq_md_target表中没有设置F_EducationDepartment
+-- ============================================
+SELECT
+ '未归属门店的生美业绩' AS 统计来源,
+ COUNT(DISTINCT billing.djmd) as 门店数量,
+ COUNT(*) as 开单记录数,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+LEFT JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND (target.F_StoreId IS NULL
+ OR target.F_EducationDepartment IS NULL
+ OR target.F_EducationDepartment = '');
+
+-- ============================================
+-- 6. 关键检查:查看每个门店的生美业绩,看看是否有重复统计
+-- ============================================
+SELECT
+ billing.djmd as 门店ID,
+ md.Dm as 门店名称,
+ target.F_EducationDepartment as 教育部门ID,
+ dept.F_FullName as 教育部门名称,
+ COUNT(*) as 开单记录数,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩,
+ COUNT(DISTINCT target.F_Id) as 目标表记录数
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+LEFT JOIN lq_mdxx md ON billing.djmd = md.F_Id
+LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND target.F_EducationDepartment IS NOT NULL
+ AND target.F_EducationDepartment != ''
+GROUP BY billing.djmd, md.Dm, target.F_EducationDepartment, dept.F_FullName
+ORDER BY 生美业绩 DESC;
+
+-- ============================================
+-- 7. 检查是否有门店在lq_md_target表中有多条记录(不同月份,但查询时可能有问题)
+-- ============================================
+SELECT
+ F_StoreId,
+ COUNT(DISTINCT F_Month) as 月份数,
+ GROUP_CONCAT(DISTINCT F_Month ORDER BY F_Month) as 月份列表,
+ COUNT(*) as 总记录数
+FROM lq_md_target
+WHERE F_StoreId IN (
+ SELECT DISTINCT billing.djmd
+ FROM lq_kd_pxmx pxmx
+ INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+ INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+ WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+)
+ AND (F_EducationDepartment IS NOT NULL AND F_EducationDepartment != '')
+GROUP BY F_StoreId
+HAVING COUNT(*) > 1;
+
+
+
+
+
+
+
diff --git a/sql/检查生美业绩统计差异.sql b/sql/检查生美业绩统计差异.sql
new file mode 100644
index 0000000..40ef5d1
--- /dev/null
+++ b/sql/检查生美业绩统计差异.sql
@@ -0,0 +1,144 @@
+-- ============================================
+-- 检查生美业绩统计差异
+-- ============================================
+-- 问题:品项明细表统计生美数据是1869781.81,但日报天王团统计教育一部+教育二部合计是1876061.21,差异6279.40
+--
+-- 可能原因:
+-- 1. 门店在lq_md_target表中有重复记录(同一月份多条记录)
+-- 2. 统计范围不一致(时间范围或门店范围)
+-- 3. 数据关联逻辑问题
+
+-- ============================================
+-- 1. 检查lq_md_target表中是否有重复记录(同一门店同一月份多条记录)
+-- ============================================
+SELECT
+ F_StoreId,
+ F_Month,
+ COUNT(*) as record_count,
+ GROUP_CONCAT(DISTINCT F_EducationDepartment) as education_depts
+FROM lq_md_target
+WHERE F_Month = DATE_FORMAT(NOW(), '%Y%m')
+ AND (F_EducationDepartment IS NOT NULL AND F_EducationDepartment != '')
+GROUP BY F_StoreId, F_Month
+HAVING COUNT(*) > 1;
+
+-- ============================================
+-- 2. 检查品项明细表统计生美业绩(所有门店,不限制部门归属)
+-- ============================================
+SELECT
+ '品项明细表统计' AS 统计来源,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH);
+
+-- ============================================
+-- 3. 检查日报天王团统计生美业绩(只统计有部门归属的门店,按部门分组)
+-- ============================================
+SELECT
+ '日报天王团统计' AS 统计来源,
+ target.F_EducationDepartment as 部门ID,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND target.F_EducationDepartment IS NOT NULL
+ AND target.F_EducationDepartment != ''
+GROUP BY target.F_EducationDepartment;
+
+-- ============================================
+-- 4. 检查是否有生美品项的开单记录,但门店在lq_md_target表中没有设置F_EducationDepartment
+-- ============================================
+SELECT
+ '未归属门店的生美业绩' AS 统计来源,
+ COUNT(DISTINCT billing.djmd) as 门店数量,
+ COUNT(*) as 开单记录数,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+LEFT JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND (target.F_StoreId IS NULL
+ OR target.F_EducationDepartment IS NULL
+ OR target.F_EducationDepartment = '');
+
+-- ============================================
+-- 5. 检查是否有门店在lq_md_target表中有多条记录(可能导致重复统计)
+-- ============================================
+SELECT
+ billing.djmd as 门店ID,
+ COUNT(DISTINCT target.F_Id) as 目标表记录数,
+ COUNT(*) as 开单记录数,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额,
+ GROUP_CONCAT(DISTINCT target.F_EducationDepartment) as 教育部门列表
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND target.F_EducationDepartment IS NOT NULL
+ AND target.F_EducationDepartment != ''
+GROUP BY billing.djmd
+HAVING COUNT(DISTINCT target.F_Id) > 1;
+
+-- ============================================
+-- 6. 详细检查:查看每个门店的生美业绩和部门归属情况
+-- ============================================
+SELECT
+ billing.djmd as 门店ID,
+ md.Dm as 门店名称,
+ target.F_EducationDepartment as 教育部门ID,
+ dept.F_FullName as 教育部门名称,
+ COUNT(*) as 开单记录数,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩
+FROM lq_kd_pxmx pxmx
+INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
+ AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
+LEFT JOIN lq_mdxx md ON billing.djmd = md.F_Id
+LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
+WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '生美'
+ AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
+ AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
+ AND target.F_EducationDepartment IS NOT NULL
+ AND target.F_EducationDepartment != ''
+GROUP BY billing.djmd, md.Dm, target.F_EducationDepartment, dept.F_FullName
+ORDER BY 生美业绩 DESC;
+
+
+
+
+
+
+
diff --git a/sql/清洗流水表添加送出送回时间字段.sql b/sql/清洗流水表添加送出送回时间字段.sql
new file mode 100644
index 0000000..a7f1ba4
--- /dev/null
+++ b/sql/清洗流水表添加送出送回时间字段.sql
@@ -0,0 +1,63 @@
+-- ============================================
+-- 为清洗流水表(lq_laundry_flow)添加送出/送回时间字段
+-- ============================================
+-- 说明:此脚本为清洗流水表添加送出时间和送回时间字段,用于记录实际的送出/送回时间
+--
+-- 字段说明:
+-- F_SendTime:送出时间,用于记录实际的送出时间(流水类型为0时使用)
+-- F_ReturnTime:送回时间,用于记录实际的送回时间(流水类型为1时使用)
+--
+-- 业务含义:
+-- - 送出时间:记录实际送出清洗的时间,便于后续统计
+-- - 送回时间:记录实际送回清洗的时间,便于后续统计
+-- - F_CreateTime:保持为记录创建时间(系统时间),用于记录数据录入时间
+--
+-- 注意事项:
+-- - 字段类型为DATETIME,允许为NULL(历史数据可能没有这些时间)
+-- - 字段位置:放在 F_CreateTime 字段之后
+-- - 创建后需要更新历史数据,将 F_CreateTime 的值复制到对应的时间字段
+
+-- ============================================
+-- 1. 添加送出时间字段
+-- ============================================
+ALTER TABLE `lq_laundry_flow`
+ADD COLUMN `F_SendTime` DATETIME NULL COMMENT '送出时间(流水类型为0时使用)' AFTER `F_CreateTime`;
+
+-- ============================================
+-- 2. 添加送回时间字段
+-- ============================================
+ALTER TABLE `lq_laundry_flow`
+ADD COLUMN `F_ReturnTime` DATETIME NULL COMMENT '送回时间(流水类型为1时使用)' AFTER `F_SendTime`;
+
+-- ============================================
+-- 3. 更新历史数据:将创建时间复制到对应的时间字段
+-- ============================================
+-- 更新送出记录:将创建时间复制到送出时间
+UPDATE `lq_laundry_flow`
+SET `F_SendTime` = `F_CreateTime`
+WHERE `F_FlowType` = 0
+ AND `F_SendTime` IS NULL
+ AND `F_CreateTime` IS NOT NULL;
+
+-- 更新送回记录:将创建时间复制到送回时间
+UPDATE `lq_laundry_flow`
+SET `F_ReturnTime` = `F_CreateTime`
+WHERE `F_FlowType` = 1
+ AND `F_ReturnTime` IS NULL
+ AND `F_CreateTime` IS NOT NULL;
+
+-- ============================================
+-- 4. 验证更新结果
+-- ============================================
+-- 查看更新后的统计信息
+SELECT
+ F_FlowType,
+ CASE WHEN F_FlowType = 0 THEN '送出' ELSE '送回' END as FlowTypeName,
+ COUNT(*) as TotalCount,
+ COUNT(CASE WHEN F_FlowType = 0 THEN F_SendTime END) as HasSendTimeCount,
+ COUNT(CASE WHEN F_FlowType = 1 THEN F_ReturnTime END) as HasReturnTimeCount
+FROM lq_laundry_flow
+WHERE F_IsEffective = 1
+GROUP BY F_FlowType;
+
+
diff --git a/test_tianwang_api.py b/test_tianwang_api.py
new file mode 100644
index 0000000..3b1e749
--- /dev/null
+++ b/test_tianwang_api.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import json
+import sys
+from datetime import datetime
+
+# 测试接口
+import subprocess
+
+# 测试2025年1月的数据
+cmd1 = [
+ 'curl', '-s', '-X', 'POST',
+ 'http://localhost:2011/api/Extend/LqDailyReport/get-tianwang-group-performance-completion',
+ '-H', 'Content-Type: application/json',
+ '-d', '{"startTime": "2025-01-01", "endTime": "2025-01-31"}'
+]
+
+result1 = subprocess.run(cmd1, capture_output=True, text=True)
+data1 = json.loads(result1.stdout)
+
+print("=" * 60)
+print("2025年1月数据")
+print("=" * 60)
+print(f"接口状态: {data1.get('code')}")
+print(f"返回消息: {data1.get('msg')}")
+
+depts1 = [d for d in data1.get('data', []) if '教育' in d.get('DepartmentName', '')]
+print("\n=== 教育部数据 ===")
+for d in depts1:
+ print(f"{d.get('DepartmentName')}:")
+ print(f" BillingPerformance: {d.get('BillingPerformance')}")
+ print(f" RefundPerformance: {d.get('RefundPerformance')}")
+ print(f" DeductAmount: {d.get('DeductAmount')}")
+ print(f" CompletedPerformance: {d.get('CompletedPerformance')}")
+ print(f" StoreCount: {d.get('StoreCount')}")
+
+total1 = sum([d.get('BillingPerformance', 0) for d in depts1])
+print(f"\n教育一部+教育二部合计 BillingPerformance: {total1}")
+
+# 测试当前月份的数据
+current_month = datetime.now().strftime("%Y-%m")
+start_date = f"{current_month}-01"
+end_date = datetime.now().strftime("%Y-%m-%d")
+
+cmd2 = [
+ 'curl', '-s', '-X', 'POST',
+ 'http://localhost:2011/api/Extend/LqDailyReport/get-tianwang-group-performance-completion',
+ '-H', 'Content-Type: application/json',
+ '-d', f'{{"startTime": "{start_date}", "endTime": "{end_date}"}}'
+]
+
+result2 = subprocess.run(cmd2, capture_output=True, text=True)
+data2 = json.loads(result2.stdout)
+
+print("\n" + "=" * 60)
+print(f"当前月份 ({current_month}) 数据")
+print("=" * 60)
+print(f"接口状态: {data2.get('code')}")
+
+depts2 = [d for d in data2.get('data', []) if '教育' in d.get('DepartmentName', '')]
+print("\n=== 教育部数据 ===")
+for d in depts2:
+ print(f"{d.get('DepartmentName')}:")
+ print(f" BillingPerformance: {d.get('BillingPerformance')}")
+ print(f" RefundPerformance: {d.get('RefundPerformance')}")
+ print(f" DeductAmount: {d.get('DeductAmount')}")
+ print(f" CompletedPerformance: {d.get('CompletedPerformance')}")
+ print(f" StoreCount: {d.get('StoreCount')}")
+
+total2 = sum([d.get('BillingPerformance', 0) for d in depts2])
+print(f"\n教育一部+教育二部合计 BillingPerformance: {total2}")
+
+
+
+
+
+
diff --git a/主任工资毛利计算逻辑梳理.md b/主任工资毛利计算逻辑梳理.md
index e90b3b1..997bd1e 100644
--- a/主任工资毛利计算逻辑梳理.md
+++ b/主任工资毛利计算逻辑梳理.md
@@ -300,3 +300,4 @@ ADD COLUMN F_GrossProfit DECIMAL(18,2) DEFAULT 0.00 COMMENT '毛利(销售业
4. **业绩达标判断**:基于毛利是否≥生命线
5. **提成计算**:基于毛利,不是基于销售业绩
+
diff --git a/储扣表ItemCategory字段为空问题梳理.md b/储扣表ItemCategory字段为空问题梳理.md
new file mode 100644
index 0000000..a46c3e2
--- /dev/null
+++ b/储扣表ItemCategory字段为空问题梳理.md
@@ -0,0 +1,76 @@
+# 储扣表 F_ItemCategory 字段为空问题梳理
+
+## 问题描述
+储扣表(lq_kd_deductinfo)中的 `F_ItemCategory` 字段有时候是空的。
+
+## 数据库统计
+- 总有效记录数:1865
+- 有分类字段的记录数:1863
+- 分类字段为空的记录数:2
+
+## 问题分析
+
+### 1. 创建开单时的逻辑(第910行)
+```csharp
+ItemCategory = await _db.Queryable()
+ .Where(x => x.Id == item.ItemId)
+ .Select(x => x.Qt2)
+ .FirstAsync(),
+```
+- 使用 `item.ItemId` 查询品项分类
+- 如果 `item.ItemId` 为空或无效,查询会返回 null
+
+### 2. 更新开单时的逻辑(第1257行)
+```csharp
+ItemCategory = await _db.Queryable()
+ .Where(x => x.Id == item.DeductId) // ❌ 错误:应该使用 item.ItemId
+ .Select(x => x.Qt2)
+ .FirstAsync()
+```
+- **问题**:使用了 `item.DeductId` 而不是 `item.ItemId`
+- `DeductId` 是品项明细表(lq_kd_pxmx)的ID,不是项目资料表(lq_xmzl)的ID
+- 这会导致查询不到结果,返回 null
+
+### 3. 字段说明
+- **DeductId**:扣减品项关联ID,对应 `lq_kd_pxmx.F_Id`(品项明细表的主键)
+- **ItemId**:品项id,对应 `lq_xmzl.F_Id`(项目资料表的主键)
+- **ItemCategory**:品项分类,应该从 `lq_xmzl.qt2` 获取
+
+### 4. 导致空值的原因
+1. **更新开单时使用了错误的字段**:使用 `item.DeductId` 而不是 `item.ItemId`
+2. **查询不到结果**:如果 `item.ItemId` 为空或无效,`FirstAsync()` 会返回 null
+3. **品项不存在或无效**:如果品项在 `lq_xmzl` 表中不存在或无效,查询也会返回 null
+
+## 解决方案
+
+### ✅ 方案1:修复更新开单时的逻辑(已修复)
+将第1257行的 `item.DeductId` 改为 `item.ItemId`,与创建开单时的逻辑保持一致。
+
+**修复代码**:
+```csharp
+// 修复前(错误)
+ItemCategory = await _db.Queryable()
+ .Where(x => x.Id == item.DeductId) // ❌ 错误
+ .Select(x => x.Qt2)
+ .FirstAsync()
+
+// 修复后(正确)
+ItemCategory = await _db.Queryable()
+ .Where(x => x.Id == item.ItemId) // ✅ 正确
+ .Select(x => x.Qt2)
+ .FirstAsync()
+```
+
+### ✅ 方案2:修复历史数据(已提供SQL脚本)
+对于已经存在的空值记录,执行 `sql/修复储扣表ItemCategory字段为空的数据.sql` 脚本修复。
+
+### 方案3:增加容错处理(可选)
+在查询时增加容错处理,如果查询不到结果,尝试从其他途径获取分类。
+
+## 验证
+从数据库查询结果看:
+- F_ItemId = '76',对应的 lq_xmzl 表中 qt2 = '科美'(存在且有效)
+- F_ItemId = '100024',对应的 lq_xmzl 表中 qt2 = '科美'(存在且有效)
+
+说明这些记录的 `ItemId` 是有效的,问题应该是在更新开单时使用了错误的字段。
+
diff --git a/总部股份统计说明.md b/总部股份统计说明.md
new file mode 100644
index 0000000..7053faf
--- /dev/null
+++ b/总部股份统计说明.md
@@ -0,0 +1,64 @@
+# 总部股份统计说明 (Headquarters Share Statistics)
+
+## 1. 概述
+本以文档详细说明总部股份统计的逻辑。
+
+---
+
+## 2. 收入 (Income)
+
+### 2.1 收入 (General Income)
+* **说明**: 全部开单业绩的 9%(需减去退款)。
+* **计算逻辑**: (所有门店的总开单实付业绩 - 所有门店的总实退金额) * 9%。
+* **数据来源**: `lq_kd_kdjlb`, `lq_hytk_hytk`。
+
+### 2.2 收入-科技部 (Tech Dept Income)
+* **说明**: 科美开单的 30% 里面的 9%。
+* **计算逻辑**: (所有门店的科美开单业绩 * 30%) * 9%。
+ * 即: `(总科美业绩 - 总科美退款) * 0.3 * 0.09`。
+* **数据来源**: `lq_kd_pxmx` (筛选科美) 关联开单和退款。
+
+---
+
+## 3. 成本 (Cost)
+
+### 3.1 成本-报销 (Headquarters Expenses)
+* **说明**: 报销里面的总部的费用。
+* **计算逻辑**: 筛选报销申请中,归属于“总部”的报销。
+* **数据来源**:
+ * 表: `lq_reimbursement_application`。
+ * **判定方式**: 根据 `F_ApplicationStoreId` 是否为总部机构,或 报销分类是否标记为总部费用。
+
+### 3.2 成本-人工 (Labor)
+* **说明**: 不处理(保留字段)。
+
+### 3.3 成本-教育部房租 (Education Dept Rent)
+* **说明**: 合同里面获取。
+* **数据来源**:
+ * 表: `lq_contract_rent_detail`。
+ * **判定**: 关联 `lq_contract`,筛选 合同类型/归属 为“教育部” 的合同。
+
+### 3.4 成本-仓库房租 (Warehouse Rent)
+* **说明**: 合同里面获取。
+* **数据来源**:
+ * 表: `lq_contract_rent_detail`。
+ * **判定**: 关联 `lq_contract`,筛选 合同类型/归属 为“仓库” 的合同。
+
+### 3.5 成本-总部房租 (HQ Rent)
+* **说明**: 合同里面获取。
+* **数据来源**:
+ * 表: `lq_contract_rent_detail`。
+ * **判定**: 关联 `lq_contract`,筛选 合同类型/归属 为“总部” 的合同。
+
+---
+
+## 4. 利润计算 (Profit Calculation)
+
+### 4.1 总部运营利润
+```
+总部运营利润 = 收入(门店9%)
+ + 收入(科技部9%)
+ - 成本(报销)
+ - 成本(人工)
+ - 成本(总部房租 + 教育部房租 + 仓库房租)
+```
diff --git a/测试储扣接口.sh b/测试储扣接口.sh
new file mode 100755
index 0000000..4fdfeb0
--- /dev/null
+++ b/测试储扣接口.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+# 测试储扣相关接口
+
+# 1. 获取token
+echo "=== 1. 获取Token ==="
+TOKEN_RESPONSE=$(curl -s -X POST "http://localhost:2011/api/oauth/Login" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e")
+
+echo "$TOKEN_RESPONSE" | python3 -m json.tool
+
+TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data['data']['token'])")
+
+if [ -z "$TOKEN" ]; then
+ echo "❌ Token获取失败"
+ exit 1
+fi
+
+echo "✅ Token获取成功: ${TOKEN:0:50}..."
+echo ""
+
+# 2. 测试储扣金额统计接口(按品项分类)
+echo "=== 2. 测试储扣金额统计接口(get-deduct-amount-statistics)==="
+echo "请求参数: {\"startTime\": \"2025-11-01\", \"endTime\": \"2025-11-30\"}"
+echo ""
+
+DEDUCT_RESPONSE=$(curl -s -X POST "http://localhost:2011/api/Extend/LqDailyReport/get-deduct-amount-statistics" \
+ -H "Authorization: $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"startTime": "2025-11-01", "endTime": "2025-11-30"}')
+
+echo "$DEDUCT_RESPONSE" | python3 -m json.tool
+
+# 检查返回结果
+if echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); exit(0 if data.get('code') == 200 else 1)" 2>/dev/null; then
+ echo "✅ 储扣金额统计接口调用成功"
+
+ # 提取数据
+ YIMEI=$(echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('data', {}).get('yiMeiAmount', 0))" 2>/dev/null)
+ SHENGMEI=$(echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('data', {}).get('shengMeiAmount', 0))" 2>/dev/null)
+ KEMEI=$(echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('data', {}).get('keMeiAmount', 0))" 2>/dev/null)
+ TOTAL=$(echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('data', {}).get('totalAmount', 0))" 2>/dev/null)
+
+ echo "医美储扣金额: $YIMEI"
+ echo "生美储扣金额: $SHENGMEI"
+ echo "科美储扣金额: $KEMEI"
+ echo "总储扣金额: $TOTAL"
+else
+ echo "❌ 储扣金额统计接口调用失败"
+fi
+
+echo ""
+echo ""
+
+# 3. 测试部门业绩完成情况接口(包含储扣统计)
+echo "=== 3. 测试部门业绩完成情况接口(get-tianwang-group-performance-completion)==="
+echo "请求参数: {\"startTime\": \"2025-11-01\", \"endTime\": \"2025-11-30\"}"
+echo ""
+
+DEPT_RESPONSE=$(curl -s -X POST "http://localhost:2011/api/Extend/LqDailyReport/get-tianwang-group-performance-completion" \
+ -H "Authorization: $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"startTime": "2025-11-01", "endTime": "2025-11-30"}')
+
+echo "$DEPT_RESPONSE" | python3 -m json.tool
+
+# 检查返回结果
+if echo "$DEPT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); exit(0 if data.get('code') == 200 else 1)" 2>/dev/null; then
+ echo "✅ 部门业绩完成情况接口调用成功"
+
+ # 提取第一个部门的数据
+ FIRST_DEPT=$(echo "$DEPT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); depts=data.get('data', []); print(json.dumps(depts[0] if depts else {}, indent=2))" 2>/dev/null)
+
+ if [ ! -z "$FIRST_DEPT" ] && [ "$FIRST_DEPT" != "{}" ]; then
+ echo ""
+ echo "第一个部门的数据:"
+ echo "$FIRST_DEPT"
+ fi
+else
+ echo "❌ 部门业绩完成情况接口调用失败"
+fi
+
+echo ""
+echo "=== 测试完成 ==="
+
diff --git a/科技部股份统计说明.md b/科技部股份统计说明.md
new file mode 100644
index 0000000..7e074ad
--- /dev/null
+++ b/科技部股份统计说明.md
@@ -0,0 +1,87 @@
+# 科技部股份统计说明 (Tech Department Share Statistics)
+
+## 1. 概述
+本以文档详细说明科技部股份统计的逻辑。科技部需区分 **科技一部** 和 **科技二部**,根据管理的门店归属进行区分。
+
+---
+
+## 2. 收入 (Income)
+
+### 2.1 收入
+* **说明**: 各个门店科美开单的 30%(需减去退款)。
+* **计算逻辑**:
+ 1. 找到该科技部(一部或二部)管辖的所有门店。
+ 2. 统计这些门店的 **科美项目** 开单实付业绩。
+ 3. 减去 对应的科美项目实退金额。
+ 4. 结果 * 30%。
+* **数据来源**:
+ * 门店归属: `lq_mdxx` (门店信息表) 中的 `kjb` (科技部) 字段或其他归属字段。
+ * 开单: `lq_kd_pxmx` (`F_BeautyType` = 科美) 关联 `lq_kd_kdjlb`.
+ * 退款: `lq_hytk_jksyj` (包含退款品项信息) 或 `lq_hytk_hytk`。
+
+---
+
+## 3. 成本 (Cost)
+
+### 3.1 成本-报销 (Reimbursement)
+* **说明**: 报销费用里面的“科技部报销”。
+* **计算逻辑**:
+ * 筛选一级分类为“科技部报销”的申请。
+ * 区分一部/二部:根据报销单的 `F_ApplicationStoreId` (申请门店) 的归属。
+* **数据来源**:
+ * 表: `lq_reimbursement_application`, `lq_reimbursement_category`。
+
+### 3.2 成本-人工-科技部老师 (Teacher Base Salary)
+* **说明**: 科技部老师底薪。
+* **数据来源**:
+ * 表: `lq_tech_teacher_salary_statistics` (科技部老师工资统计表 - 需确认表名)。
+ * 字段: `F_ActualBaseSalary`。
+
+### 3.3 成本-人工-科技部手工费 (Teacher Manual Fee)
+* **说明**: 来源于消耗里面科技部老师业绩的手工费。
+* **数据来源**:
+ * 表: `lq_xh_jksyj` (消耗表)。
+ * 条件: `Jks` (健康师) 为科技部老师 且 `F_BeautyType` = 科美。
+ * 字段: `F_LaborCost`。
+
+### 3.4 成本-人工-科技部开单提成 (Teacher Billing Commission)
+* **说明**: 科技部老师的卡单提成。
+* **数据来源**:
+ * 表: `lq_tech_teacher_salary_statistics`。
+ * 字段: `F_BillingCommission` (开单提成 - 需确认具体字段名)。
+
+### 3.5 成本-人工-科技部消耗提成 (Teacher Consumption Commission)
+* **说明**: 科技部老师的消耗提成。
+* **数据来源**:
+ * 表: `lq_tech_teacher_salary_statistics`。
+ * 字段: `F_ConsumptionCommission` (消耗提成 - 需确认具体字段名)。
+
+### 3.6 成本-人工-科技部总经理 (General Manager Base Salary)
+* **说明**: 科技部总经理底薪。
+* **数据来源**:
+ * 表: `lq_tech_general_manager_salary_statistics` (科技部总经理工资表)。
+ * 字段: `F_ActualBaseSalary`。
+
+### 3.7 成本-人工-科技部提成 (General Manager Commission)
+* **说明**: 科技部总经理提成。
+* **数据来源**:
+ * 表: `lq_tech_general_manager_salary_statistics`。
+ * 字段: `F_TotalCommissionAmount`。
+
+### 3.8 保留/不处理字段
+* 成本-人工-科技部专家提成
+* 成本-人工-科技部加班
+* 奖励-科技部
+* 成本-其他1, 其他2
+
+---
+
+## 4. 利润计算 (Profit Calculation)
+
+### 4.1 科技部利润
+```
+科技部利润 = 收入
+ - 成本报销
+ - 成本人工(底薪 + 手工 + 开单提成 + 消耗提成 + 专家提成 + 加班 + 总经理底薪 + 总经理提成)
+ - 其他
+```
diff --git a/股份统计表结构.sql b/股份统计表结构.sql
new file mode 100644
index 0000000..64bda99
--- /dev/null
+++ b/股份统计表结构.sql
@@ -0,0 +1,169 @@
+/*
+ 股份统计相关表结构设计
+ Created Date: 2025-12-16
+ Description: 用于存储门店、科技部、总部的股份统计数据
+*/
+
+-- ----------------------------
+-- 1. 门店股份统计表 (lq_share_statistics_store)
+-- ----------------------------
+DROP TABLE IF EXISTS `lq_share_statistics_store`;
+CREATE TABLE `lq_share_statistics_store` (
+ `F_Id` varchar(50) NOT NULL COMMENT '主键ID',
+ `F_StoreId` varchar(50) NOT NULL COMMENT '门店ID',
+ `F_StoreName` varchar(100) DEFAULT NULL COMMENT '门店名称',
+ `F_StatisticsMonth` varchar(50) NOT NULL COMMENT '统计月份(YYYYMM)',
+
+ -- 收入部分
+ `F_MainIncome` decimal(18,2) DEFAULT '0.00' COMMENT '主营收入(消耗总业绩, 不扣减退款)',
+ `F_ConsumeLifeBeauty` decimal(18,2) DEFAULT '0.00' COMMENT '生美消耗业绩',
+ `F_ConsumeTechBeauty` decimal(18,2) DEFAULT '0.00' COMMENT '科美消耗业绩',
+ `F_ConsumeMedicalBeauty` decimal(18,2) DEFAULT '0.00' COMMENT '医美消耗业绩',
+ `F_ConsumeCooperation` decimal(18,2) DEFAULT '0.00' COMMENT '合作消费业绩',
+ `F_ConsumeProduct` decimal(18,2) DEFAULT '0.00' COMMENT '产品消耗业绩',
+ `F_ConsumeOther` decimal(18,2) DEFAULT '0.00' COMMENT '其他消耗业绩',
+ `F_OtherIncome` decimal(18,2) DEFAULT '0.00' COMMENT '其他收入(退款差额>0)',
+ `F_AdvanceReceipt` decimal(18,2) DEFAULT '0.00' COMMENT '预收款(开单实付)',
+ `F_Refund` decimal(18,2) DEFAULT '0.00' COMMENT '实际退款(实退业绩)',
+ `F_Receivables` decimal(18,2) DEFAULT '0.00' COMMENT '应收(合作医院)',
+ `F_BankDeposit` decimal(18,2) DEFAULT '0.00' COMMENT '银行存款(开单实付-应收)',
+
+ -- 成本部分
+ `F_CostProduct` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-产品(仓库领取)',
+ `F_CostFutian` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-福田(福田仓库领取)',
+ `F_CostTowel` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-毛巾(清洗送出)',
+ `F_CostTechDept` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-科技部(科美业绩30%)',
+ `F_CostManagementFee` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-管理费(总业绩9%)',
+ `F_CostCooperation` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-合作(保留)',
+ `F_CostMajorProject` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-大项目(保留)',
+ `F_CostOther` decimal(18,2) DEFAULT '0.00' COMMENT '其他成本(退款差额<0)',
+
+ -- 人工工资部分
+ `F_SalaryBaseHealthCoach` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-健康师底薪',
+ `F_SalaryBaseAssistant` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店助底薪',
+ `F_SalaryBaseDirector` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店助主任底薪',
+ `F_SalaryBaseStoreManager` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店长底薪',
+ `F_SalaryBaseGeneralManager` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-总经理/经理底薪',
+
+ `F_SalaryCommissionHealthCoach` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-健康师提成',
+ `F_SalaryCommissionAssistant` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店助提成',
+ `F_SalaryCommissionDirector` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店助主任提成',
+ `F_SalaryCommissionStoreManager` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店长提成',
+ `F_SalaryCommissionGeneralManager` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-总经理/经理提成',
+ `F_SalaryManual` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-手工',
+ `F_SalaryAttendance` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-出勤(保留)',
+ `F_SalaryPhoneCustody` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-岗位-手机保管费',
+ `F_SalaryHeadcountReward` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-岗位-人头',
+ `F_SalaryTZone` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-门店T区',
+
+ -- 费用与其他
+ `F_SocialSecurity` decimal(18,2) DEFAULT '0.00' COMMENT '社保(保留)',
+ `F_StoreRent` decimal(18,2) DEFAULT '0.00' COMMENT '门店房租',
+ `F_DormRent` decimal(18,2) DEFAULT '0.00' COMMENT '宿舍房租(保留)',
+ `F_CurrentPeriodExpense` decimal(18,2) DEFAULT '0.00' COMMENT '当期费用(报销)',
+ `F_CurrentPeriodExpenseQin` decimal(18,2) DEFAULT '0.00' COMMENT '当前费用-秦董(保留)',
+ `F_FinancialFee` decimal(18,2) DEFAULT '0.00' COMMENT '财务费用(保留)',
+
+ -- 奖励
+ `F_RewardMedicalBeauty` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-医美(保留)',
+ `F_RewardOther` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-其他(保留)',
+ `F_RewardLargeOrder` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-大单奖(保留)',
+ `F_RewardFirstOrder` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-首单奖(保留)',
+ `F_RewardGeneral` decimal(18,2) DEFAULT '0.00' COMMENT '奖励(保留)',
+
+ -- 其他保留字段
+ `F_Other1` decimal(18,2) DEFAULT '0.00' COMMENT '其他1(保留)',
+ `F_Other2` decimal(18,2) DEFAULT '0.00' COMMENT '其他2(保留)',
+
+ -- 利润结果
+ `F_Profit` decimal(18,2) DEFAULT '0.00' COMMENT '利润(预收-实退-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)',
+ `F_FinancialProfit` decimal(18,2) DEFAULT '0.00' COMMENT '财务利润(主营-其他收入-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)',
+
+ -- 基础字段
+ `F_CreateTime` datetime DEFAULT NULL COMMENT '创建时间',
+ `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
+ `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人',
+ `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人',
+ `F_IsEffective` int(1) DEFAULT '1' COMMENT '是否有效(1:有效 0:无效)',
+
+ PRIMARY KEY (`F_Id`),
+ KEY `Idx_StoreId` (`F_StoreId`),
+ KEY `Idx_Month` (`F_StatisticsMonth`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='门店股份统计表';
+
+
+-- ----------------------------
+-- 2. 科技部股份统计表 (lq_share_statistics_tech_dept)
+-- ----------------------------
+DROP TABLE IF EXISTS `lq_share_statistics_tech_dept`;
+CREATE TABLE `lq_share_statistics_tech_dept` (
+ `F_Id` varchar(50) NOT NULL COMMENT '主键ID',
+ `F_DepartmentName` varchar(100) DEFAULT NULL COMMENT '部门名称(科技一部/二部)',
+ `F_StatisticsMonth` varchar(50) NOT NULL COMMENT '统计月份(YYYYMM)',
+
+ -- 收入
+ `F_Income` decimal(18,2) DEFAULT '0.00' COMMENT '收入(门店科美开单30%)',
+
+ -- 成本
+ `F_CostReimbursement` decimal(18,2) DEFAULT '0.00' COMMENT '成本-报销',
+ `F_CostTeacherBase` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部老师底薪',
+ `F_CostTeacherManual` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部手工费',
+ `F_CostTeacherBillingComm` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部开单提成',
+ `F_CostTeacherConsumeComm` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部消耗提成',
+ `F_CostTeacherExpertComm` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部专家提成(保留)',
+ `F_CostTeacherOvertime` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部加班(保留)',
+ `F_CostGMBase` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部总经理底薪',
+ `F_CostGMComm` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部总经理提成',
+ `F_RewardTechDept` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-科技部(保留)',
+
+ -- 其他
+ `F_CostOther1` decimal(18,2) DEFAULT '0.00' COMMENT '成本-其他1(保留)',
+ `F_CostOther2` decimal(18,2) DEFAULT '0.00' COMMENT '成本-其他2(保留)',
+
+ -- 利润结果
+ `F_Profit` decimal(18,2) DEFAULT '0.00' COMMENT '科技部利润',
+
+ -- 基础字段
+ `F_CreateTime` datetime DEFAULT NULL COMMENT '创建时间',
+ `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
+ `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人',
+ `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人',
+ `F_IsEffective` int(1) DEFAULT '1' COMMENT '是否有效(1:有效 0:无效)',
+
+ PRIMARY KEY (`F_Id`),
+ KEY `Idx_Month` (`F_StatisticsMonth`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='科技部股份统计表';
+
+
+-- ----------------------------
+-- 3. 总部股份统计表 (lq_share_statistics_hq)
+-- ----------------------------
+DROP TABLE IF EXISTS `lq_share_statistics_hq`;
+CREATE TABLE `lq_share_statistics_hq` (
+ `F_Id` varchar(50) NOT NULL COMMENT '主键ID',
+ `F_StatisticsMonth` varchar(50) NOT NULL COMMENT '统计月份(YYYYMM)',
+
+ -- 收入
+ `F_IncomeGeneral` decimal(18,2) DEFAULT '0.00' COMMENT '收入-全部(开单业绩9%)',
+ `F_IncomeTechDept` decimal(18,2) DEFAULT '0.00' COMMENT '收入-科技部(科美30%的9%)',
+
+ -- 成本
+ `F_CostReimbursement` decimal(18,2) DEFAULT '0.00' COMMENT '成本-报销(总部费用)',
+ `F_CostLabor` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工(保留)',
+ `F_CostEducationRent` decimal(18,2) DEFAULT '0.00' COMMENT '成本-教育部房租',
+ `F_CostWarehouseRent` decimal(18,2) DEFAULT '0.00' COMMENT '成本-仓库房租',
+ `F_CostHQRent` decimal(18,2) DEFAULT '0.00' COMMENT '成本-总部房租',
+
+ -- 利润结果
+ `F_OperationalProfit` decimal(18,2) DEFAULT '0.00' COMMENT '总部运营利润',
+
+ -- 基础字段
+ `F_CreateTime` datetime DEFAULT NULL COMMENT '创建时间',
+ `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
+ `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人',
+ `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人',
+ `F_IsEffective` int(1) DEFAULT '1' COMMENT '是否有效(1:有效 0:无效)',
+
+ PRIMARY KEY (`F_Id`),
+ KEY `Idx_Month` (`F_StatisticsMonth`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='总部股份统计表';
diff --git a/门店股份统计说明.md b/门店股份统计说明.md
new file mode 100644
index 0000000..bf43b21
--- /dev/null
+++ b/门店股份统计说明.md
@@ -0,0 +1,204 @@
+# 门店股份统计说明 (Store Share Statistics)
+
+## 1. 概述
+本以文档详细说明门店股份统计的各个组成部分,包括主营收入、其他收入、各项成本、人工工资及费用的计算逻辑和数据来源。
+
+---
+
+## 2. 收入 (Income)
+
+### 2.1 主营收入 (Main Income)
+* **说明**: 按照消耗业绩统计(需区分生美、科美、医美、产品等)。
+* **计算逻辑**: 统计当月 `lq_xh_jksyj` (耗卡健康师业绩表) 中的 `jksyj` (健康师业绩)。
+* **数据来源**:
+ * 表: `lq_xh_jksyj`
+ * 字段: `jksyj` (业绩金额), `F_ItemCategory` (品项分类 - 生美/科美/医美/产品)
+ * **注意**: 需关联 `lq_kd_pxmx` (开单品项明细) 或通过 `F_ItemId` 获取更详细属性。
+ * **退款扣除**: 主营收入计算时,应为 **总消耗业绩 - 对应品项的消耗退款**。即 `(开单消耗业绩 - 退款消耗业绩)`。需确保“主营收入”是净收入。
+
+### 2.2 其他收入 (Other Income)
+* **说明**: 退款差额 > 0 的部分。
+* **计算逻辑**:
+ 1. 计算单笔退款的差额 = `应退金额` - `实退金额`。
+ 2. 若差额 > 0,计入其他收入。
+* **数据来源**:
+ * 表: `lq_hytk_hytk` (会员退款表)
+ * 字段: `tkje` (应退金额/退卡金额), `F_ActualRefundAmount` (实退金额)
+ * 计算: `SUM(CASE WHEN (tkje - F_ActualRefundAmount) > 0 THEN (tkje - F_ActualRefundAmount) ELSE 0 END)`
+
+### 2.3 预收款 (Advance Receipt)
+* **说明**: 来源于开单的实收业绩。
+* **数据来源**:
+ * 表: `lq_kd_kdjlb` (开单记录表)
+ * 字段: `sfyj` (实付业绩)
+
+### 2.4 退款 (Refund)
+* **说明**: 按照退款单里面的实退业绩来计算。
+* **数据来源**:
+ * 表: `lq_hytk_hytk`
+ * 字段: `F_ActualRefundAmount` (实退金额)
+
+### 2.5 退款差额 (Refund Difference)
+* **说明**: `应退金额` - `实退金额`。
+ * `> 0`: 归纳到 **其他收入**
+ * `< 0`: 归纳到 **其他成本**
+* **数据来源**: 同 2.2。
+
+### 2.6 应收 (Receivables)
+* **说明**: 开单时选择了合作医院的款项。
+* **计算逻辑**: 统计支付方式或关联机构为“合作医院”的开单金额。
+* **数据来源**:
+ * 表: `lq_kd_kdjlb`
+ * 字段: `Hgjg` (合作机构) 或 支付方式字段。
+ * **判定**: `Hgjg` 不为空 且 属于医院类型。
+
+### 2.7 银行存款 (Bank Deposit)
+* **说明**: 开单时,**除了**选择了合作医院以外的所有款项。
+* **计算逻辑**: 总实付业绩 - 应收。
+
+---
+
+## 3. 成本 (Cost)
+
+### 3.1 主营成本-产品 (product)
+* **说明**: 仓库领取数据,算上个月成本。
+* **数据来源**:
+ * 表: `lq_inventory_usage` (库存使用记录)
+ * 字段: `F_TotalAmount` (合计金额)
+ * **条件**: `F_UsageTime` 在上个月范围内。
+
+### 3.2 主营成本-福田 (futian)
+* **说明**: 仓库地址是“福田”的领取数据,算上个月成本。
+* **数据来源**:
+ * 表: `lq_inventory_usage`
+ * **待确认**: 需确认“福田”仓库的 `StoreId` 或如何标识。暂时需通过 `StoreId` 或 `ProductId` 对应的仓库属性识别。
+
+### 3.3 主营成本-毛巾 (towel)
+* **说明**: 清洗记录里面的送出。
+* **数据来源**:
+ * 表: `lq_laundry_flow` (清洗流水表)
+ * 字段: `F_TotalPrice`
+ * **条件**: `F_FlowType` = 0 (送出)。
+
+### 3.4 主营成本-科技部 (tech_dept)
+* **说明**: 门店开单中属于科美的(总业绩 - 退款业绩) * 30%。
+* **计算逻辑**:
+ 1. 筛选科美项目 (`lq_kd_pxmx.F_BeautyType` 为科美 或 `ItemCategory` 为科美)。
+ 2. 计算科美项目的实付业绩总和 - 对应退款。
+ 3. 乘以 30%。
+
+### 3.5 主营成本-管理费 (management_fee)
+* **说明**: 门店所有开单(总业绩 - 退款业绩) * 9%。
+* **计算逻辑**: (`lq_kd_kdjlb.sfyj` 总和 - `lq_hytk_hytk.F_ActualRefundAmount` 总和) * 9%。
+
+### 3.6 主营成本-合作 (cooperation)
+* **说明**: 不处理(保留字段)。
+
+### 3.7 主营成本-大项目 (major_project)
+* **说明**: 不处理(保留字段)。
+
+---
+
+## 4. 人工工资 (Labor Cost)
+
+### 4.1 人工工资-底薪 (base_salary)
+* **人工工资-健康师底薪**: 来源于 `lq_salary_statistics.F_ActualBaseSalary`。
+* **人工工资-店助底薪**: 来源于 `lq_assistant_salary_statistics.F_ActualBaseSalary` (岗位为“店助”)。
+* **人工工资-店助主任底薪**: 来源于 `lq_director_salary_statistics.F_ActualBaseSalary` (岗位为“店助主任”)。
+* **人工工资-店长底薪**: 来源于 `lq_store_manager_salary_statistics.F_ActualBaseSalary`。
+* **人工工资-总经理/经理底薪**: 来源于事业部总经理/经理工资表 `F_ActualBaseSalary` (按门店平均分摊 - 底薪固定4000,按管理门店数平均)。
+
+### 4.2 人工工资-提成 (commission)
+* **人工工资-健康师提成**: 来源于 `lq_salary_statistics.F_TotalCommissionAmount`。
+* **人工工资-店助提成**: 来源于 `lq_assistant_salary_statistics.F_TotalCommissionAmount`。
+* **人工工资-店助主任提成**: 来源于 `lq_director_salary_statistics.F_TotalCommissionAmount`。
+* **人工工资-店长提成**: 来源于 `lq_store_manager_salary_statistics.F_TotalCommissionAmount`。
+* **人工工资-总经理/经理提成**: 来源于事业部总经理/经理工资表中该门店对应的提成金额。
+ * **注意**: 总经理提成是按门店单独计算汇总的,工资表中需记录分店提成明细或能反查单店提成。
+ * 根据规则:`总提成 = SUM(各门店提成金额)`。统计时需提取该门店贡献的部分。
+
+### 4.3 人工工资-手工 (manual_fee)
+* **说明**: 健康师手工费(消耗表)。
+* **数据来源**:
+ * 表: `lq_xh_jksyj`
+ * 字段: `F_LaborCost` (手工费)
+ * **或者**: 直接取工资表中的 `F_HandworkFee` (如果有统计)。推荐使用消耗表累加更精准。
+
+### 4.4 人工工资-岗位-手机保管费 (phone_custody)
+* **说明**: 店助的手机保管费。
+* **数据来源**:
+ * 表: `lq_assistant_salary_statistics`
+ * 字段: `F_PhoneManagementFee` (手机保管费,已确认字段存在)。
+
+### 4.5 人工工资-岗位-人头 (headcount_reward)
+* **说明**: 店助的人头阶段奖励。
+* **数据来源**:
+ * 表: `lq_assistant_salary_statistics`
+ * 字段: 需确认具体的奖励字段,可能是 `F_HeadcountAward` 或包含在奖金中。
+
+### 4.6 人工工资-门店T区 (t_zone)
+* **说明**: 健康师工资表里面T区的工资金额。
+* **数据来源**:
+ * 表: `lq_salary_statistics`
+ * 字段: 筛选 T区员工的 `F_ActualSalary` 或 特定 T区 补贴字段。
+
+---
+
+## 5. 费用与其他 (Expenses & Others)
+
+### 5.1 门店房租 (store_rent)
+* **说明**: 来源于合同对应的每月房租明细。
+* **数据来源**:
+ * 表: `lq_contract_rent_detail` (月租明细表)
+ * 字段: `F_DueAmount` (应缴金额)
+ * 条件: `F_PaymentMonth` 匹配当月。
+
+### 5.2 当期费用 (current_period_expense)
+* **说明**: 报销费用 -> 费用分类 = "当期费用"。
+* **数据来源**:
+ * 表: `lq_reimbursement_application` (报销申请) 关联 `lq_reimbursement_category` (报销分类)。
+ * 条件: 通过分类表筛选出一级分类或二级分类为“当期费用”的记录。
+
+### 5.3 其他成本 (other_cost)
+* **说明**: 退款差额 < 0 的部分 (即 `(应退 - 实退) < 0`,多退了)。
+* **计算逻辑**: 绝对值(`应退` - `实退`)。
+
+### 5.4 保留/不处理字段
+* 人工工资-出勤
+* 社保
+* 宿舍房租
+* 当前费用-秦董
+* 财务费用
+* 各项奖励 (医美/其他/大单/首单)
+* 其他1, 其他2
+
+---
+
+## 6. 利润计算 (Profit Calculation)
+
+### 6.1 利润 (Profit)
+```
+利润 = 预收款
+ - 实际退款
+ - 主营成本(产品 + 福田 + 毛巾 + 合作 + 大项目 + 科技部 + 管理费)
+ - 人工工资(底薪 + 提成 + 手工 + 出勤 + 岗位 + T区)
+ - 社保(公司部分)
+ - 门店房租
+ - 宿舍房租
+ - 当前费用
+ - 当前费用(秦董)
+ - 财务费用
+ - 奖励(各项)
+ - 其他1 - 其他2
+```
+
+### 6.2 财务利润 (Financial Profit)
+```
+财务利润 = 主营收入
+ - 其他收入
+ - 主营成本(...)
+ - 人工工资(...)
+ - 社保(...)
+ - 门店房租
+ - ... (同上扣除项)
+```