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) +``` +财务利润 = 主营收入 + - 其他收入 + - 主营成本(...) + - 人工工资(...) + - 社保(...) + - 门店房租 + - ... (同上扣除项) +```