Commit cea16511630744ea545df953b2f8c29b6efd4faa

Authored by 李宇
2 parents 3c9d616b b79763af

Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP

Showing 56 changed files with 4601 additions and 130 deletions
PROJECT_RULES.md
... ... @@ -143,6 +143,44 @@ Id = Guid.NewGuid().ToString()
143 143 - 必须包含所有可能的HTTP状态码响应说明
144 144 - 复杂接口必须提供完整的请求示例
145 145  
  146 +### 接口测试规范
  147 +- **必须测试**: 所有新开发的接口或修改的接口都必须进行测试
  148 +- **测试要求**:
  149 + - 使用实际数据测试接口功能
  150 + - 验证接口返回数据的正确性
  151 + - 测试边界情况和异常情况
  152 + - 验证接口性能和响应时间
  153 + - 确保接口符合业务逻辑要求
  154 +- **测试方式**: 可以使用 curl、Postman、Swagger 等工具进行接口测试
  155 +- **测试通过**: 只有测试通过的接口才能提交代码
  156 +- **测试token获取**:
  157 + - **接口地址**: `/api/oauth/Login`
  158 + - **请求方式**: POST
  159 + - **Content-Type**: `application/x-www-form-urlencoded`
  160 + - **请求参数**:
  161 + - `account`: `admin`
  162 + - `password`: `e10adc3949ba59abbe56e057f20f883e`
  163 + - **curl示例**:
  164 + ```bash
  165 + curl -X POST "http://localhost:2011/api/oauth/Login" \
  166 + -H "Content-Type: application/x-www-form-urlencoded" \
  167 + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e"
  168 + ```
  169 + - **返回格式**:
  170 + ```json
  171 + {
  172 + "code": 200,
  173 + "msg": "操作成功",
  174 + "data": {
  175 + "theme": "functional",
  176 + "token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  177 + "user": null
  178 + }
  179 + }
  180 + ```
  181 + - **token使用**: 返回的token已包含"Bearer "前缀,可直接在请求头中使用:`Authorization: {data.token}`
  182 +
  183 +
146 184 ## 📊 数据一致性规范
147 185  
148 186 ### 统计与列表数据
... ...
excel/健康师额外数据模板_测试.xlsx 0 → 100644
No preview for this file type
excel/健康师额外数据模板_测试导入.xlsx 0 → 100644
No preview for this file type
netcore/src/Modularity/Extend/NCC.Extend.Entitys.Dto/LqTechTeacherSalary/TechTeacherStatisticsOutput.cs deleted
1   -using System;
2   -
3   -namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary
4   -{
5   - /// <summary>
6   - /// 科技部老师统计数据输出
7   - /// </summary>
8   - public class TechTeacherStatisticsOutput
9   - {
10   - /// <summary>
11   - /// 员工ID
12   - /// </summary>
13   - public string EmployeeId { get; set; }
14   -
15   - /// <summary>
16   - /// 员工姓名
17   - /// </summary>
18   - public string EmployeeName { get; set; }
19   -
20   - /// <summary>
21   - /// 开单业绩
22   - /// </summary>
23   - public decimal OrderAchievement { get; set; }
24   -
25   - /// <summary>
26   - /// 消耗业绩
27   - /// </summary>
28   - public decimal ConsumeAchievement { get; set; }
29   -
30   - /// <summary>
31   - /// 退卡业绩
32   - /// </summary>
33   - public decimal RefundAchievement { get; set; }
34   -
35   - /// <summary>
36   - /// 人头(按月份+客户去重统计)
37   - /// </summary>
38   - public int PersonCount { get; set; }
39   -
40   - /// <summary>
41   - /// 人次(按日期+客户去重统计)
42   - /// </summary>
43   - public decimal PersonTimes { get; set; }
44   -
45   - /// <summary>
46   - /// 手工费
47   - /// </summary>
48   - public decimal LaborCost { get; set; }
49   - }
50   -}
51   -
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs
... ... @@ -33,6 +33,11 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
33 33 public decimal productPrice { get; set; }
34 34  
35 35 /// <summary>
  36 + /// 产品归属仓库
  37 + /// </summary>
  38 + public string productWarehouse { get; set; }
  39 +
  40 + /// <summary>
36 41 /// 门店ID
37 42 /// </summary>
38 43 public string storeId { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs
... ... @@ -76,6 +76,16 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
76 76 /// 门店名称
77 77 /// </summary>
78 78 public string storeName { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 合作机构
  82 + /// </summary>
  83 + public string hgjg { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 付款医院
  87 + /// </summary>
  88 + public string fkyy { get; set; }
79 89 }
80 90 }
81 91  
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowListOutput.cs
... ... @@ -109,6 +109,18 @@ namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
109 109 /// </summary>
110 110 [Display(Name = "创建时间")]
111 111 public DateTime createTime { get; set; }
  112 +
  113 + /// <summary>
  114 + /// 送出时间(流水类型为0时使用)
  115 + /// </summary>
  116 + [Display(Name = "送出时间")]
  117 + public DateTime? sendTime { get; set; }
  118 +
  119 + /// <summary>
  120 + /// 送回时间(流水类型为1时使用)
  121 + /// </summary>
  122 + [Display(Name = "送回时间")]
  123 + public DateTime? returnTime { get; set; }
112 124 }
113 125 }
114 126  
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowUpdateInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  5 +{
  6 + /// <summary>
  7 + /// 清洗流水修改输入
  8 + /// </summary>
  9 + public class LqLaundryFlowUpdateInput
  10 + {
  11 + /// <summary>
  12 + /// 记录ID
  13 + /// </summary>
  14 + [Required(ErrorMessage = "记录ID不能为空")]
  15 + [StringLength(50, ErrorMessage = "记录ID长度不能超过50个字符")]
  16 + [Display(Name = "记录ID")]
  17 + public string Id { get; set; }
  18 +
  19 + /// <summary>
  20 + /// 数量
  21 + /// </summary>
  22 + [Range(0, int.MaxValue, ErrorMessage = "数量不能小于0")]
  23 + [Display(Name = "数量")]
  24 + public int? Quantity { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 送出时间(流水类型为0时使用)
  28 + /// </summary>
  29 + [Display(Name = "送出时间")]
  30 + public DateTime? SendTime { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 送回时间(流水类型为1时使用)
  34 + /// </summary>
  35 + [Display(Name = "送回时间")]
  36 + public DateTime? ReturnTime { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 备注
  40 + /// </summary>
  41 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  42 + [Display(Name = "备注")]
  43 + public string Remark { get; set; }
  44 + }
  45 +}
  46 +
  47 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqGenerateInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsHq
  2 +{
  3 + /// <summary>
  4 + /// 总部股份统计生成输入
  5 + /// </summary>
  6 + public class ShareStatisticsHqGenerateInput
  7 + {
  8 + /// <summary>
  9 + /// 统计月份(YYYYMM)
  10 + /// </summary>
  11 + public string StatisticsMonth { get; set; }
  12 + }
  13 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsHq
  4 +{
  5 + /// <summary>
  6 + /// 总部股份统计输出
  7 + /// </summary>
  8 + public class ShareStatisticsHqOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 统计月份
  17 + /// </summary>
  18 + public string StatisticsMonth { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 收入-全部
  22 + /// </summary>
  23 + public decimal IncomeGeneral { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 收入-科技部
  27 + /// </summary>
  28 + public decimal IncomeTechDept { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 成本-报销
  32 + /// </summary>
  33 + public decimal CostReimbursement { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 成本-人工
  37 + /// </summary>
  38 + public decimal CostLabor { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 成本-教育部房租
  42 + /// </summary>
  43 + public decimal CostEducationRent { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 成本-仓库房租
  47 + /// </summary>
  48 + public decimal CostWarehouseRent { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 成本-总部房租
  52 + /// </summary>
  53 + public decimal CostHQRent { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 运营利润
  57 + /// </summary>
  58 + public decimal OperationalProfit { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 创建时间
  62 + /// </summary>
  63 + public DateTime CreateTime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 更新时间
  67 + /// </summary>
  68 + public DateTime? UpdateTime { get; set; }
  69 + }
  70 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsHq/ShareStatisticsHqQueryInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsHq
  2 +{
  3 + /// <summary>
  4 + /// 总部股份统计查询输入
  5 + /// </summary>
  6 + public class ShareStatisticsHqQueryInput
  7 + {
  8 + /// <summary>
  9 + /// 统计月份(YYYYMM)
  10 + /// </summary>
  11 + public string StatisticsMonth { get; set; }
  12 + }
  13 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreGenerateInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsStore
  4 +{
  5 + /// <summary>
  6 + /// 门店股份统计生成输入
  7 + /// </summary>
  8 + public class ShareStatisticsStoreGenerateInput
  9 + {
  10 + /// <summary>
  11 + /// 统计月份(YYYYMM)
  12 + /// </summary>
  13 + public string StatisticsMonth { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店ID(可选,为空则生成所有门店)
  17 + /// </summary>
  18 + public string StoreId { get; set; }
  19 + }
  20 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsStore
  4 +{
  5 + /// <summary>
  6 + /// 门店股份统计输出
  7 + /// </summary>
  8 + public class ShareStatisticsStoreOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店ID
  17 + /// </summary>
  18 + public string StoreId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店名称
  22 + /// </summary>
  23 + public string StoreName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 统计月份(YYYYMM)
  27 + /// </summary>
  28 + public string StatisticsMonth { get; set; }
  29 +
  30 + // 收入部分
  31 + /// <summary>
  32 + /// 主营收入(消耗总业绩, 不扣减退款)
  33 + /// </summary>
  34 + public decimal MainIncome { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 生美消耗业绩
  38 + /// </summary>
  39 + public decimal ConsumeLifeBeauty { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 科美消耗业绩
  43 + /// </summary>
  44 + public decimal ConsumeTechBeauty { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 医美消耗业绩
  48 + /// </summary>
  49 + public decimal ConsumeMedicalBeauty { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 合作消费业绩
  53 + /// </summary>
  54 + public decimal ConsumeCooperation { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 产品消耗业绩
  58 + /// </summary>
  59 + public decimal ConsumeProduct { get; set; }
  60 +
  61 + /// <summary>
  62 + /// 其他消耗业绩
  63 + /// </summary>
  64 + public decimal ConsumeOther { get; set; }
  65 +
  66 + /// <summary>
  67 + /// 其他收入(退款差额>0)
  68 + /// </summary>
  69 + public decimal OtherIncome { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 预收款(开单实付)
  73 + /// </summary>
  74 + public decimal AdvanceReceipt { get; set; }
  75 +
  76 + /// <summary>
  77 + /// 实际退款(实退业绩)
  78 + /// </summary>
  79 + public decimal Refund { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 应收(合作医院)
  83 + /// </summary>
  84 + public decimal Receivables { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 银行存款(开单实付-应收)
  88 + /// </summary>
  89 + public decimal BankDeposit { get; set; }
  90 +
  91 + // 成本部分
  92 + /// <summary>
  93 + /// 主营成本-产品(仓库领取)
  94 + /// </summary>
  95 + public decimal CostProduct { get; set; }
  96 +
  97 + /// <summary>
  98 + /// 主营成本-福田(福田仓库领取)
  99 + /// </summary>
  100 + public decimal CostFutian { get; set; }
  101 +
  102 + /// <summary>
  103 + /// 主营成本-毛巾(清洗送出)
  104 + /// </summary>
  105 + public decimal CostTowel { get; set; }
  106 +
  107 + /// <summary>
  108 + /// 主营成本-科技部(科美业绩30%)
  109 + /// </summary>
  110 + public decimal CostTechDept { get; set; }
  111 +
  112 + /// <summary>
  113 + /// 主营成本-管理费(总业绩9%)
  114 + /// </summary>
  115 + public decimal CostManagementFee { get; set; }
  116 +
  117 + /// <summary>
  118 + /// 主营成本-合作(保留)
  119 + /// </summary>
  120 + public decimal CostCooperation { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 主营成本-大项目(保留)
  124 + /// </summary>
  125 + public decimal CostMajorProject { get; set; }
  126 +
  127 + /// <summary>
  128 + /// 其他成本(退款差额<0)
  129 + /// </summary>
  130 + public decimal CostOther { get; set; }
  131 +
  132 + // 人工工资部分
  133 + /// <summary>
  134 + /// 人工工资-健康师底薪
  135 + /// </summary>
  136 + public decimal SalaryBaseHealthCoach { get; set; }
  137 +
  138 + /// <summary>
  139 + /// 人工工资-店助底薪
  140 + /// </summary>
  141 + public decimal SalaryBaseAssistant { get; set; }
  142 +
  143 + /// <summary>
  144 + /// 人工工资-店助主任底薪
  145 + /// </summary>
  146 + public decimal SalaryBaseDirector { get; set; }
  147 +
  148 + /// <summary>
  149 + /// 人工工资-店长底薪
  150 + /// </summary>
  151 + public decimal SalaryBaseStoreManager { get; set; }
  152 +
  153 + /// <summary>
  154 + /// 人工工资-总经理/经理底薪
  155 + /// </summary>
  156 + public decimal SalaryBaseGeneralManager { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 人工工资-健康师提成
  160 + /// </summary>
  161 + public decimal SalaryCommissionHealthCoach { get; set; }
  162 +
  163 + /// <summary>
  164 + /// 人工工资-店助提成
  165 + /// </summary>
  166 + public decimal SalaryCommissionAssistant { get; set; }
  167 +
  168 + /// <summary>
  169 + /// 人工工资-店助主任提成
  170 + /// </summary>
  171 + public decimal SalaryCommissionDirector { get; set; }
  172 +
  173 + /// <summary>
  174 + /// 人工工资-店长提成
  175 + /// </summary>
  176 + public decimal SalaryCommissionStoreManager { get; set; }
  177 +
  178 + /// <summary>
  179 + /// 人工工资-总经理/经理提成
  180 + /// </summary>
  181 + public decimal SalaryCommissionGeneralManager { get; set; }
  182 +
  183 + /// <summary>
  184 + /// 人工工资-手工
  185 + /// </summary>
  186 + public decimal SalaryManual { get; set; }
  187 +
  188 + /// <summary>
  189 + /// 人工工资-出勤(保留)
  190 + /// </summary>
  191 + public decimal SalaryAttendance { get; set; }
  192 +
  193 + /// <summary>
  194 + /// 人工工资-岗位-手机保管费
  195 + /// </summary>
  196 + public decimal SalaryPhoneCustody { get; set; }
  197 +
  198 + /// <summary>
  199 + /// 人工工资-岗位-人头
  200 + /// </summary>
  201 + public decimal SalaryHeadcountReward { get; set; }
  202 +
  203 + /// <summary>
  204 + /// 人工工资-门店T区
  205 + /// </summary>
  206 + public decimal SalaryTZone { get; set; }
  207 +
  208 + // 费用与其他
  209 + /// <summary>
  210 + /// 社保(保留)
  211 + /// </summary>
  212 + public decimal SocialSecurity { get; set; }
  213 +
  214 + /// <summary>
  215 + /// 门店房租
  216 + /// </summary>
  217 + public decimal StoreRent { get; set; }
  218 +
  219 + /// <summary>
  220 + /// 宿舍房租(保留)
  221 + /// </summary>
  222 + public decimal DormRent { get; set; }
  223 +
  224 + /// <summary>
  225 + /// 当期费用(报销)
  226 + /// </summary>
  227 + public decimal CurrentPeriodExpense { get; set; }
  228 +
  229 + /// <summary>
  230 + /// 当前费用-秦董(保留)
  231 + /// </summary>
  232 + public decimal CurrentPeriodExpenseQin { get; set; }
  233 +
  234 + /// <summary>
  235 + /// 财务费用(保留)
  236 + /// </summary>
  237 + public decimal FinancialFee { get; set; }
  238 +
  239 + // 奖励
  240 + /// <summary>
  241 + /// 奖励-医美(保留)
  242 + /// </summary>
  243 + public decimal RewardMedicalBeauty { get; set; }
  244 +
  245 + /// <summary>
  246 + /// 奖励-其他(保留)
  247 + /// </summary>
  248 + public decimal RewardOther { get; set; }
  249 +
  250 + /// <summary>
  251 + /// 奖励-大单奖(保留)
  252 + /// </summary>
  253 + public decimal RewardLargeOrder { get; set; }
  254 +
  255 + /// <summary>
  256 + /// 奖励-首单奖(保留)
  257 + /// </summary>
  258 + public decimal RewardFirstOrder { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 奖励(保留)
  262 + /// </summary>
  263 + public decimal RewardGeneral { get; set; }
  264 +
  265 + // 其他保留字段
  266 + /// <summary>
  267 + /// 其他1(保留)
  268 + /// </summary>
  269 + public decimal Other1 { get; set; }
  270 +
  271 + /// <summary>
  272 + /// 其他2(保留)
  273 + /// </summary>
  274 + public decimal Other2 { get; set; }
  275 +
  276 + // 利润结果
  277 + /// <summary>
  278 + /// 利润(预收-实退-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)
  279 + /// </summary>
  280 + public decimal Profit { get; set; }
  281 +
  282 + /// <summary>
  283 + /// 财务利润(主营-其他收入-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)
  284 + /// </summary>
  285 + public decimal FinancialProfit { get; set; }
  286 +
  287 + // 基础字段
  288 + /// <summary>
  289 + /// 创建时间
  290 + /// </summary>
  291 + public DateTime? CreateTime { get; set; }
  292 +
  293 + /// <summary>
  294 + /// 更新时间
  295 + /// </summary>
  296 + public DateTime? UpdateTime { get; set; }
  297 + }
  298 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsStore/ShareStatisticsStoreQueryInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsStore
  4 +{
  5 + /// <summary>
  6 + /// 门店股份统计查询输入
  7 + /// </summary>
  8 + public class ShareStatisticsStoreQueryInput
  9 + {
  10 + /// <summary>
  11 + /// 统计月份(YYYYMM)
  12 + /// </summary>
  13 + public string StatisticsMonth { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店ID(可选)
  17 + /// </summary>
  18 + public string StoreId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店名称(可选,模糊查询)
  22 + /// </summary>
  23 + public string StoreName { get; set; }
  24 + }
  25 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptGenerateInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsTechDept
  2 +{
  3 + /// <summary>
  4 + /// 科技部股份统计生成输入
  5 + /// </summary>
  6 + public class ShareStatisticsTechDeptGenerateInput
  7 + {
  8 + /// <summary>
  9 + /// 统计月份(YYYYMM)
  10 + /// </summary>
  11 + public string StatisticsMonth { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 部门名称(科技一部/科技二部,为空则生成两个部门)
  15 + /// </summary>
  16 + public string DepartmentName { get; set; }
  17 + }
  18 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsTechDept
  4 +{
  5 + /// <summary>
  6 + /// 科技部股份统计输出
  7 + /// </summary>
  8 + public class ShareStatisticsTechDeptOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 部门名称
  17 + /// </summary>
  18 + public string DepartmentName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 统计月份
  22 + /// </summary>
  23 + public string StatisticsMonth { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 收入
  27 + /// </summary>
  28 + public decimal Income { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 成本-报销
  32 + /// </summary>
  33 + public decimal CostReimbursement { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 成本-人工-老师底薪
  37 + /// </summary>
  38 + public decimal CostTeacherBase { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 成本-人工-手工费
  42 + /// </summary>
  43 + public decimal CostTeacherManual { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 成本-人工-开单提成
  47 + /// </summary>
  48 + public decimal CostTeacherBillingComm { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 成本-人工-消耗提成
  52 + /// </summary>
  53 + public decimal CostTeacherConsumeComm { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 成本-人工-专家提成
  57 + /// </summary>
  58 + public decimal CostTeacherExpertComm { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 成本-人工-加班
  62 + /// </summary>
  63 + public decimal CostTeacherOvertime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 成本-人工-总经理底薪
  67 + /// </summary>
  68 + public decimal CostGMBase { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 成本-人工-总经理提成
  72 + /// </summary>
  73 + public decimal CostGMComm { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 奖励-科技部
  77 + /// </summary>
  78 + public decimal RewardTechDept { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 成本-其他1
  82 + /// </summary>
  83 + public decimal CostOther1 { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 成本-其他2
  87 + /// </summary>
  88 + public decimal CostOther2 { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 利润
  92 + /// </summary>
  93 + public decimal Profit { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 创建时间
  97 + /// </summary>
  98 + public DateTime CreateTime { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 更新时间
  102 + /// </summary>
  103 + public DateTime? UpdateTime { get; set; }
  104 + }
  105 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqShareStatisticsTechDept/ShareStatisticsTechDeptQueryInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqShareStatisticsTechDept
  2 +{
  3 + /// <summary>
  4 + /// 科技部股份统计查询输入
  5 + /// </summary>
  6 + public class ShareStatisticsTechDeptQueryInput
  7 + {
  8 + /// <summary>
  9 + /// 统计月份(YYYYMM)
  10 + /// </summary>
  11 + public string StatisticsMonth { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 部门名称
  15 + /// </summary>
  16 + public string DepartmentName { get; set; }
  17 + }
  18 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_contract_monthly_cost/LqContractMonthlyCostEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_contract_monthly_cost
  6 +{
  7 + /// <summary>
  8 + /// 合同成本按月统计表
  9 + /// </summary>
  10 + [SugarTable("lq_contract_monthly_cost")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqContractMonthlyCostEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 合同ID(关联lq_contract.F_Id)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_ContractId")]
  24 + public string ContractId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 门店ID(关联lq_mdxx.F_Id,冗余字段便于查询)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_StoreId")]
  30 + public string StoreId { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 店名(冗余字段,便于查询)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_StoreName")]
  36 + public string StoreName { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 分类(冗余字段,便于按分类统计)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_Category")]
  42 + public string Category { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 统计月份(格式:YYYY-MM-01,表示该月的第一天)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_Month")]
  48 + public DateTime Month { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 该月的合同成本(缴租金额 / 交租周期)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_MonthlyCost", DecimalDigits = 2)]
  54 + public decimal MonthlyCost { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 交租周期(冗余字段,便于查询)
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_PaymentCycle")]
  60 + public int PaymentCycle { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 缴租金额(冗余字段,便于查询)
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_PaymentAmount", DecimalDigits = 2)]
  66 + public decimal PaymentAmount { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 是否有效(1-有效,0-无效)
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_IsEffective")]
  72 + public int IsEffective { get; set; } = 1;
  73 +
  74 + /// <summary>
  75 + /// 创建人ID
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_CreateUser")]
  78 + public string CreateUser { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 创建时间
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_CreateTime")]
  84 + public DateTime CreateTime { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 更新时间
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_UpdateTime")]
  90 + public DateTime? UpdateTime { get; set; }
  91 + }
  92 +}
  93 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_deductinfo/LqKdDeductinfoEntity.cs
... ... @@ -36,6 +36,12 @@ namespace NCC.Extend.Entitys.lq_kd_deductinfo
36 36 public string BillingId { get; set; }
37 37  
38 38 /// <summary>
  39 + /// 开单时间(来源:lq_kd_kdjlb.kdrq)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_BillingTime")]
  42 + public DateTime? BillingTime { get; set; }
  43 +
  44 + /// <summary>
39 45 /// 合计金额
40 46 /// </summary>
41 47 [SugarColumn(ColumnName = "F_Amount")]
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_laundry_flow/LqLaundryFlowEntity.cs
... ... @@ -88,6 +88,18 @@ namespace NCC.Extend.Entitys.lq_laundry_flow
88 88 /// </summary>
89 89 [SugarColumn(ColumnName = "F_CreateTime")]
90 90 public DateTime CreateTime { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 送出时间(流水类型为0时使用)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_SendTime")]
  96 + public DateTime? SendTime { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 送回时间(流水类型为1时使用)
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_ReturnTime")]
  102 + public DateTime? ReturnTime { get; set; }
91 103 }
92 104 }
93 105  
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_hq/LqShareStatisticsHqEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_share_statistics_hq
  6 +{
  7 + /// <summary>
  8 + /// 总部股份统计表
  9 + /// </summary>
  10 + [SugarTable("lq_share_statistics_hq")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqShareStatisticsHqEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 统计月份(YYYYMM)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_StatisticsMonth")]
  24 + public string StatisticsMonth { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 收入-全部(开单业绩9%)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_IncomeGeneral")]
  30 + public decimal IncomeGeneral { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 收入-科技部(科美30%的9%)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_IncomeTechDept")]
  36 + public decimal IncomeTechDept { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 成本-报销(总部费用)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_CostReimbursement")]
  42 + public decimal CostReimbursement { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 成本-人工(保留)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_CostLabor")]
  48 + public decimal CostLabor { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 成本-教育部房租
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_CostEducationRent")]
  54 + public decimal CostEducationRent { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 成本-仓库房租
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_CostWarehouseRent")]
  60 + public decimal CostWarehouseRent { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 成本-总部房租
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_CostHQRent")]
  66 + public decimal CostHQRent { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 总部运营利润
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_OperationalProfit")]
  72 + public decimal OperationalProfit { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 创建时间
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_CreateTime")]
  78 + public DateTime CreateTime { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 更新时间
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_UpdateTime")]
  84 + public DateTime? UpdateTime { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 创建人
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_CreateUser")]
  90 + public string CreateUser { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 更新人
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_UpdateUser")]
  96 + public string UpdateUser { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 是否有效(1:有效 0:无效)
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_IsEffective")]
  102 + public int IsEffective { get; set; } = 1;
  103 + }
  104 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_share_statistics_store
  6 +{
  7 + /// <summary>
  8 + /// 门店股份统计表
  9 + /// </summary>
  10 + [SugarTable("lq_share_statistics_store")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqShareStatisticsStoreEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_StoreId")]
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 门店名称
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_StoreName")]
  30 + public string StoreName { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 统计月份(YYYYMM)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_StatisticsMonth")]
  36 + public string StatisticsMonth { get; set; }
  37 +
  38 + // 收入部分
  39 + /// <summary>
  40 + /// 主营收入(消耗总业绩, 不扣减退款)
  41 + /// </summary>
  42 + [SugarColumn(ColumnName = "F_MainIncome")]
  43 + public decimal MainIncome { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 生美消耗业绩
  47 + /// </summary>
  48 + [SugarColumn(ColumnName = "F_ConsumeLifeBeauty")]
  49 + public decimal ConsumeLifeBeauty { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 科美消耗业绩
  53 + /// </summary>
  54 + [SugarColumn(ColumnName = "F_ConsumeTechBeauty")]
  55 + public decimal ConsumeTechBeauty { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 医美消耗业绩
  59 + /// </summary>
  60 + [SugarColumn(ColumnName = "F_ConsumeMedicalBeauty")]
  61 + public decimal ConsumeMedicalBeauty { get; set; }
  62 +
  63 + /// <summary>
  64 + /// 合作消费业绩
  65 + /// </summary>
  66 + [SugarColumn(ColumnName = "F_ConsumeCooperation")]
  67 + public decimal ConsumeCooperation { get; set; }
  68 +
  69 + /// <summary>
  70 + /// 产品消耗业绩
  71 + /// </summary>
  72 + [SugarColumn(ColumnName = "F_ConsumeProduct")]
  73 + public decimal ConsumeProduct { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 其他消耗业绩
  77 + /// </summary>
  78 + [SugarColumn(ColumnName = "F_ConsumeOther")]
  79 + public decimal ConsumeOther { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 其他收入(退款差额>0)
  83 + /// </summary>
  84 + [SugarColumn(ColumnName = "F_OtherIncome")]
  85 + public decimal OtherIncome { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 预收款(开单实付)
  89 + /// </summary>
  90 + [SugarColumn(ColumnName = "F_AdvanceReceipt")]
  91 + public decimal AdvanceReceipt { get; set; }
  92 +
  93 + /// <summary>
  94 + /// 实际退款(实退业绩)
  95 + /// </summary>
  96 + [SugarColumn(ColumnName = "F_Refund")]
  97 + public decimal Refund { get; set; }
  98 +
  99 + /// <summary>
  100 + /// 应收(合作医院)
  101 + /// </summary>
  102 + [SugarColumn(ColumnName = "F_Receivables")]
  103 + public decimal Receivables { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 银行存款(开单实付-应收)
  107 + /// </summary>
  108 + [SugarColumn(ColumnName = "F_BankDeposit")]
  109 + public decimal BankDeposit { get; set; }
  110 +
  111 + // 成本部分
  112 + /// <summary>
  113 + /// 主营成本-产品(仓库领取)
  114 + /// </summary>
  115 + [SugarColumn(ColumnName = "F_CostProduct")]
  116 + public decimal CostProduct { get; set; }
  117 +
  118 + /// <summary>
  119 + /// 主营成本-福田(福田仓库领取)
  120 + /// </summary>
  121 + [SugarColumn(ColumnName = "F_CostFutian")]
  122 + public decimal CostFutian { get; set; }
  123 +
  124 + /// <summary>
  125 + /// 主营成本-毛巾(清洗送出)
  126 + /// </summary>
  127 + [SugarColumn(ColumnName = "F_CostTowel")]
  128 + public decimal CostTowel { get; set; }
  129 +
  130 + /// <summary>
  131 + /// 主营成本-科技部(科美业绩30%)
  132 + /// </summary>
  133 + [SugarColumn(ColumnName = "F_CostTechDept")]
  134 + public decimal CostTechDept { get; set; }
  135 +
  136 + /// <summary>
  137 + /// 主营成本-管理费(总业绩9%)
  138 + /// </summary>
  139 + [SugarColumn(ColumnName = "F_CostManagementFee")]
  140 + public decimal CostManagementFee { get; set; }
  141 +
  142 + /// <summary>
  143 + /// 主营成本-合作(保留)
  144 + /// </summary>
  145 + [SugarColumn(ColumnName = "F_CostCooperation")]
  146 + public decimal CostCooperation { get; set; }
  147 +
  148 + /// <summary>
  149 + /// 主营成本-大项目(保留)
  150 + /// </summary>
  151 + [SugarColumn(ColumnName = "F_CostMajorProject")]
  152 + public decimal CostMajorProject { get; set; }
  153 +
  154 + /// <summary>
  155 + /// 其他成本(退款差额<0)
  156 + /// </summary>
  157 + [SugarColumn(ColumnName = "F_CostOther")]
  158 + public decimal CostOther { get; set; }
  159 +
  160 + // 人工工资部分
  161 + /// <summary>
  162 + /// 人工工资-健康师底薪
  163 + /// </summary>
  164 + [SugarColumn(ColumnName = "F_SalaryBaseHealthCoach")]
  165 + public decimal SalaryBaseHealthCoach { get; set; }
  166 +
  167 + /// <summary>
  168 + /// 人工工资-店助底薪
  169 + /// </summary>
  170 + [SugarColumn(ColumnName = "F_SalaryBaseAssistant")]
  171 + public decimal SalaryBaseAssistant { get; set; }
  172 +
  173 + /// <summary>
  174 + /// 人工工资-店助主任底薪
  175 + /// </summary>
  176 + [SugarColumn(ColumnName = "F_SalaryBaseDirector")]
  177 + public decimal SalaryBaseDirector { get; set; }
  178 +
  179 + /// <summary>
  180 + /// 人工工资-店长底薪
  181 + /// </summary>
  182 + [SugarColumn(ColumnName = "F_SalaryBaseStoreManager")]
  183 + public decimal SalaryBaseStoreManager { get; set; }
  184 +
  185 + /// <summary>
  186 + /// 人工工资-总经理/经理底薪
  187 + /// </summary>
  188 + [SugarColumn(ColumnName = "F_SalaryBaseGeneralManager")]
  189 + public decimal SalaryBaseGeneralManager { get; set; }
  190 +
  191 + /// <summary>
  192 + /// 人工工资-健康师提成
  193 + /// </summary>
  194 + [SugarColumn(ColumnName = "F_SalaryCommissionHealthCoach")]
  195 + public decimal SalaryCommissionHealthCoach { get; set; }
  196 +
  197 + /// <summary>
  198 + /// 人工工资-店助提成
  199 + /// </summary>
  200 + [SugarColumn(ColumnName = "F_SalaryCommissionAssistant")]
  201 + public decimal SalaryCommissionAssistant { get; set; }
  202 +
  203 + /// <summary>
  204 + /// 人工工资-店助主任提成
  205 + /// </summary>
  206 + [SugarColumn(ColumnName = "F_SalaryCommissionDirector")]
  207 + public decimal SalaryCommissionDirector { get; set; }
  208 +
  209 + /// <summary>
  210 + /// 人工工资-店长提成
  211 + /// </summary>
  212 + [SugarColumn(ColumnName = "F_SalaryCommissionStoreManager")]
  213 + public decimal SalaryCommissionStoreManager { get; set; }
  214 +
  215 + /// <summary>
  216 + /// 人工工资-总经理/经理提成
  217 + /// </summary>
  218 + [SugarColumn(ColumnName = "F_SalaryCommissionGeneralManager")]
  219 + public decimal SalaryCommissionGeneralManager { get; set; }
  220 +
  221 + /// <summary>
  222 + /// 人工工资-手工
  223 + /// </summary>
  224 + [SugarColumn(ColumnName = "F_SalaryManual")]
  225 + public decimal SalaryManual { get; set; }
  226 +
  227 + /// <summary>
  228 + /// 人工工资-出勤(保留)
  229 + /// </summary>
  230 + [SugarColumn(ColumnName = "F_SalaryAttendance")]
  231 + public decimal SalaryAttendance { get; set; }
  232 +
  233 + /// <summary>
  234 + /// 人工工资-岗位-手机保管费
  235 + /// </summary>
  236 + [SugarColumn(ColumnName = "F_SalaryPhoneCustody")]
  237 + public decimal SalaryPhoneCustody { get; set; }
  238 +
  239 + /// <summary>
  240 + /// 人工工资-岗位-人头
  241 + /// </summary>
  242 + [SugarColumn(ColumnName = "F_SalaryHeadcountReward")]
  243 + public decimal SalaryHeadcountReward { get; set; }
  244 +
  245 + /// <summary>
  246 + /// 人工工资-门店T区
  247 + /// </summary>
  248 + [SugarColumn(ColumnName = "F_SalaryTZone")]
  249 + public decimal SalaryTZone { get; set; }
  250 +
  251 + // 费用与其他
  252 + /// <summary>
  253 + /// 社保(保留)
  254 + /// </summary>
  255 + [SugarColumn(ColumnName = "F_SocialSecurity")]
  256 + public decimal SocialSecurity { get; set; }
  257 +
  258 + /// <summary>
  259 + /// 门店房租
  260 + /// </summary>
  261 + [SugarColumn(ColumnName = "F_StoreRent")]
  262 + public decimal StoreRent { get; set; }
  263 +
  264 + /// <summary>
  265 + /// 宿舍房租(保留)
  266 + /// </summary>
  267 + [SugarColumn(ColumnName = "F_DormRent")]
  268 + public decimal DormRent { get; set; }
  269 +
  270 + /// <summary>
  271 + /// 当期费用(报销)
  272 + /// </summary>
  273 + [SugarColumn(ColumnName = "F_CurrentPeriodExpense")]
  274 + public decimal CurrentPeriodExpense { get; set; }
  275 +
  276 + /// <summary>
  277 + /// 当前费用-秦董(保留)
  278 + /// </summary>
  279 + [SugarColumn(ColumnName = "F_CurrentPeriodExpenseQin")]
  280 + public decimal CurrentPeriodExpenseQin { get; set; }
  281 +
  282 + /// <summary>
  283 + /// 财务费用(保留)
  284 + /// </summary>
  285 + [SugarColumn(ColumnName = "F_FinancialFee")]
  286 + public decimal FinancialFee { get; set; }
  287 +
  288 + // 奖励
  289 + /// <summary>
  290 + /// 奖励-医美(保留)
  291 + /// </summary>
  292 + [SugarColumn(ColumnName = "F_RewardMedicalBeauty")]
  293 + public decimal RewardMedicalBeauty { get; set; }
  294 +
  295 + /// <summary>
  296 + /// 奖励-其他(保留)
  297 + /// </summary>
  298 + [SugarColumn(ColumnName = "F_RewardOther")]
  299 + public decimal RewardOther { get; set; }
  300 +
  301 + /// <summary>
  302 + /// 奖励-大单奖(保留)
  303 + /// </summary>
  304 + [SugarColumn(ColumnName = "F_RewardLargeOrder")]
  305 + public decimal RewardLargeOrder { get; set; }
  306 +
  307 + /// <summary>
  308 + /// 奖励-首单奖(保留)
  309 + /// </summary>
  310 + [SugarColumn(ColumnName = "F_RewardFirstOrder")]
  311 + public decimal RewardFirstOrder { get; set; }
  312 +
  313 + /// <summary>
  314 + /// 奖励(保留)
  315 + /// </summary>
  316 + [SugarColumn(ColumnName = "F_RewardGeneral")]
  317 + public decimal RewardGeneral { get; set; }
  318 +
  319 + // 其他保留字段
  320 + /// <summary>
  321 + /// 其他1(保留)
  322 + /// </summary>
  323 + [SugarColumn(ColumnName = "F_Other1")]
  324 + public decimal Other1 { get; set; }
  325 +
  326 + /// <summary>
  327 + /// 其他2(保留)
  328 + /// </summary>
  329 + [SugarColumn(ColumnName = "F_Other2")]
  330 + public decimal Other2 { get; set; }
  331 +
  332 + // 利润结果
  333 + /// <summary>
  334 + /// 利润(预收-实退-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)
  335 + /// </summary>
  336 + [SugarColumn(ColumnName = "F_Profit")]
  337 + public decimal Profit { get; set; }
  338 +
  339 + /// <summary>
  340 + /// 财务利润(主营-其他收入-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)
  341 + /// </summary>
  342 + [SugarColumn(ColumnName = "F_FinancialProfit")]
  343 + public decimal FinancialProfit { get; set; }
  344 +
  345 + // 基础字段
  346 + /// <summary>
  347 + /// 创建时间
  348 + /// </summary>
  349 + [SugarColumn(ColumnName = "F_CreateTime")]
  350 + public DateTime? CreateTime { get; set; }
  351 +
  352 + /// <summary>
  353 + /// 更新时间
  354 + /// </summary>
  355 + [SugarColumn(ColumnName = "F_UpdateTime")]
  356 + public DateTime? UpdateTime { get; set; }
  357 +
  358 + /// <summary>
  359 + /// 创建人
  360 + /// </summary>
  361 + [SugarColumn(ColumnName = "F_CreateUser")]
  362 + public string CreateUser { get; set; }
  363 +
  364 + /// <summary>
  365 + /// 更新人
  366 + /// </summary>
  367 + [SugarColumn(ColumnName = "F_UpdateUser")]
  368 + public string UpdateUser { get; set; }
  369 +
  370 + /// <summary>
  371 + /// 是否有效(1:有效 0:无效)
  372 + /// </summary>
  373 + [SugarColumn(ColumnName = "F_IsEffective")]
  374 + public int IsEffective { get; set; }
  375 + }
  376 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_tech_dept/LqShareStatisticsTechDeptEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_share_statistics_tech_dept
  6 +{
  7 + /// <summary>
  8 + /// 科技部股份统计表
  9 + /// </summary>
  10 + [SugarTable("lq_share_statistics_tech_dept")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqShareStatisticsTechDeptEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 部门名称(科技一部/二部)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_DepartmentName")]
  24 + public string DepartmentName { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 统计月份(YYYYMM)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_StatisticsMonth")]
  30 + public string StatisticsMonth { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 收入(门店科美开单30%)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_Income")]
  36 + public decimal Income { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 成本-报销
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_CostReimbursement")]
  42 + public decimal CostReimbursement { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 成本-人工-科技部老师底薪
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_CostTeacherBase")]
  48 + public decimal CostTeacherBase { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 成本-人工-科技部手工费
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_CostTeacherManual")]
  54 + public decimal CostTeacherManual { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 成本-人工-科技部开单提成
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_CostTeacherBillingComm")]
  60 + public decimal CostTeacherBillingComm { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 成本-人工-科技部消耗提成
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_CostTeacherConsumeComm")]
  66 + public decimal CostTeacherConsumeComm { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 成本-人工-科技部专家提成(保留)
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_CostTeacherExpertComm")]
  72 + public decimal CostTeacherExpertComm { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 成本-人工-科技部加班(保留)
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_CostTeacherOvertime")]
  78 + public decimal CostTeacherOvertime { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 成本-人工-科技部总经理底薪
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_CostGMBase")]
  84 + public decimal CostGMBase { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 成本-人工-科技部总经理提成
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_CostGMComm")]
  90 + public decimal CostGMComm { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 奖励-科技部(保留)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_RewardTechDept")]
  96 + public decimal RewardTechDept { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 成本-其他1(保留)
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_CostOther1")]
  102 + public decimal CostOther1 { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 成本-其他2(保留)
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_CostOther2")]
  108 + public decimal CostOther2 { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 科技部利润
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_Profit")]
  114 + public decimal Profit { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 创建时间
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_CreateTime")]
  120 + public DateTime CreateTime { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 更新时间
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_UpdateTime")]
  126 + public DateTime? UpdateTime { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 创建人
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_CreateUser")]
  132 + public string CreateUser { get; set; }
  133 +
  134 + /// <summary>
  135 + /// 更新人
  136 + /// </summary>
  137 + [SugarColumn(ColumnName = "F_UpdateUser")]
  138 + public string UpdateUser { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 是否有效(1:有效 0:无效)
  142 + /// </summary>
  143 + [SugarColumn(ColumnName = "F_IsEffective")]
  144 + public int IsEffective { get; set; } = 1;
  145 + }
  146 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs
1 1 using System;
2 2 using System.Collections.Generic;
  3 +using System.Globalization;
3 4 using System.Linq;
4 5 using System.Threading.Tasks;
5 6 using Microsoft.AspNetCore.Mvc;
... ... @@ -12,6 +13,7 @@ using NCC.Extend.Entitys.Dto.LqContract;
12 13 using NCC.Extend.Entitys.Enum;
13 14 using NCC.Extend.Entitys.lq_contract;
14 15 using NCC.Extend.Entitys.lq_contract_rent_detail;
  16 +using NCC.Extend.Entitys.lq_contract_monthly_cost;
15 17 using NCC.Extend.Entitys.lq_mdxx;
16 18 using NCC.FriendlyException;
17 19 using NCC.System.Entitys.Permission;
... ... @@ -147,6 +149,9 @@ namespace NCC.Extend
147 149 // 自动生成月租明细
148 150 await GenerateRentDetailsAsync(contractEntity.Id, contractEntity.ContractStartDate, contractEntity.ContractEndDate, contractEntity.PaymentCycle, contractEntity.PaymentAmount);
149 151  
  152 + // 自动生成按月成本记录
  153 + await GenerateMonthlyCostAsync(contractEntity.Id, contractEntity.StoreId, contractEntity.StoreName, contractEntity.Category, contractEntity.ContractStartDate, contractEntity.ContractEndDate, contractEntity.PaymentCycle, contractEntity.PaymentAmount);
  154 +
150 155 // 计算下次应交时间
151 156 await CalculateNextPaymentDate(contractEntity.Id);
152 157  
... ... @@ -249,6 +254,9 @@ namespace NCC.Extend
249 254 contract.PaymentCycle != input.PaymentCycle ||
250 255 contract.PaymentAmount != input.PaymentAmount;
251 256  
  257 + // 判断是否需要更新成本记录的分类(如果只修改了分类,不需要重新生成,只需更新分类字段)
  258 + bool needUpdateCategory = contract.Category != input.Category;
  259 +
252 260 // 更新合同信息
253 261 contract.StoreId = input.StoreId;
254 262 contract.StoreName = store.Dm ?? "";
... ... @@ -287,8 +295,33 @@ namespace NCC.Extend
287 295 .Where(x => x.ContractId == contract.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
288 296 .ExecuteCommandAsync();
289 297  
  298 + // 删除旧的成本记录(逻辑删除)
  299 + await _db.Updateable<LqContractMonthlyCostEntity>()
  300 + .SetColumns(x => new LqContractMonthlyCostEntity
  301 + {
  302 + IsEffective = StatusEnum.无效.GetHashCode(),
  303 + UpdateTime = DateTime.Now
  304 + })
  305 + .Where(x => x.ContractId == contract.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
  306 + .ExecuteCommandAsync();
  307 +
290 308 // 生成新的明细
291 309 await GenerateRentDetailsAsync(contract.Id, contract.ContractStartDate, contract.ContractEndDate, contract.PaymentCycle, contract.PaymentAmount);
  310 +
  311 + // 生成新的成本记录
  312 + await GenerateMonthlyCostAsync(contract.Id, contract.StoreId, contract.StoreName, contract.Category, contract.ContractStartDate, contract.ContractEndDate, contract.PaymentCycle, contract.PaymentAmount);
  313 + }
  314 + else if (needUpdateCategory)
  315 + {
  316 + // 如果只修改了分类,只需更新成本记录的分类字段
  317 + await _db.Updateable<LqContractMonthlyCostEntity>()
  318 + .SetColumns(x => new LqContractMonthlyCostEntity
  319 + {
  320 + Category = contract.Category,
  321 + UpdateTime = DateTime.Now
  322 + })
  323 + .Where(x => x.ContractId == contract.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
  324 + .ExecuteCommandAsync();
292 325 }
293 326  
294 327 // 重新计算下次应交时间
... ... @@ -365,6 +398,16 @@ namespace NCC.Extend
365 398 .Where(x => x.ContractId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
366 399 .ExecuteCommandAsync();
367 400  
  401 + // 删除该合同的所有成本记录(逻辑删除)
  402 + await _db.Updateable<LqContractMonthlyCostEntity>()
  403 + .SetColumns(x => new LqContractMonthlyCostEntity
  404 + {
  405 + IsEffective = StatusEnum.无效.GetHashCode(),
  406 + UpdateTime = DateTime.Now
  407 + })
  408 + .Where(x => x.ContractId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  409 + .ExecuteCommandAsync();
  410 +
368 411 // 删除合同(逻辑删除)
369 412 contract.IsEffective = StatusEnum.无效.GetHashCode();
370 413 contract.UpdateUser = _userManager.UserId;
... ... @@ -792,6 +835,35 @@ namespace NCC.Extend
792 835  
793 836 #endregion
794 837  
  838 + #region 根据合同id获取合同成本列表
  839 + /// <summary>
  840 + /// 根据合同id获取合同成本列表
  841 + /// </summary>
  842 + /// <param name="contractId"></param>
  843 + /// <returns></returns>
  844 + [HttpGet("GetContractCostList")]
  845 + public async Task<dynamic> GetContractCostListAsync([FromQuery] string contractId)
  846 + {
  847 + try
  848 + {
  849 + if (string.IsNullOrWhiteSpace(contractId))
  850 + {
  851 + throw NCCException.Oh("合同ID不能为空");
  852 + }
  853 +
  854 + var costList = await _db.Queryable<LqContractMonthlyCostEntity>()
  855 + .Where(x => x.ContractId == contractId && x.IsEffective == StatusEnum.有效.GetHashCode())
  856 + .ToListAsync();
  857 + return costList;
  858 + }
  859 + catch (Exception ex)
  860 + {
  861 + _logger.LogError(ex, "获取合同成本列表失败");
  862 + throw NCCException.Oh($"获取合同成本列表失败:{ex.Message}");
  863 + }
  864 + }
  865 + #endregion
  866 +
795 867 #region 私有方法
796 868  
797 869 /// <summary>
... ... @@ -842,6 +914,57 @@ namespace NCC.Extend
842 914 }
843 915  
844 916 /// <summary>
  917 + /// 生成按月成本记录
  918 + /// </summary>
  919 + /// <param name="contractId">合同ID</param>
  920 + /// <param name="storeId">门店ID</param>
  921 + /// <param name="storeName">店名</param>
  922 + /// <param name="category">分类</param>
  923 + /// <param name="contractStartDate">合同起始日期</param>
  924 + /// <param name="contractEndDate">合同结束日期</param>
  925 + /// <param name="paymentCycle">交租周期(月)</param>
  926 + /// <param name="paymentAmount">缴租金额</param>
  927 + private async Task GenerateMonthlyCostAsync(string contractId, string storeId, string storeName, string category, DateTime contractStartDate, DateTime contractEndDate, int paymentCycle, decimal paymentAmount)
  928 + {
  929 + // 计算每个月成本 = 缴租金额 / 交租周期
  930 + var monthlyCost = paymentAmount / paymentCycle;
  931 +
  932 + var costRecords = new List<LqContractMonthlyCostEntity>();
  933 + var currentMonth = new DateTime(contractStartDate.Year, contractStartDate.Month, 1);
  934 +
  935 + // 从合同起始月份开始,逐月生成成本记录,直到合同结束月份
  936 + while (currentMonth <= contractEndDate)
  937 + {
  938 + var costRecord = new LqContractMonthlyCostEntity
  939 + {
  940 + Id = YitIdHelper.NextId().ToString(),
  941 + ContractId = contractId,
  942 + StoreId = storeId,
  943 + StoreName = storeName,
  944 + Category = category,
  945 + Month = currentMonth,
  946 + MonthlyCost = monthlyCost,
  947 + PaymentCycle = paymentCycle,
  948 + PaymentAmount = paymentAmount,
  949 + IsEffective = StatusEnum.有效.GetHashCode(),
  950 + CreateUser = _userManager.UserId,
  951 + CreateTime = DateTime.Now
  952 + };
  953 +
  954 + costRecords.Add(costRecord);
  955 +
  956 + // 计算下一个月
  957 + currentMonth = currentMonth.AddMonths(1);
  958 + }
  959 +
  960 + // 批量插入
  961 + if (costRecords.Any())
  962 + {
  963 + await _db.Insertable(costRecords).ExecuteCommandAsync();
  964 + }
  965 + }
  966 +
  967 + /// <summary>
845 968 /// 计算下次应交时间(匿名方法)
846 969 /// </summary>
847 970 /// <param name="contractId">合同ID</param>
... ... @@ -888,6 +1011,152 @@ namespace NCC.Extend
888 1011  
889 1012 #endregion
890 1013  
  1014 + #region 获取合同成本按月统计
  1015 +
  1016 + /// <summary>
  1017 + /// 获取合同成本按月统计
  1018 + /// </summary>
  1019 + /// <remarks>
  1020 + /// 根据合同ID、门店ID、月份等条件查询合同成本按月统计数据
  1021 + ///
  1022 + /// 示例请求:
  1023 + /// ```
  1024 + /// GET /api/Extend/LqContract/GetMonthlyCost?contractId=合同ID&storeId=门店ID&startMonth=2025-01&endMonth=2025-12
  1025 + /// ```
  1026 + ///
  1027 + /// 参数说明:
  1028 + /// - contractId: 合同ID(可选)
  1029 + /// - storeId: 门店ID(可选)
  1030 + /// - startMonth: 开始月份(格式:YYYY-MM,可选)
  1031 + /// - endMonth: 结束月份(格式:YYYY-MM,可选)
  1032 + /// </remarks>
  1033 + /// <param name="contractId">合同ID(可选)</param>
  1034 + /// <param name="storeId">门店ID(可选)</param>
  1035 + /// <param name="category">分类(可选)</param>
  1036 + /// <param name="startMonth">开始月份(格式:YYYY-MM,可选)</param>
  1037 + /// <param name="endMonth">结束月份(格式:YYYY-MM,可选)</param>
  1038 + /// <returns>合同成本按月统计列表</returns>
  1039 + /// <response code="200">查询成功</response>
  1040 + /// <response code="500">服务器错误</response>
  1041 + [HttpGet("GetMonthlyCost")]
  1042 + public async Task<dynamic> GetMonthlyCostAsync(
  1043 + [FromQuery] string contractId = null,
  1044 + [FromQuery] string storeId = null,
  1045 + [FromQuery] string category = null,
  1046 + [FromQuery] string startMonth = null,
  1047 + [FromQuery] string endMonth = null)
  1048 + {
  1049 + try
  1050 + {
  1051 + var query = _db.Queryable<LqContractMonthlyCostEntity>()
  1052 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  1053 + .WhereIF(!string.IsNullOrWhiteSpace(contractId), x => x.ContractId == contractId)
  1054 + .WhereIF(!string.IsNullOrWhiteSpace(storeId), x => x.StoreId == storeId)
  1055 + .WhereIF(!string.IsNullOrWhiteSpace(category), x => x.Category == category);
  1056 +
  1057 + // 处理月份范围筛选
  1058 + if (!string.IsNullOrWhiteSpace(startMonth))
  1059 + {
  1060 + if (DateTime.TryParseExact(startMonth, "yyyy-MM", null, DateTimeStyles.None, out DateTime startDate))
  1061 + {
  1062 + var startMonthDate = new DateTime(startDate.Year, startDate.Month, 1);
  1063 + query = query.Where(x => x.Month >= startMonthDate);
  1064 + }
  1065 + }
  1066 +
  1067 + if (!string.IsNullOrWhiteSpace(endMonth))
  1068 + {
  1069 + if (DateTime.TryParseExact(endMonth, "yyyy-MM", null, DateTimeStyles.None, out DateTime endDate))
  1070 + {
  1071 + var endMonthDate = new DateTime(endDate.Year, endDate.Month, DateTime.DaysInMonth(endDate.Year, endDate.Month));
  1072 + query = query.Where(x => x.Month <= endMonthDate);
  1073 + }
  1074 + }
  1075 +
  1076 + var data = await query
  1077 + .OrderBy(x => x.Month)
  1078 + .Select(x => new
  1079 + {
  1080 + id = x.Id,
  1081 + contractId = x.ContractId,
  1082 + storeId = x.StoreId,
  1083 + storeName = x.StoreName,
  1084 + category = x.Category,
  1085 + month = x.Month.ToString("yyyy-MM"),
  1086 + monthlyCost = x.MonthlyCost,
  1087 + paymentCycle = x.PaymentCycle,
  1088 + paymentAmount = x.PaymentAmount,
  1089 + createTime = x.CreateTime
  1090 + })
  1091 + .ToListAsync();
  1092 +
  1093 + return new { code = 200, msg = "查询成功", data = data };
  1094 + }
  1095 + catch (Exception ex)
  1096 + {
  1097 + _logger.LogError(ex, "获取合同成本按月统计失败");
  1098 + throw NCCException.Oh($"查询失败:{ex.Message}");
  1099 + }
  1100 + }
  1101 +
  1102 + /// <summary>
  1103 + /// 按门店和月份统计合同成本
  1104 + /// </summary>
  1105 + /// <remarks>
  1106 + /// 根据门店ID和月份统计该门店的合同成本总和
  1107 + ///
  1108 + /// 示例请求:
  1109 + /// ```
  1110 + /// GET /api/Extend/LqContract/GetStoreMonthlyCost?storeId=门店ID&month=2025-11
  1111 + /// ```
  1112 + /// </remarks>
  1113 + /// <param name="storeId">门店ID</param>
  1114 + /// <param name="month">月份(格式:YYYY-MM)</param>
  1115 + /// <returns>该门店该月的合同成本总和</returns>
  1116 + /// <response code="200">查询成功</response>
  1117 + /// <response code="400">参数错误</response>
  1118 + /// <response code="500">服务器错误</response>
  1119 + [HttpGet("GetStoreMonthlyCost")]
  1120 + public async Task<dynamic> GetStoreMonthlyCostAsync(
  1121 + [FromQuery] string storeId,
  1122 + [FromQuery] string month)
  1123 + {
  1124 + try
  1125 + {
  1126 + if (string.IsNullOrWhiteSpace(storeId))
  1127 + {
  1128 + throw NCCException.Oh("门店ID不能为空");
  1129 + }
  1130 +
  1131 + if (string.IsNullOrWhiteSpace(month))
  1132 + {
  1133 + throw NCCException.Oh("月份不能为空");
  1134 + }
  1135 +
  1136 + if (!DateTime.TryParseExact(month, "yyyy-MM", null, DateTimeStyles.None, out DateTime monthDate))
  1137 + {
  1138 + throw NCCException.Oh("月份格式错误,应为 YYYY-MM");
  1139 + }
  1140 +
  1141 + var monthStart = new DateTime(monthDate.Year, monthDate.Month, 1);
  1142 +
  1143 + var totalCost = await _db.Queryable<LqContractMonthlyCostEntity>()
  1144 + .Where(x => x.StoreId == storeId
  1145 + && x.Month == monthStart
  1146 + && x.IsEffective == StatusEnum.有效.GetHashCode())
  1147 + .SumAsync(x => x.MonthlyCost);
  1148 +
  1149 + return new { code = 200, msg = "查询成功", data = new { storeId = storeId, month = month, totalCost = totalCost } };
  1150 + }
  1151 + catch (Exception ex)
  1152 + {
  1153 + _logger.LogError(ex, "按门店和月份统计合同成本失败");
  1154 + throw NCCException.Oh($"查询失败:{ex.Message}");
  1155 + }
  1156 + }
  1157 +
  1158 + #endregion
  1159 +
891 1160 #region 统计门店合同费用
892 1161  
893 1162 /// <summary>
... ... @@ -960,13 +1229,13 @@ namespace NCC.Extend
960 1229 var details = await _db.Queryable<LqContractRentDetailEntity, LqContractEntity>(
961 1230 (detail, contract) => new JoinQueryInfos(
962 1231 JoinType.Inner, detail.ContractId == contract.Id))
963   - .Where((detail, contract) =>
  1232 + .Where((detail, contract) =>
964 1233 contract.StoreId == input.StoreId &&
965 1234 contract.IsEffective == StatusEnum.有效.GetHashCode() &&
966 1235 detail.IsEffective == StatusEnum.有效.GetHashCode() &&
967 1236 detail.PaymentMonth >= monthStart &&
968 1237 detail.PaymentMonth <= monthEnd)
969   - .WhereIF(input.Categories != null && input.Categories.Length > 0,
  1238 + .WhereIF(input.Categories != null && input.Categories.Length > 0,
970 1239 (detail, contract) => contract.Category != null && input.Categories.Contains(contract.Category))
971 1240 .Select((detail, contract) => new
972 1241 {
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
... ... @@ -706,14 +706,15 @@ namespace NCC.Extend
706 706 }
707 707  
708 708 // 2.3.1 查询开单业绩(按品项类型)
  709 + // 使用targetDict来关联,确保每个门店只关联一条目标记录,避免重复统计
  710 + // 先按门店统计业绩,再按部门汇总
709 711 var billingSql = $@"
710 712 SELECT
711   - target.{deptField} as TargetDeptId,
  713 + billing.djmd as StoreId,
712 714 COALESCE(SUM(pxmx.F_ActualPrice), 0) as StoreAmount
713 715 FROM lq_kd_pxmx pxmx
714 716 INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
715 717 INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
716   - INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}'
717 718 WHERE pxmx.F_IsEffective = 1
718 719 AND billing.F_IsEffective = 1
719 720 AND item.F_IsEffective = 1
... ... @@ -721,35 +722,48 @@ namespace NCC.Extend
721 722 AND billing.djmd IN ('{storeIdsStr}')
722 723 AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
723 724 AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
724   - GROUP BY target.{deptField}";
  725 + GROUP BY billing.djmd";
725 726  
726 727 var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql);
727 728  
728   - // 分配开单业绩到对应部门
  729 + // 分配开单业绩到对应部门(使用targetDict确保每个门店只关联一条目标记录)
729 730 foreach (var billing in billingData ?? Enumerable.Empty<dynamic>())
730 731 {
731 732 var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m;
732   - var targetDeptId = billing?.TargetDeptId?.ToString();
  733 + var storeId = billing?.StoreId?.ToString();
733 734  
734   - if (storeAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
  735 + if (storeAmount <= 0 || string.IsNullOrEmpty(storeId))
735 736 continue;
736 737  
737   - // 只分配给在查询列表中的部门,避免重复统计
738   - if (departmentDict.ContainsKey(targetDeptId))
  738 + // 从targetDict中获取门店对应的部门信息
  739 + if (targetDict.ContainsKey(storeId))
739 740 {
740   - departmentDict[targetDeptId].BillingPerformance += storeAmount;
  741 + var target = targetDict[storeId];
  742 + // 动态获取部门字段值
  743 + string targetDeptId = null;
  744 + if (deptField == "F_EducationDepartment")
  745 + targetDeptId = target?.F_EducationDepartment?.ToString();
  746 + else if (deptField == "F_TechDepartment")
  747 + targetDeptId = target?.F_TechDepartment?.ToString();
  748 + else if (deptField == "F_MajorProjectDepartment")
  749 + targetDeptId = target?.F_MajorProjectDepartment?.ToString();
  750 +
  751 + if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
  752 + {
  753 + departmentDict[targetDeptId].BillingPerformance += storeAmount;
  754 + }
741 755 }
742 756 }
743 757  
744 758 // 2.3.2 查询退卡业绩(按品项类型)
  759 + // 使用targetDict来关联,确保每个门店只关联一条目标记录,避免重复统计
745 760 var refundSql = $@"
746 761 SELECT
747   - target.{deptField} as TargetDeptId,
  762 + refund.md as StoreId,
748 763 COALESCE(SUM(refund_mx.tkje), 0) as StoreRefundAmount
749 764 FROM lq_hytk_mx refund_mx
750 765 INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id
751 766 INNER JOIN lq_xmzl item ON refund_mx.px = item.F_Id
752   - INNER JOIN lq_md_target target ON refund.md = target.F_StoreId AND target.F_Month = '{month}'
753 767 WHERE refund_mx.F_IsEffective = 1
754 768 AND refund.F_IsEffective = 1
755 769 AND item.F_IsEffective = 1
... ... @@ -757,59 +771,86 @@ namespace NCC.Extend
757 771 AND refund.md IN ('{storeIdsStr}')
758 772 AND refund.tksj >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
759 773 AND refund.tksj < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
760   - GROUP BY target.{deptField}";
  774 + GROUP BY refund.md";
761 775  
762 776 var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql);
763 777  
764   - // 分配退卡业绩到对应部门
  778 + // 分配退卡业绩到对应部门(使用targetDict确保每个门店只关联一条目标记录)
765 779 foreach (var refund in refundData ?? Enumerable.Empty<dynamic>())
766 780 {
767 781 var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m;
768   - var targetDeptId = refund?.TargetDeptId?.ToString();
  782 + var storeId = refund?.StoreId?.ToString();
769 783  
770   - if (storeRefundAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
  784 + if (storeRefundAmount <= 0 || string.IsNullOrEmpty(storeId))
771 785 continue;
772 786  
773   - // 只分配给在查询列表中的部门,避免重复统计
774   - if (departmentDict.ContainsKey(targetDeptId))
  787 + // 从targetDict中获取门店对应的部门信息
  788 + if (targetDict.ContainsKey(storeId))
775 789 {
776   - departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
  790 + var target = targetDict[storeId];
  791 + // 动态获取部门字段值
  792 + string targetDeptId = null;
  793 + if (deptField == "F_EducationDepartment")
  794 + targetDeptId = target?.F_EducationDepartment?.ToString();
  795 + else if (deptField == "F_TechDepartment")
  796 + targetDeptId = target?.F_TechDepartment?.ToString();
  797 + else if (deptField == "F_MajorProjectDepartment")
  798 + targetDeptId = target?.F_MajorProjectDepartment?.ToString();
  799 +
  800 + if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
  801 + {
  802 + departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
  803 + }
777 804 }
778 805 }
779 806  
780 807 // 2.3.3 查询储扣金额(按品项类型)
  808 + // 使用储扣记录表中的开单时间(F_BillingTime)进行时间过滤,如果为空则使用开单记录表的开单时间(kdrq)
  809 + // 使用targetDict来关联,确保每个门店只关联一条目标记录,避免重复统计
781 810 var deductSql = $@"
782 811 SELECT
783   - target.{deptField} as TargetDeptId,
  812 + billing.djmd as StoreId,
784 813 COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount
785 814 FROM lq_kd_deductinfo deduct
786 815 INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
787 816 INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
788   - INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}'
789 817 WHERE deduct.F_IsEffective = 1
790 818 AND billing.F_IsEffective = 1
791 819 AND item.F_IsEffective = 1
792 820 AND item.qt2 = '{itemType}'
793 821 AND billing.djmd IN ('{storeIdsStr}')
794   - AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
795   - AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
796   - GROUP BY target.{deptField}";
  822 + AND COALESCE(deduct.F_BillingTime, billing.kdrq) >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
  823 + AND COALESCE(deduct.F_BillingTime, billing.kdrq) < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
  824 + GROUP BY billing.djmd";
797 825  
798 826 var deductData = await _db.Ado.SqlQueryAsync<dynamic>(deductSql);
799 827  
800   - // 分配储扣金额到对应部门
  828 + // 分配储扣金额到对应部门(使用targetDict确保每个门店只关联一条目标记录)
801 829 foreach (var deduct in deductData ?? Enumerable.Empty<dynamic>())
802 830 {
803 831 var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m;
804   - var targetDeptId = deduct?.TargetDeptId?.ToString();
  832 + var storeId = deduct?.StoreId?.ToString();
805 833  
806   - if (storeDeductAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
  834 + if (storeDeductAmount <= 0 || string.IsNullOrEmpty(storeId))
807 835 continue;
808 836  
809   - // 只累加到在查询列表中的部门,避免重复统计
810   - if (departmentDict.ContainsKey(targetDeptId))
  837 + // 从targetDict中获取门店对应的部门信息
  838 + if (targetDict.ContainsKey(storeId))
811 839 {
812   - departmentDict[targetDeptId].DeductAmount += storeDeductAmount;
  840 + var target = targetDict[storeId];
  841 + // 动态获取部门字段值
  842 + string targetDeptId = null;
  843 + if (deptField == "F_EducationDepartment")
  844 + targetDeptId = target?.F_EducationDepartment?.ToString();
  845 + else if (deptField == "F_TechDepartment")
  846 + targetDeptId = target?.F_TechDepartment?.ToString();
  847 + else if (deptField == "F_MajorProjectDepartment")
  848 + targetDeptId = target?.F_MajorProjectDepartment?.ToString();
  849 +
  850 + if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
  851 + {
  852 + departmentDict[targetDeptId].DeductAmount += storeDeductAmount;
  853 + }
813 854 }
814 855 }
815 856 }
... ... @@ -1801,6 +1842,7 @@ namespace NCC.Extend
1801 1842 var (startDate, endDate) = GetTimeRange(input.StartTime, input.EndTime);
1802 1843  
1803 1844 // 统计储扣金额,按品项分类分组
  1845 + // 使用储扣记录表中的开单时间(F_BillingTime)进行时间过滤,如果为空则使用开单记录表的开单时间(kdrq)
1804 1846 var sql = $@"
1805 1847 SELECT
1806 1848 COALESCE(SUM(CASE WHEN item.qt2 = '医美' THEN deduct.F_Amount ELSE 0 END), 0) as YiMeiAmount,
... ... @@ -1813,8 +1855,8 @@ namespace NCC.Extend
1813 1855 LEFT JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id AND item.F_IsEffective = 1
1814 1856 WHERE deduct.F_IsEffective = 1
1815 1857 AND billing.F_IsEffective = 1
1816   - AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00'
1817   - AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'";
  1858 + AND COALESCE(deduct.F_BillingTime, billing.kdrq) >= '{startDate:yyyy-MM-dd} 00:00:00'
  1859 + AND COALESCE(deduct.F_BillingTime, billing.kdrq) < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'";
1818 1860  
1819 1861 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql);
1820 1862 var data = result.FirstOrDefault();
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
... ... @@ -296,6 +296,7 @@ namespace NCC.Extend
296 296 .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ExpenseAmount ?? 0));
297 297  
298 298 // 1.12 洗毛巾费用统计(只统计送出的记录,F_FlowType = 0)
  299 + // 优先使用送出时间(F_SendTime),如果为空则使用创建时间(F_CreateTime)
299 300 var laundryCostSql = $@"
300 301 SELECT
301 302 F_StoreId as StoreId,
... ... @@ -303,7 +304,7 @@ namespace NCC.Extend
303 304 FROM lq_laundry_flow
304 305 WHERE F_IsEffective = 1
305 306 AND F_FlowType = 0
306   - AND DATE_FORMAT(F_CreateTime, '%Y%m') = @monthStr
  307 + AND DATE_FORMAT(COALESCE(F_SendTime, F_CreateTime), '%Y%m') = @monthStr
307 308 GROUP BY F_StoreId";
308 309  
309 310 var laundryCostData = await _db.Ado.SqlQueryAsync<dynamic>(laundryCostSql, new { monthStr });
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
... ... @@ -186,8 +186,8 @@ namespace NCC.Extend
186 186 _db.Ado.BeginTran();
187 187 try
188 188 {
189   - var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync();
190   - if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  189 + var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync();
  190 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
191 191  
192 192 // 计算并更新产品的平均单价(加权平均成本法)
193 193 // 普通入库和采购入库都需要更新平均单价
... ... @@ -398,8 +398,8 @@ namespace NCC.Extend
398 398 _db.Ado.BeginTran();
399 399 try
400 400 {
401   - var isOk = await _db.Updateable(existingInventory).ExecuteCommandAsync();
402   - if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  401 + var isOk = await _db.Updateable(existingInventory).ExecuteCommandAsync();
  402 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
403 403  
404 404 // 如果数量或单价发生变化,需要重新计算平均单价
405 405 // 注意:更新库存时,如果数量或单价变化,需要重新计算整个产品的平均单价
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
... ... @@ -503,6 +503,7 @@ namespace NCC.Extend
503 503 productName = product.ProductName,
504 504 productCategory = product.ProductCategory,
505 505 productPrice = product.Price,
  506 + productWarehouse = product.Warehouse, // 产品归属仓库
506 507 storeId = u.StoreId,
507 508 storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(store => store.Id == u.StoreId).Select(store => store.Dm),
508 509 usageTime = u.UsageTime,
... ... @@ -648,6 +649,7 @@ namespace NCC.Extend
648 649 productName = product.ProductName,
649 650 productCategory = product.ProductCategory,
650 651 productPrice = product.Price,
  652 + productWarehouse = product.Warehouse, // 产品归属仓库
651 653 storeId = u.StoreId,
652 654 storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(store => store.Id == u.StoreId).Select(store => store.Dm),
653 655 usageTime = u.UsageTime,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -897,6 +897,7 @@ namespace NCC.Extend.LqKdKdjlb
897 897 {
898 898 Id = YitIdHelper.NextId().ToString(),
899 899 BillingId = newEntity.Id,
  900 + BillingTime = newEntity.Kdrq, // 设置开单时间
900 901 DeductId = item.DeductId,
901 902 DeductType = item.DeductType,
902 903 Amount = item.Amount,
... ... @@ -1243,6 +1244,7 @@ namespace NCC.Extend.LqKdKdjlb
1243 1244 {
1244 1245 Id = YitIdHelper.NextId().ToString(),
1245 1246 BillingId = id,
  1247 + BillingTime = entity.Kdrq, // 设置开单时间
1246 1248 DeductId = item.DeductId,
1247 1249 DeductType = item.DeductType,
1248 1250 Amount = item.Amount,
... ... @@ -1252,7 +1254,7 @@ namespace NCC.Extend.LqKdKdjlb
1252 1254 ItemId = item.ItemId,
1253 1255 IsEffective = StatusEnum.有效.GetHashCode(), // 设置为有效
1254 1256 CreateTime = DateTime.Now, // 设置创建时间
1255   - ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.DeductId).Select(x => x.Qt2).FirstAsync()
  1257 + ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.ItemId).Select(x => x.Qt2).FirstAsync() // 修复:使用 ItemId 而不是 DeductId
1256 1258 };
1257 1259 allDeductEntities.Add(lqKdDeductEntity);
1258 1260 }
... ... @@ -2292,6 +2294,7 @@ namespace NCC.Extend.LqKdKdjlb
2292 2294 Sfyj = input.Sfyj,
2293 2295 DeductAmount = input.DeductAmount,
2294 2296 Qk = input.Qk,
  2297 + Bz = input.Remark,
2295 2298 UpdateTime = DateTime.Now
2296 2299 }).Where(w => w.Id == input.BillingId).ExecuteCommandAsync();
2297 2300  
... ... @@ -3791,11 +3794,19 @@ namespace NCC.Extend.LqKdKdjlb
3791 3794 }
3792 3795  
3793 3796 // 批量查询开单记录,获取门店ID
  3797 + // 批量查询开单记录,获取门店ID及扩展信息
3794 3798 var billingStoreDict = new Dictionary<string, string>();
  3799 + var billingExtraInfoDict = new Dictionary<string, dynamic>();
  3800 +
3795 3801 if (billingIds.Any())
3796 3802 {
3797   - var billings = await _db.Queryable<LqKdKdjlbEntity>().Where(x => billingIds.Contains(x.Id)).Select(x => new { x.Id, x.Djmd }).ToListAsync();
  3803 + var billings = await _db.Queryable<LqKdKdjlbEntity>()
  3804 + .Where(x => billingIds.Contains(x.Id))
  3805 + .Select(x => new { x.Id, x.Djmd, x.Hgjg, x.Fkyy })
  3806 + .ToListAsync();
  3807 +
3798 3808 billingStoreDict = billings.ToDictionary(x => x.Id, x => x.Djmd ?? "");
  3809 + billingExtraInfoDict = billings.ToDictionary(x => x.Id, x => (dynamic)new { Hgjg = x.Hgjg, Fkyy = x.Fkyy });
3799 3810 }
3800 3811  
3801 3812 // 批量查询门店信息
... ... @@ -3823,7 +3834,9 @@ namespace NCC.Extend.LqKdKdjlb
3823 3834 sourceType = pxmx.SourceType,
3824 3835 remark = pxmx.Remark,
3825 3836 storeId = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) ? billingStoreDict[pxmx.Glkdbh] : "",
3826   - storeName = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) && !string.IsNullOrEmpty(billingStoreDict[pxmx.Glkdbh]) && storeDict.ContainsKey(billingStoreDict[pxmx.Glkdbh]) ? storeDict[billingStoreDict[pxmx.Glkdbh]] : ""
  3837 + storeName = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) && !string.IsNullOrEmpty(billingStoreDict[pxmx.Glkdbh]) && storeDict.ContainsKey(billingStoreDict[pxmx.Glkdbh]) ? storeDict[billingStoreDict[pxmx.Glkdbh]] : "",
  3838 + hgjg = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Hgjg : "",
  3839 + fkyy = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Fkyy : ""
3827 3840 }).ToList();
3828 3841  
3829 3842 // 6. 返回分页结果
... ... @@ -3915,6 +3928,7 @@ namespace NCC.Extend.LqKdKdjlb
3915 3928  
3916 3929  
3917 3930 // 查询并分页,使用子查询获取开单类型
  3931 + // 优先使用储扣记录表中的开单时间,如果为空则使用开单记录表中的开单时间
3918 3932 var data = await baseQuery.Select((deduct, billing, member, store) => new LqKdDeductinfoListOutput
3919 3933 {
3920 3934 Id = deduct.Id ?? "",
... ... @@ -3929,13 +3943,13 @@ namespace NCC.Extend.LqKdKdjlb
3929 3943 CreateTime = deduct.CreateTime,
3930 3944 ProjectNumber = deduct.ProjectNumber,
3931 3945 ItemCategory = deduct.ItemCategory ?? "",
3932   - BillingDate = billing.Kdrq,
  3946 + BillingDate = deduct.BillingTime ?? billing.Kdrq, // 优先使用储扣记录表中的开单时间
3933 3947 MemberId = billing.Kdhy ?? "",
3934 3948 MemberName = member.Khmc ?? "",
3935 3949 MemberPhone = member.Sjh ?? "",
3936 3950 StoreId = billing.Djmd ?? "",
3937 3951 StoreName = store.Dm ?? "",
3938   - TimePeriod = billing.Kdrq,
  3952 + TimePeriod = deduct.BillingTime ?? billing.Kdrq, // 优先使用储扣记录表中的开单时间
3939 3953 BillingType = SqlFunc.Subqueryable<LqKdPxmxEntity>()
3940 3954 .Where(pxmx => pxmx.Id == deduct.DeductId && pxmx.Px == deduct.ItemId)
3941 3955 .Select(pxmx => pxmx.SourceType),
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs
... ... @@ -115,7 +115,8 @@ namespace NCC.Extend
115 115 Remark = input.Remark,
116 116 IsEffective = StatusEnum.有效.GetHashCode(),
117 117 CreateUser = _userManager.UserId,
118   - CreateTime = DateTime.Now
  118 + CreateTime = DateTime.Now,
  119 + SendTime = DateTime.Now // 设置送出时间
119 120 };
120 121  
121 122 var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
... ... @@ -212,7 +213,8 @@ namespace NCC.Extend
212 213 Remark = input.Remark,
213 214 IsEffective = StatusEnum.有效.GetHashCode(),
214 215 CreateUser = _userManager.UserId,
215   - CreateTime = DateTime.Now
  216 + CreateTime = DateTime.Now,
  217 + ReturnTime = DateTime.Now // 设置送回时间
216 218 };
217 219  
218 220 var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
... ... @@ -228,6 +230,109 @@ namespace NCC.Extend
228 230 }
229 231 #endregion
230 232  
  233 + #region 修改送出/送回记录
  234 + /// <summary>
  235 + /// 修改送出/送回记录
  236 + /// </summary>
  237 + /// <remarks>
  238 + /// 修改清洗流水记录的数量、送出时间、送回时间和备注
  239 + ///
  240 + /// 示例请求:
  241 + /// ```json
  242 + /// {
  243 + /// "id": "记录ID",
  244 + /// "quantity": 95,
  245 + /// "sendTime": "2025-11-01 10:00:00",
  246 + /// "returnTime": "2025-11-05 15:00:00",
  247 + /// "remark": "修改备注"
  248 + /// }
  249 + /// ```
  250 + ///
  251 + /// 参数说明:
  252 + /// - id: 记录ID(必填)
  253 + /// - quantity: 数量(可选,修改时需重新计算总费用)
  254 + /// - sendTime: 送出时间(可选,仅流水类型为0时有效)
  255 + /// - returnTime: 送回时间(可选,仅流水类型为1时有效)
  256 + /// - remark: 备注(可选)
  257 + /// </remarks>
  258 + /// <param name="input">修改输入</param>
  259 + /// <returns>修改结果</returns>
  260 + /// <response code="200">修改成功</response>
  261 + /// <response code="400">记录不存在或参数错误</response>
  262 + /// <response code="500">服务器错误</response>
  263 + [HttpPost("Update")]
  264 + public async Task<dynamic> UpdateAsync([FromBody] LqLaundryFlowUpdateInput input)
  265 + {
  266 + try
  267 + {
  268 + // 查询记录是否存在
  269 + var entity = await _db.Queryable<LqLaundryFlowEntity>()
  270 + .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
  271 + .FirstAsync();
  272 +
  273 + if (entity == null)
  274 + {
  275 + throw NCCException.Oh("记录不存在或已失效");
  276 + }
  277 +
  278 + // 更新数量(如果提供)
  279 + if (input.Quantity.HasValue)
  280 + {
  281 + entity.Quantity = input.Quantity.Value;
  282 + // 重新计算总费用
  283 + entity.TotalPrice = entity.Quantity * entity.LaundryPrice;
  284 + }
  285 +
  286 + // 更新送出时间(仅流水类型为0时有效)
  287 + if (input.SendTime.HasValue)
  288 + {
  289 + if (entity.FlowType != 0)
  290 + {
  291 + throw NCCException.Oh("只有送出记录才能修改送出时间");
  292 + }
  293 + entity.SendTime = input.SendTime.Value;
  294 + }
  295 +
  296 + // 更新送回时间(仅流水类型为1时有效)
  297 + if (input.ReturnTime.HasValue)
  298 + {
  299 + if (entity.FlowType != 1)
  300 + {
  301 + throw NCCException.Oh("只有送回记录才能修改送回时间");
  302 + }
  303 + entity.ReturnTime = input.ReturnTime.Value;
  304 + }
  305 +
  306 + // 更新备注(如果提供)
  307 + if (input.Remark != null)
  308 + {
  309 + entity.Remark = input.Remark;
  310 + }
  311 +
  312 + // 执行更新
  313 + var isOk = await _db.Updateable(entity)
  314 + .UpdateColumns(it => new
  315 + {
  316 + it.Quantity,
  317 + it.TotalPrice,
  318 + it.SendTime,
  319 + it.ReturnTime,
  320 + it.Remark
  321 + })
  322 + .ExecuteCommandAsync();
  323 +
  324 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  325 +
  326 + return new { message = "修改成功", totalPrice = entity.TotalPrice };
  327 + }
  328 + catch (Exception ex)
  329 + {
  330 + _logger.LogError(ex, "修改清洗流水记录失败");
  331 + throw NCCException.Oh($"修改失败:{ex.Message}");
  332 + }
  333 + }
  334 + #endregion
  335 +
231 336 #region 获取清洗流水列表
232 337 /// <summary>
233 338 /// 获取清洗流水列表
... ... @@ -275,7 +380,9 @@ namespace NCC.Extend
275 380 isEffective = flow.IsEffective,
276 381 createUser = flow.CreateUser,
277 382 createUserName = "",
278   - createTime = flow.CreateTime
  383 + createTime = flow.CreateTime,
  384 + sendTime = flow.SendTime,
  385 + returnTime = flow.ReturnTime
279 386 })
280 387 .MergeTable()
281 388 .OrderBy(sidx + " " + sort)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
... ... @@ -1515,8 +1515,8 @@ namespace NCC.Extend.LqReimbursementApplication
1515 1515 // 查询本月已审核通过的报销申请
1516 1516 var applications = await _db.Queryable<LqReimbursementApplicationEntity>()
1517 1517 .Where(x => (x.ApprovalStatus ?? x.ApproveStatus) == "已通过")
1518   - .Where(x => x.ApplicationTime.HasValue &&
1519   - x.ApplicationTime.Value.Year == queryYear &&
  1518 + .Where(x => x.ApplicationTime.HasValue &&
  1519 + x.ApplicationTime.Value.Year == queryYear &&
1520 1520 x.ApplicationTime.Value.Month == int.Parse(queryMonth))
1521 1521 .ToListAsync();
1522 1522  
... ... @@ -1552,7 +1552,7 @@ namespace NCC.Extend.LqReimbursementApplication
1552 1552 foreach (var app in applications)
1553 1553 {
1554 1554 var appPurchaseRecords = purchaseRecords.Where(x => x.ApplicationId == app.Id).ToList();
1555   -
  1555 +
1556 1556 if (appPurchaseRecords.Any())
1557 1557 {
1558 1558 // 每个购买记录作为一行
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
... ... @@ -211,16 +211,31 @@ namespace NCC.Extend
211 211 throw new Exception($"第{i + 1}行:月份必须在1-12之间");
212 212 }
213 213  
  214 + // 辅助方法:清理数值字符串(去除千分位分隔符、货币符号等)
  215 + Func<string, string> CleanNumericString = (str) =>
  216 + {
  217 + if (string.IsNullOrWhiteSpace(str))
  218 + return "0";
  219 + // 去除常见的非数字字符(保留小数点和负号)
  220 + return str.Trim()
  221 + .Replace(",", "") // 去除千分位分隔符
  222 + .Replace(",", "") // 去除中文逗号
  223 + .Replace("¥", "") // 去除人民币符号
  224 + .Replace("$", "") // 去除美元符号
  225 + .Replace("元", "") // 去除"元"字
  226 + .Replace(" ", ""); // 去除空格
  227 + };
  228 +
214 229 // 解析数值字段(允许为空,默认为0)
215   - decimal.TryParse(baseRewardPerformanceText, out decimal baseRewardPerformance);
216   - decimal.TryParse(cooperationRewardPerformanceText, out decimal cooperationRewardPerformance);
217   - decimal.TryParse(newCustomerPerformanceText, out decimal newCustomerPerformance);
218   - decimal.TryParse(newCustomerConversionRateText, out decimal newCustomerConversionRate);
219   - decimal.TryParse(upgradePerformanceText, out decimal upgradePerformance);
220   - decimal.TryParse(upgradeConversionRateText, out decimal upgradeConversionRate);
221   - decimal.TryParse(upgradeCustomerCountText, out decimal upgradeCustomerCount);
222   - decimal.TryParse(otherPerformanceAddText, out decimal otherPerformanceAdd);
223   - decimal.TryParse(otherPerformanceSubtractText, out decimal otherPerformanceSubtract);
  230 + decimal.TryParse(CleanNumericString(baseRewardPerformanceText), out decimal baseRewardPerformance);
  231 + decimal.TryParse(CleanNumericString(cooperationRewardPerformanceText), out decimal cooperationRewardPerformance);
  232 + decimal.TryParse(CleanNumericString(newCustomerPerformanceText), out decimal newCustomerPerformance);
  233 + decimal.TryParse(CleanNumericString(newCustomerConversionRateText), out decimal newCustomerConversionRate);
  234 + decimal.TryParse(CleanNumericString(upgradePerformanceText), out decimal upgradePerformance);
  235 + decimal.TryParse(CleanNumericString(upgradeConversionRateText), out decimal upgradeConversionRate);
  236 + decimal.TryParse(CleanNumericString(upgradeCustomerCountText), out decimal upgradeCustomerCount);
  237 + decimal.TryParse(CleanNumericString(otherPerformanceAddText), out decimal otherPerformanceAdd);
  238 + decimal.TryParse(CleanNumericString(otherPerformanceSubtractText), out decimal otherPerformanceSubtract);
224 239  
225 240 var item = new SalaryExtraCalculationImportInput
226 241 {
... ... @@ -411,7 +426,22 @@ namespace NCC.Extend
411 426 // 批量更新现有记录
412 427 if (entitiesToUpdate.Any())
413 428 {
414   - await _db.Updateable(entitiesToUpdate).ExecuteCommandAsync();
  429 + // 明确指定要更新的字段,确保所有字段都被更新(包括升单业绩)
  430 + // 注意:Updateable接收实体列表时,会自动根据主键更新,不需要Where条件
  431 + await _db.Updateable(entitiesToUpdate)
  432 + .UpdateColumns(it => new
  433 + {
  434 + it.BaseRewardPerformance,
  435 + it.CooperationRewardPerformance,
  436 + it.NewCustomerPerformance,
  437 + it.NewCustomerConversionRate,
  438 + it.UpgradePerformance,
  439 + it.UpgradeConversionRate,
  440 + it.UpgradeCustomerCount,
  441 + it.OtherPerformanceAdd,
  442 + it.OtherPerformanceSubtract
  443 + })
  444 + .ExecuteCommandAsync();
415 445 }
416 446  
417 447 var result = new
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
... ... @@ -623,8 +623,15 @@ namespace NCC.Extend
623 623 isNewStore);
624 624  
625 625 // 4.2 提成计算
626   - // 业绩门槛: 战队成员个人总业绩 <= 6000 无提成
627   - if (!string.IsNullOrEmpty(salary.GoldTriangleId) && salary.TotalPerformance <= 6000)
  626 + // 业绩门槛: 战队成员个人总业绩 <= 6000 无提成 (需按日均计算)
  627 + // 规则:战队成员日均业绩 <= 6000 / 当月天数 -> 无提成
  628 + decimal memberThreshold = 6000m;
  629 + if (daysInMonth > 0 && salary.WorkingDays > 0)
  630 + {
  631 + memberThreshold = (6000m / daysInMonth) * salary.WorkingDays;
  632 + }
  633 +
  634 + if (!string.IsNullOrEmpty(salary.GoldTriangleId) && salary.TotalPerformance < memberThreshold) // 修正为小于校验
628 635 {
629 636 salary.TotalCommission = 0;
630 637 salary.BasePerformanceCommission = 0;
... ... @@ -644,13 +651,15 @@ namespace NCC.Extend
644 651 // 获取战队人数 (注意:这里应该是有效战队人数)
645 652 var teamMemberCount = employeeStats.Values.Count(x => x.GoldTriangleId == salary.GoldTriangleId);
646 653 // 注意:提成点按原始基础业绩计算,不是实际基础业绩
647   - commissionPoint = GetTeamCommissionPoint(teamMemberCount, salary.TeamPerformance);
  654 + // 战队成员不按日均考核提成点,只考核个人门槛
  655 + commissionPoint = GetTeamCommissionPoint(teamMemberCount, salary.TeamPerformance, daysInMonth, salary.WorkingDays);
648 656 }
649 657 else
650 658 {
651 659 // 单人 (或被剔除出战队)
652 660 // 注意:提成点按原始总业绩计算
653   - commissionPoint = GetTeamCommissionPoint(1, salary.TotalPerformance);
  661 + // 单人按日均考核提成点
  662 + commissionPoint = GetTeamCommissionPoint(1, salary.TotalPerformance, daysInMonth, salary.WorkingDays);
654 663 }
655 664  
656 665 salary.CommissionPoint = commissionPoint;
... ... @@ -697,6 +706,17 @@ namespace NCC.Extend
697 706 if (!string.IsNullOrEmpty(salary.EmployeeName) && salary.EmployeeName.Contains("T区"))
698 707 {
699 708 salary.StoreTZoneCommission = salary.StoreTotalPerformance * 0.05m * 0.05m;
  709 +
  710 + // T区人员仅核算提成,其他项(底薪、手工、社保等)归零
  711 + salary.HealthCoachBaseSalary = 0;
  712 + salary.HandworkFee = 0;
  713 + salary.BasePerformanceCommission = 0;
  714 + salary.CooperationPerformanceCommission = 0;
  715 + salary.ConsultantCommission = 0;
  716 + salary.NewCustomerPerformanceCommission = 0;
  717 + salary.UpgradePerformanceCommission = 0;
  718 + salary.TotalSubsidy = 0;
  719 + salary.TotalDeduction = 0;
700 720 }
701 721  
702 722 salary.TotalCommission = salary.BasePerformanceCommission
... ... @@ -801,7 +821,7 @@ namespace NCC.Extend
801 821 /// <summary>
802 822 /// 获取战队提成点
803 823 /// </summary>
804   - private decimal GetTeamCommissionPoint(int memberCount, decimal teamPerformance)
  824 + private decimal GetTeamCommissionPoint(int memberCount, decimal teamPerformance, int daysInMonth, decimal workingDays)
805 825 {
806 826 if (memberCount >= 3)
807 827 {
... ... @@ -820,10 +840,24 @@ namespace NCC.Extend
820 840 }
821 841 else // 1人
822 842 {
823   - if (teamPerformance >= 60000) return 0.06m;
824   - if (teamPerformance >= 40000) return 0.05m;
825   - if (teamPerformance >= 20000) return 0.04m;
826   - if (teamPerformance >= 10000) return 0.03m;
  843 + // 单人按照日均考核
  844 + decimal p1 = 60000m;
  845 + decimal p2 = 40000m;
  846 + decimal p3 = 20000m;
  847 + decimal p4 = 10000m;
  848 +
  849 + if (daysInMonth > 0 && workingDays > 0)
  850 + {
  851 + p1 = (p1 / daysInMonth) * workingDays;
  852 + p2 = (p2 / daysInMonth) * workingDays;
  853 + p3 = (p3 / daysInMonth) * workingDays;
  854 + p4 = (p4 / daysInMonth) * workingDays;
  855 + }
  856 +
  857 + if (teamPerformance >= p1) return 0.06m;
  858 + if (teamPerformance >= p2) return 0.05m;
  859 + if (teamPerformance >= p3) return 0.04m;
  860 + if (teamPerformance >= p4) return 0.03m;
827 861 }
828 862 return 0;
829 863 }
... ... @@ -839,21 +873,23 @@ namespace NCC.Extend
839 873  
840 874 // 注意:
841 875 // 1. "组员业绩"指除顾问外的其他成员业绩总和
842   - // 2. 只统计有效战队成员(考勤≥21天,未被剔除的成员)
  876 + // 2. 只统计有效战队成员(考勤≥20天,未被剔除的成员)
843 877 // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X%
844 878 // 4. 新店顾问不考核消耗
  879 + // 5. 消耗达标:高级顾问整组消耗>=6万,普通顾问整组消耗>=4万
845 880  
846   - // 使用传入的 teamMembers 计算总消耗,或者直接使用 TeamTotalConsumption (如果已计算)
847   - // 但为了保险起见,这里重新计算或使用已有的逻辑
848   - // 注意:CalculateConsultantCommission 方法签名未变,但逻辑需确认 teamConsumption 来源
849   - // 在调用此方法前,teamMembers 已经有了 Consumption 数据
  881 + // 使用传入的 teamMembers 计算总消耗
850 882 var teamConsumption = teamMembers.Sum(x => x.Consumption);
851 883  
852 884 // 计算组员(非顾问)业绩总和
853   - // teamMembers 已经是过滤后的有效成员列表(GoldTriangleId 相同且未被剔除)
854 885 var memberPerformance = teamMembers.Where(x => x.Position != "顾问").Sum(x => x.TotalPerformance);
855 886  
856   - // 高级顾问:业绩≥6万 且 组员业绩≥40% 且 (新店 或 消耗≥6万)
  887 + // 高级顾问:业绩≥6万 且 组员业绩≥40% 且 (新店(第1,2阶段) 或 消耗≥6万)
  888 + // 注意:isNewStore 仅代表是否为新店,具体免考核阶段需确认。假设新店前两个阶段免考核,第三阶段需考核。
  889 + // 这里暂且沿用 isNewStore 逻辑,如果需要更细粒度控制,应传入 NewStoreProtectionStage
  890 + // 如果 isNewStore 为 true,则默认免考核消耗(根据原需求描述:新店第3个阶段时,有金三角,但是不考核消耗)
  891 + // 用户最新指示:新店第3个阶段时,有金三角,但是不考核消耗,默认达标 -> 意味着只要是新店,不管阶段,都不考核消耗
  892 +
857 893 if (teamPerformance >= 60000 && memberPerformance >= teamPerformance * 0.4m)
858 894 {
859 895 if (isNewStore || teamConsumption >= 60000)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Mvc;
  2 +using NCC.Common.Filter;
  3 +using NCC.Dependency;
  4 +using NCC.DynamicApiController;
  5 +using NCC.Extend.Entitys.Dto.LqShareStatisticsHq;
  6 +using NCC.Extend.Entitys.lq_share_statistics_hq;
  7 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  8 +using NCC.Extend.Entitys.lq_hytk_hytk;
  9 +using NCC.Extend.Entitys.lq_kd_pxmx;
  10 +using NCC.Extend.Entitys.lq_contract_rent_detail;
  11 +using SqlSugar;
  12 +using System;
  13 +using System.Linq;
  14 +using System.Threading.Tasks;
  15 +using Yitter.IdGenerator;
  16 +
  17 +namespace NCC.Extend
  18 +{
  19 + /// <summary>
  20 + /// 总部股份统计服务
  21 + /// </summary>
  22 + [ApiDescriptionSettings(Tag = "总部股份统计服务", Name = "LqShareStatisticsHq", Order = 402)]
  23 + [Route("api/Extend/[controller]")]
  24 + public class LqShareStatisticsHqService : IDynamicApiController, ITransient
  25 + {
  26 + private readonly ISqlSugarClient _db;
  27 +
  28 + public LqShareStatisticsHqService(ISqlSugarClient db)
  29 + {
  30 + _db = db;
  31 + }
  32 +
  33 + /// <summary>
  34 + /// 生成总部股份统计数据
  35 + /// </summary>
  36 + /// <param name="input">生成参数</param>
  37 + /// <returns>生成结果</returns>
  38 + [HttpPost("generate")]
  39 + public async Task<dynamic> GenerateStatistics([FromBody] ShareStatisticsHqGenerateInput input)
  40 + {
  41 + if (string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
  42 + {
  43 + return new { code = 500, msg = "统计月份格式错误,应为 YYYYMM" };
  44 + }
  45 +
  46 + var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
  47 + var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
  48 + var startDate = new DateTime(year, month, 1);
  49 + var endDate = startDate.AddMonths(1).AddDays(-1);
  50 +
  51 + // 检查是否已存在
  52 + var existing = await _db.Queryable<LqShareStatisticsHqEntity>()
  53 + .FirstAsync(x => x.StatisticsMonth == input.StatisticsMonth);
  54 +
  55 + var entity = existing ?? new LqShareStatisticsHqEntity
  56 + {
  57 + Id = YitIdHelper.NextId().ToString(),
  58 + StatisticsMonth = input.StatisticsMonth,
  59 + IsEffective = 1,
  60 + CreateTime = DateTime.Now,
  61 + CreateUser = "System"
  62 + };
  63 +
  64 + // 计算各项数据
  65 + await CalculateIncome(entity, startDate, endDate);
  66 + await CalculateCost(entity, startDate, endDate, input.StatisticsMonth);
  67 + CalculateProfit(entity);
  68 +
  69 + entity.UpdateTime = DateTime.Now;
  70 + entity.UpdateUser = "System";
  71 +
  72 + if (existing == null)
  73 + {
  74 + await _db.Insertable(entity).ExecuteCommandAsync();
  75 + return new { code = 200, msg = "生成成功", data = new { generated = true } };
  76 + }
  77 + else
  78 + {
  79 + await _db.Updateable(entity).ExecuteCommandAsync();
  80 + return new { code = 200, msg = "更新成功", data = new { updated = true } };
  81 + }
  82 + }
  83 +
  84 + /// <summary>
  85 + /// 查询总部股份统计列表
  86 + /// </summary>
  87 + /// <param name="input">查询参数</param>
  88 + /// <returns>统计列表</returns>
  89 + [HttpGet("list")]
  90 + public async Task<dynamic> GetList([FromQuery] ShareStatisticsHqQueryInput input)
  91 + {
  92 + var query = _db.Queryable<LqShareStatisticsHqEntity>()
  93 + .Where(x => x.IsEffective == 1);
  94 +
  95 + if (!string.IsNullOrEmpty(input.StatisticsMonth))
  96 + {
  97 + query = query.Where(x => x.StatisticsMonth == input.StatisticsMonth);
  98 + }
  99 +
  100 + var list = await query.OrderBy(x => x.StatisticsMonth, OrderByType.Desc)
  101 + .Select(x => new ShareStatisticsHqOutput
  102 + {
  103 + Id = x.Id,
  104 + StatisticsMonth = x.StatisticsMonth,
  105 + IncomeGeneral = x.IncomeGeneral,
  106 + IncomeTechDept = x.IncomeTechDept,
  107 + CostReimbursement = x.CostReimbursement,
  108 + CostLabor = x.CostLabor,
  109 + CostEducationRent = x.CostEducationRent,
  110 + CostWarehouseRent = x.CostWarehouseRent,
  111 + CostHQRent = x.CostHQRent,
  112 + OperationalProfit = x.OperationalProfit,
  113 + CreateTime = x.CreateTime,
  114 + UpdateTime = x.UpdateTime
  115 + })
  116 + .ToListAsync();
  117 +
  118 + return new { code = 200, msg = "查询成功", data = list };
  119 + }
  120 +
  121 + #region 私有计算方法
  122 +
  123 + /// <summary>
  124 + /// 计算收入部分
  125 + /// </summary>
  126 + private async Task CalculateIncome(LqShareStatisticsHqEntity entity, DateTime startDate, DateTime endDate)
  127 + {
  128 + // 1. 收入-全部 = (所有门店的总开单实付业绩 - 所有门店的总实退金额) * 9%
  129 + var totalBilling = await _db.Queryable<LqKdKdjlbEntity>()
  130 + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate && x.IsEffective == 1)
  131 + .SumAsync(x => x.Sfyj);
  132 +
  133 + var totalRefund = await _db.Queryable<LqHytkHytkEntity>()
  134 + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
  135 + .SumAsync(x => x.Tkje ?? 0);
  136 +
  137 + entity.IncomeGeneral = (totalBilling - totalRefund) * 0.09m;
  138 +
  139 + // 2. 收入-科技部 = (总科美业绩 - 总科美退款) * 0.3 * 0.09
  140 + var kemeiPerformance = await _db.Queryable<LqKdPxmxEntity>()
  141 + .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  142 + .Where(x => x.ItemCategory == "科美")
  143 + .SumAsync(x => x.TotalPrice);
  144 +
  145 + // TODO: 需要确认科美退款的统计方式
  146 + entity.IncomeTechDept = kemeiPerformance * 0.3m * 0.09m;
  147 + }
  148 +
  149 + /// <summary>
  150 + /// 计算成本部分
  151 + /// </summary>
  152 + private async Task CalculateCost(LqShareStatisticsHqEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth)
  153 + {
  154 + // 1. 成本-报销 (TODO: 需要确认总部报销的判定方式)
  155 + entity.CostReimbursement = 0;
  156 +
  157 + // 2. 成本-人工 (保留)
  158 + entity.CostLabor = 0;
  159 +
  160 + // 3. 成本-教育部房租
  161 + // TODO: 需要确认如何识别教育部合同
  162 + entity.CostEducationRent = 0;
  163 +
  164 + // 4. 成本-仓库房租
  165 + // TODO: 需要确认如何识别仓库合同
  166 + entity.CostWarehouseRent = 0;
  167 +
  168 + // 5. 成本-总部房租
  169 + // TODO: 需要确认如何识别总部合同
  170 + entity.CostHQRent = 0;
  171 + }
  172 +
  173 + /// <summary>
  174 + /// 计算利润
  175 + /// </summary>
  176 + private void CalculateProfit(LqShareStatisticsHqEntity entity)
  177 + {
  178 + // 总部运营利润 = 收入(门店9%) + 收入(科技部9%) - 成本(报销) - 成本(人工) - 成本(房租)
  179 + var totalIncome = entity.IncomeGeneral + entity.IncomeTechDept;
  180 + var totalCost = entity.CostReimbursement
  181 + + entity.CostLabor
  182 + + entity.CostEducationRent
  183 + + entity.CostWarehouseRent
  184 + + entity.CostHQRent;
  185 +
  186 + entity.OperationalProfit = totalIncome - totalCost;
  187 + }
  188 +
  189 + #endregion
  190 + }
  191 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Mvc;
  2 +using NCC.Common.Filter;
  3 +using NCC.Dependency;
  4 +using NCC.DynamicApiController;
  5 +using NCC.Extend.Entitys.Dto.LqShareStatisticsStore;
  6 +using NCC.Extend.Entitys.lq_share_statistics_store;
  7 +using NCC.Extend.Entitys.lq_xh_jksyj;
  8 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  9 +using NCC.Extend.Entitys.lq_hytk_hytk;
  10 +using NCC.Extend.Entitys.lq_hytk_jksyj;
  11 +using NCC.Extend.Entitys.lq_inventory_usage;
  12 +using NCC.Extend.Entitys.lq_laundry_flow;
  13 +using NCC.Extend.Entitys.lq_salary_statistics;
  14 +using NCC.Extend.Entitys.lq_assistant_salary_statistics;
  15 +using NCC.Extend.Entitys.lq_director_salary_statistics;
  16 +using NCC.Extend.Entitys.lq_store_manager_salary_statistics;
  17 +using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics;
  18 +using NCC.Extend.Entitys.lq_contract_rent_detail;
  19 +using NCC.Extend.Entitys.lq_mdxx;
  20 +using NCC.Extend.Entitys.lq_kd_pxmx;
  21 +using NCC.Extend.Entitys.lq_product;
  22 +using NCC.Extend.Entitys.lq_inventory_usage_application;
  23 +using NCC.Extend.Entitys;
  24 +using SqlSugar;
  25 +using System;
  26 +using System.Collections.Generic;
  27 +using System.Linq;
  28 +using System.Threading.Tasks;
  29 +using Yitter.IdGenerator;
  30 +
  31 +namespace NCC.Extend
  32 +{
  33 + /// <summary>
  34 + /// 门店股份统计服务
  35 + /// </summary>
  36 + [ApiDescriptionSettings(Tag = "门店股份统计服务", Name = "LqShareStatisticsStore", Order = 400)]
  37 + [Route("api/Extend/[controller]")]
  38 + public class LqShareStatisticsStoreService : IDynamicApiController, ITransient
  39 + {
  40 + private readonly ISqlSugarClient _db;
  41 +
  42 + public LqShareStatisticsStoreService(ISqlSugarClient db)
  43 + {
  44 + _db = db;
  45 + }
  46 +
  47 + /// <summary>
  48 + /// 生成门店股份统计数据
  49 + /// </summary>
  50 + /// <param name="input">生成参数</param>
  51 + /// <returns>生成结果</returns>
  52 + [HttpPost("generate")]
  53 + public async Task<dynamic> GenerateStatistics([FromBody] ShareStatisticsStoreGenerateInput input)
  54 + {
  55 + if (string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
  56 + {
  57 + return new { code = 500, msg = "统计月份格式错误,应为 YYYYMM" };
  58 + }
  59 +
  60 + var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
  61 + var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
  62 + var startDate = new DateTime(year, month, 1);
  63 + var endDate = startDate.AddMonths(1).AddDays(-1);
  64 +
  65 + // 获取门店列表
  66 + var storeQuery = _db.Queryable<LqMdxxEntity>();
  67 + if (!string.IsNullOrEmpty(input.StoreId))
  68 + {
  69 + storeQuery = storeQuery.Where(x => x.Id == input.StoreId);
  70 + }
  71 + var stores = await storeQuery.ToListAsync();
  72 +
  73 + if (stores.Count == 0)
  74 + {
  75 + return new { code = 500, msg = "未找到有效门店" };
  76 + }
  77 +
  78 + var generatedCount = 0;
  79 + var updatedCount = 0;
  80 +
  81 + foreach (var store in stores)
  82 + {
  83 + try
  84 + {
  85 + // 检查是否已存在
  86 + var existing = await _db.Queryable<LqShareStatisticsStoreEntity>()
  87 + .FirstAsync(x => x.StoreId == store.Id && x.StatisticsMonth == input.StatisticsMonth);
  88 +
  89 + var entity = existing ?? new LqShareStatisticsStoreEntity
  90 + {
  91 + Id = YitIdHelper.NextId().ToString(),
  92 + StoreId = store.Id,
  93 + StoreName = store.Dm,
  94 + StatisticsMonth = input.StatisticsMonth,
  95 + IsEffective = 1,
  96 + CreateTime = DateTime.Now,
  97 + CreateUser = "System"
  98 + };
  99 +
  100 + // 计算各项数据
  101 + await CalculateIncome(entity, startDate, endDate);
  102 + await CalculateCost(entity, startDate, endDate);
  103 + await CalculateSalary(entity, input.StatisticsMonth);
  104 + await CalculateExpense(entity, startDate, endDate, input.StatisticsMonth);
  105 + CalculateProfit(entity);
  106 +
  107 + entity.UpdateTime = DateTime.Now;
  108 + entity.UpdateUser = "System";
  109 +
  110 + if (existing == null)
  111 + {
  112 + await _db.Insertable(entity).ExecuteCommandAsync();
  113 + generatedCount++;
  114 + }
  115 + else
  116 + {
  117 + await _db.Updateable(entity).ExecuteCommandAsync();
  118 + updatedCount++;
  119 + }
  120 + }
  121 + catch (Exception ex)
  122 + {
  123 + return new { code = 500, msg = $"处理门店 {store.Dm} 时出错: {ex.Message}, StackTrace: {ex.StackTrace}" };
  124 + }
  125 + }
  126 +
  127 + return new
  128 + {
  129 + code = 200,
  130 + msg = "生成成功",
  131 + data = new
  132 + {
  133 + generatedCount,
  134 + updatedCount,
  135 + totalCount = stores.Count
  136 + }
  137 + };
  138 + }
  139 +
  140 + /// <summary>
  141 + /// 查询门店股份统计列表
  142 + /// </summary>
  143 + /// <param name="input">查询参数</param>
  144 + /// <returns>统计列表</returns>
  145 + [HttpGet("list")]
  146 + public async Task<dynamic> GetList([FromQuery] ShareStatisticsStoreQueryInput input)
  147 + {
  148 + var query = _db.Queryable<LqShareStatisticsStoreEntity>()
  149 + .Where(x => x.IsEffective == 1);
  150 +
  151 + if (!string.IsNullOrEmpty(input.StatisticsMonth))
  152 + {
  153 + query = query.Where(x => x.StatisticsMonth == input.StatisticsMonth);
  154 + }
  155 +
  156 + if (!string.IsNullOrEmpty(input.StoreId))
  157 + {
  158 + query = query.Where(x => x.StoreId == input.StoreId);
  159 + }
  160 +
  161 + if (!string.IsNullOrEmpty(input.StoreName))
  162 + {
  163 + query = query.Where(x => x.StoreName.Contains(input.StoreName));
  164 + }
  165 +
  166 + var list = await query.OrderBy(x => x.StatisticsMonth, OrderByType.Desc)
  167 + .OrderBy(x => x.StoreName)
  168 + .Select(x => new ShareStatisticsStoreOutput
  169 + {
  170 + Id = x.Id,
  171 + StoreId = x.StoreId,
  172 + StoreName = x.StoreName,
  173 + StatisticsMonth = x.StatisticsMonth,
  174 + MainIncome = x.MainIncome,
  175 + ConsumeLifeBeauty = x.ConsumeLifeBeauty,
  176 + ConsumeTechBeauty = x.ConsumeTechBeauty,
  177 + ConsumeMedicalBeauty = x.ConsumeMedicalBeauty,
  178 + ConsumeCooperation = x.ConsumeCooperation,
  179 + ConsumeProduct = x.ConsumeProduct,
  180 + ConsumeOther = x.ConsumeOther,
  181 + OtherIncome = x.OtherIncome,
  182 + AdvanceReceipt = x.AdvanceReceipt,
  183 + Refund = x.Refund,
  184 + Receivables = x.Receivables,
  185 + BankDeposit = x.BankDeposit,
  186 + CostProduct = x.CostProduct,
  187 + CostFutian = x.CostFutian,
  188 + CostTowel = x.CostTowel,
  189 + CostTechDept = x.CostTechDept,
  190 + CostManagementFee = x.CostManagementFee,
  191 + CostCooperation = x.CostCooperation,
  192 + CostMajorProject = x.CostMajorProject,
  193 + CostOther = x.CostOther,
  194 + SalaryBaseHealthCoach = x.SalaryBaseHealthCoach,
  195 + SalaryBaseAssistant = x.SalaryBaseAssistant,
  196 + SalaryBaseDirector = x.SalaryBaseDirector,
  197 + SalaryBaseStoreManager = x.SalaryBaseStoreManager,
  198 + SalaryBaseGeneralManager = x.SalaryBaseGeneralManager,
  199 + SalaryCommissionHealthCoach = x.SalaryCommissionHealthCoach,
  200 + SalaryCommissionAssistant = x.SalaryCommissionAssistant,
  201 + SalaryCommissionDirector = x.SalaryCommissionDirector,
  202 + SalaryCommissionStoreManager = x.SalaryCommissionStoreManager,
  203 + SalaryCommissionGeneralManager = x.SalaryCommissionGeneralManager,
  204 + SalaryManual = x.SalaryManual,
  205 + SalaryAttendance = x.SalaryAttendance,
  206 + SalaryPhoneCustody = x.SalaryPhoneCustody,
  207 + SalaryHeadcountReward = x.SalaryHeadcountReward,
  208 + SalaryTZone = x.SalaryTZone,
  209 + SocialSecurity = x.SocialSecurity,
  210 + StoreRent = x.StoreRent,
  211 + DormRent = x.DormRent,
  212 + CurrentPeriodExpense = x.CurrentPeriodExpense,
  213 + CurrentPeriodExpenseQin = x.CurrentPeriodExpenseQin,
  214 + FinancialFee = x.FinancialFee,
  215 + RewardMedicalBeauty = x.RewardMedicalBeauty,
  216 + RewardOther = x.RewardOther,
  217 + RewardLargeOrder = x.RewardLargeOrder,
  218 + RewardFirstOrder = x.RewardFirstOrder,
  219 + RewardGeneral = x.RewardGeneral,
  220 + Other1 = x.Other1,
  221 + Other2 = x.Other2,
  222 + Profit = x.Profit,
  223 + FinancialProfit = x.FinancialProfit,
  224 + CreateTime = x.CreateTime,
  225 + UpdateTime = x.UpdateTime
  226 + })
  227 + .ToListAsync();
  228 +
  229 + return new { code = 200, msg = "查询成功", data = list };
  230 + }
  231 +
  232 + #region 私有计算方法
  233 +
  234 + /// <summary>
  235 + /// 计算收入部分
  236 + /// </summary>
  237 + private async Task CalculateIncome(LqShareStatisticsStoreEntity entity, DateTime startDate, DateTime endDate)
  238 + {
  239 + // 1. 主营收入 = 消耗总业绩(不扣减退款),并按分类拆分
  240 + var consumePerformance = await _db.Queryable<LqXhJksyjEntity>()
  241 + .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  242 + .SumAsync(x => x.Jksyj);
  243 +
  244 + entity.MainIncome = consumePerformance ?? 0;
  245 +
  246 + // 分项消耗业绩
  247 + var consumeLifeBeauty = await _db.Queryable<LqXhJksyjEntity>()
  248 + .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  249 + .Where(x => x.ItemCategory == "生美")
  250 + .SumAsync(x => x.Jksyj);
  251 +
  252 + var consumeTechBeauty = await _db.Queryable<LqXhJksyjEntity>()
  253 + .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  254 + .Where(x => x.ItemCategory == "科美")
  255 + .SumAsync(x => x.Jksyj);
  256 +
  257 + var consumeMedicalBeauty = await _db.Queryable<LqXhJksyjEntity>()
  258 + .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  259 + .Where(x => x.ItemCategory == "医美")
  260 + .SumAsync(x => x.Jksyj);
  261 +
  262 + var consumeCooperation = await _db.Queryable<LqXhJksyjEntity>()
  263 + .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  264 + .Where(x => x.ItemCategory == "合作")
  265 + .SumAsync(x => x.Jksyj);
  266 +
  267 + var consumeProduct = await _db.Queryable<LqXhJksyjEntity>()
  268 + .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  269 + .Where(x => x.ItemCategory == "产品")
  270 + .SumAsync(x => x.Jksyj);
  271 +
  272 + var consumeOther = await _db.Queryable<LqXhJksyjEntity>()
  273 + .Where(x => x.StoreId == entity.StoreId && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  274 + .Where(x => x.ItemCategory == "其他" || x.ItemCategory == null || x.ItemCategory == "")
  275 + .SumAsync(x => x.Jksyj);
  276 +
  277 + entity.ConsumeLifeBeauty = consumeLifeBeauty ?? 0;
  278 + entity.ConsumeTechBeauty = consumeTechBeauty ?? 0;
  279 + entity.ConsumeMedicalBeauty = consumeMedicalBeauty ?? 0;
  280 + entity.ConsumeCooperation = consumeCooperation ?? 0;
  281 + entity.ConsumeProduct = consumeProduct ?? 0;
  282 + entity.ConsumeOther = consumeOther ?? 0;
  283 +
  284 + // 2. 预收款 = 开单实付
  285 + entity.AdvanceReceipt = await _db.Queryable<LqKdKdjlbEntity>()
  286 + .Where(x => x.Djmd == entity.StoreId && x.Kdrq >= startDate && x.Kdrq <= endDate && x.IsEffective == 1)
  287 + .SumAsync(x => x.Sfyj);
  288 +
  289 + // 3. 实际退款 = 退卡实退金额
  290 + entity.Refund = await _db.Queryable<LqHytkHytkEntity>()
  291 + .Where(x => x.Md == entity.StoreId && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
  292 + .SumAsync(x => x.ActualRefundAmount ?? 0);
  293 +
  294 + // 4. 应收 = 合作医院开单金额
  295 + entity.Receivables = await _db.Queryable<LqKdKdjlbEntity>()
  296 + .Where(x => x.Djmd == entity.StoreId && x.Kdrq >= startDate && x.Kdrq <= endDate
  297 + && x.IsEffective == 1 && !string.IsNullOrEmpty(x.Fkyy))
  298 + .SumAsync(x => x.Sfyj);
  299 +
  300 + // 5. 银行存款 = 开单实付 - 应收
  301 + entity.BankDeposit = entity.AdvanceReceipt - entity.Receivables;
  302 +
  303 + // 6. 其他收入 = 退款差额 > 0 的部分(按照退款单应退金额-实退金额)
  304 + // 退款差额 = tkje - ActualRefundAmount
  305 + var positiveRefundDiff = await _db.Queryable<LqHytkHytkEntity>()
  306 + .Where(x => x.Md == entity.StoreId && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
  307 + .SumAsync(x => SqlFunc.IIF((x.Tkje - (x.ActualRefundAmount ?? 0)) > 0,
  308 + x.Tkje - (x.ActualRefundAmount ?? 0),
  309 + 0));
  310 +
  311 + entity.OtherIncome = positiveRefundDiff ?? 0;
  312 + }
  313 +
  314 + /// <summary>
  315 + /// 计算成本部分
  316 + /// </summary>
  317 + private async Task CalculateCost(LqShareStatisticsStoreEntity entity, DateTime startDate, DateTime endDate)
  318 + {
  319 + // 1. 产品成本 = 仓库领用金额
  320 + entity.CostProduct = await _db.Queryable<LqInventoryUsageEntity>()
  321 + .Where(x => x.StoreId == entity.StoreId && x.UsageTime >= startDate && x.UsageTime <= endDate && x.IsEffective == 1)
  322 + .SumAsync(x => x.TotalAmount);
  323 +
  324 + // 2. 福田成本 = 福田仓库领用(上一个月的成本)
  325 + // 读取产品归属仓库为“福田仓库”的库存领用记录,
  326 + // 并且仅统计已审批通过且已领取的使用申请(领用完成后才纳入统计)
  327 + var prevMonthStart = startDate.AddMonths(-1);
  328 + var prevMonthEnd = startDate.AddDays(-1);
  329 +
  330 + entity.CostFutian = await _db.Queryable<LqInventoryUsageEntity, LqProductEntity, LqInventoryUsageApplicationEntity>((u, p, a) => new JoinQueryInfos(
  331 + JoinType.Inner, u.ProductId == p.Id,
  332 + JoinType.Inner, u.UsageBatchId == a.UsageBatchId))
  333 + .Where((u, p, a) => p.Warehouse == "福田仓库"
  334 + && u.StoreId == entity.StoreId
  335 + && u.UsageTime >= prevMonthStart && u.UsageTime <= prevMonthEnd
  336 + && u.IsEffective == 1
  337 + && a.IsEffective == 1
  338 + && a.ApprovalStatus == "已通过"
  339 + && a.IsReceived == 1)
  340 + .SumAsync((u, p, a) => u.TotalAmount);
  341 +
  342 + // 3. 毛巾成本 = 洗毛巾费用
  343 + entity.CostTowel = await _db.Queryable<LqLaundryFlowEntity>()
  344 + .Where(x => x.StoreId == entity.StoreId
  345 + && x.SendTime >= startDate && x.SendTime <= endDate
  346 + && x.IsEffective == 1)
  347 + .SumAsync(x => x.TotalPrice);
  348 +
  349 + // 4. 科技部成本 = 科美业绩 * 30%
  350 + var kemeiPerformance = await _db.Queryable<LqKdPxmxEntity>()
  351 + .Where(x => x.Glkdbh.StartsWith(entity.StoreId) && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1)
  352 + .Where(x => x.ItemCategory == "科美")
  353 + .SumAsync(x => x.TotalPrice);
  354 +
  355 + entity.CostTechDept = kemeiPerformance * 0.3m;
  356 +
  357 + // 5. 管理费 = 总业绩 * 9%
  358 + var totalPerformance = await _db.Queryable<LqKdKdjlbEntity>()
  359 + .Where(x => x.Djmd == entity.StoreId && x.Kdrq >= startDate && x.Kdrq <= endDate && x.IsEffective == 1)
  360 + .SumAsync(x => x.Sfyj);
  361 +
  362 + entity.CostManagementFee = totalPerformance * 0.09m;
  363 +
  364 + // 6. 其他成本 = 退款差额 < 0 的部分(按照退款单应退金额-实退金额)
  365 + var negativeRefundDiffAbs = await _db.Queryable<LqHytkHytkEntity>()
  366 + .Where(x => x.Md == entity.StoreId && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
  367 + .SumAsync(x => SqlFunc.IIF((x.Tkje - (x.ActualRefundAmount ?? 0)) < 0,
  368 + (x.ActualRefundAmount ?? 0) - x.Tkje,
  369 + 0));
  370 +
  371 + entity.CostOther = negativeRefundDiffAbs ?? 0;
  372 +
  373 + // 保留字段暂时为0
  374 + entity.CostCooperation = 0;
  375 + entity.CostMajorProject = 0;
  376 + }
  377 +
  378 + /// <summary>
  379 + /// 计算人工工资部分
  380 + /// </summary>
  381 + private async Task CalculateSalary(LqShareStatisticsStoreEntity entity, string statisticsMonth)
  382 + {
  383 + // 1. 健康师底薪和提成
  384 + var healthCoachSalary = await _db.Queryable<LqSalaryStatisticsEntity>()
  385 + .Where(x => x.StoreId == entity.StoreId && x.StatisticsMonth == statisticsMonth)
  386 + .Select(x => new
  387 + {
  388 + BaseSalary = SqlFunc.AggregateSum(x.HealthCoachBaseSalary),
  389 + Commission = SqlFunc.AggregateSum(x.TotalCommission),
  390 + Manual = SqlFunc.AggregateSum(x.HandworkFee),
  391 + TZone = SqlFunc.AggregateSum(x.StoreTZoneCommission)
  392 + })
  393 + .FirstAsync();
  394 +
  395 + entity.SalaryBaseHealthCoach = healthCoachSalary?.BaseSalary ?? 0;
  396 + entity.SalaryCommissionHealthCoach = healthCoachSalary?.Commission ?? 0;
  397 + entity.SalaryManual = healthCoachSalary?.Manual ?? 0;
  398 + entity.SalaryTZone = healthCoachSalary?.TZone ?? 0;
  399 +
  400 + // 2. 店助底薪和提成
  401 + var assistantSalary = await _db.Queryable<LqAssistantSalaryStatisticsEntity>()
  402 + .Where(x => x.StoreId == entity.StoreId && x.StatisticsMonth == statisticsMonth && x.Position == "店助")
  403 + .Select(x => new
  404 + {
  405 + BaseSalary = SqlFunc.AggregateSum(x.BaseSalary),
  406 + Commission = SqlFunc.AggregateSum(x.CommissionAmount),
  407 + PhoneCustody = SqlFunc.AggregateSum(x.PhoneManagementFee),
  408 + HeadcountReward = SqlFunc.AggregateSum(x.Stage1Reward + x.Stage2Reward)
  409 + })
  410 + .FirstAsync();
  411 +
  412 + entity.SalaryBaseAssistant = assistantSalary?.BaseSalary ?? 0;
  413 + entity.SalaryCommissionAssistant = assistantSalary?.Commission ?? 0;
  414 + entity.SalaryPhoneCustody = assistantSalary?.PhoneCustody ?? 0;
  415 + entity.SalaryHeadcountReward = assistantSalary?.HeadcountReward ?? 0;
  416 +
  417 + // 3. 店助主任底薪和提成
  418 + var directorSalary = await _db.Queryable<LqAssistantSalaryStatisticsEntity>()
  419 + .Where(x => x.StoreId == entity.StoreId && x.StatisticsMonth == statisticsMonth && x.Position == "店助主任")
  420 + .Select(x => new
  421 + {
  422 + BaseSalary = SqlFunc.AggregateSum(x.BaseSalary),
  423 + Commission = SqlFunc.AggregateSum(x.CommissionAmount),
  424 + PhoneCustody = SqlFunc.AggregateSum(x.PhoneManagementFee),
  425 + HeadcountReward = SqlFunc.AggregateSum(x.Stage1Reward + x.Stage2Reward)
  426 + })
  427 + .FirstAsync();
  428 +
  429 + entity.SalaryBaseDirector = directorSalary?.BaseSalary ?? 0;
  430 + entity.SalaryCommissionDirector = directorSalary?.Commission ?? 0;
  431 +
  432 + // 4. 店长底薪和提成
  433 + var storeManagerSalary = await _db.Queryable<LqStoreManagerSalaryStatisticsEntity>()
  434 + .Where(x => x.StoreId == entity.StoreId && x.StatisticsMonth == statisticsMonth)
  435 + .Select(x => new
  436 + {
  437 + BaseSalary = SqlFunc.AggregateSum(x.ActualBaseSalary),
  438 + Commission = SqlFunc.AggregateSum(x.CommissionAmount)
  439 + })
  440 + .FirstAsync();
  441 +
  442 + entity.SalaryBaseStoreManager = storeManagerSalary?.BaseSalary ?? 0;
  443 + entity.SalaryCommissionStoreManager = storeManagerSalary?.Commission ?? 0;
  444 +
  445 + // 5. 总经理/经理底薪和提成
  446 + // 底薪按门店数平均分摊,提成需要从 F_StorePerformanceDetail JSON 中提取该门店的提成
  447 + var gmSalary = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>()
  448 + .Where(x => x.StatisticsMonth == statisticsMonth)
  449 + .Where(x => x.StorePerformanceDetail.Contains(entity.StoreId))
  450 + .ToListAsync();
  451 +
  452 + decimal gmBaseSalary = 0;
  453 + decimal gmCommission = 0;
  454 +
  455 + foreach (var gm in gmSalary)
  456 + {
  457 + // 底薪平均分摊
  458 + if (!string.IsNullOrEmpty(gm.StorePerformanceDetail))
  459 + {
  460 + try
  461 + {
  462 + var storeDetails = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(gm.StorePerformanceDetail);
  463 + var storeCount = storeDetails?.Count ?? 1;
  464 + gmBaseSalary += gm.BaseSalary / storeCount;
  465 +
  466 + // 提取该门店的提成
  467 + var storeDetail = storeDetails?.FirstOrDefault(s => s.ContainsKey("StoreId") && s["StoreId"].ToString() == entity.StoreId);
  468 + if (storeDetail != null && storeDetail.ContainsKey("Commission"))
  469 + {
  470 + gmCommission += Convert.ToDecimal(storeDetail["Commission"]);
  471 + }
  472 + }
  473 + catch
  474 + {
  475 + // JSON 解析失败,使用默认分摊
  476 + gmBaseSalary += gm.BaseSalary;
  477 + }
  478 + }
  479 + }
  480 +
  481 + entity.SalaryBaseGeneralManager = gmBaseSalary;
  482 + entity.SalaryCommissionGeneralManager = gmCommission;
  483 +
  484 + // 保留字段
  485 + entity.SalaryAttendance = 0;
  486 + }
  487 +
  488 + /// <summary>
  489 + /// 计算费用部分
  490 + /// </summary>
  491 + private async Task CalculateExpense(LqShareStatisticsStoreEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth)
  492 + {
  493 + // 1. 门店房租 - 需要通过合同关联门店
  494 + // 从合同表关联租金明细
  495 + // 解析统计月份
  496 + var year = int.Parse(statisticsMonth.Substring(0, 4));
  497 + var month = int.Parse(statisticsMonth.Substring(4, 2));
  498 +
  499 + var rentDetail = await _db.Queryable<LqContractRentDetailEntity, NCC.Extend.Entitys.lq_contract.LqContractEntity>((rd, c) => new JoinQueryInfos(
  500 + JoinType.Inner, rd.ContractId == c.Id))
  501 + .Where((rd, c) => c.StoreId == entity.StoreId
  502 + && rd.PaymentMonth.Year == year
  503 + && rd.PaymentMonth.Month == month
  504 + && rd.IsEffective == 1)
  505 + .Select((rd, c) => rd.DueAmount)
  506 + .ToListAsync();
  507 +
  508 + entity.StoreRent = rentDetail.Sum();
  509 +
  510 + // 2. 当期费用 = 报销费用中一级分类为“当期费用”的已审核通过记录
  511 + // 只统计:报销申请审批通过 + 申请门店=当前门店 + 申请时间在当月
  512 + var currentPeriodExpense = await _db.Queryable<LqReimbursementApplicationEntity, LqPurchaseRecordsEntity, LqReimbursementCategoryEntity>((app, pr, cat) => new JoinQueryInfos(
  513 + JoinType.Inner, pr.ApplicationId == app.Id,
  514 + JoinType.Inner, pr.ReimbursementCategoryId == cat.Id))
  515 + .Where((app, pr, cat) => app.ApplicationStoreId == entity.StoreId
  516 + && app.ApplicationTime >= startDate && app.ApplicationTime <= endDate
  517 + && (app.ApprovalStatus ?? app.ApproveStatus) == "已通过"
  518 + && cat.Level1Name == "当期费用")
  519 + .SumAsync((app, pr, cat) => pr.Amount);
  520 +
  521 + entity.CurrentPeriodExpense = currentPeriodExpense;
  522 +
  523 + // 保留字段
  524 + entity.SocialSecurity = 0;
  525 + entity.DormRent = 0;
  526 + entity.CurrentPeriodExpenseQin = 0;
  527 + entity.FinancialFee = 0;
  528 + entity.RewardMedicalBeauty = 0;
  529 + entity.RewardOther = 0;
  530 + entity.RewardLargeOrder = 0;
  531 + entity.RewardFirstOrder = 0;
  532 + entity.RewardGeneral = 0;
  533 + entity.Other1 = 0;
  534 + entity.Other2 = 0;
  535 + }
  536 +
  537 + /// <summary>
  538 + /// 计算利润
  539 + /// </summary>
  540 + private void CalculateProfit(LqShareStatisticsStoreEntity entity)
  541 + {
  542 + // 人工工资汇总
  543 + var totalSalary = entity.SalaryBaseHealthCoach + entity.SalaryBaseAssistant + entity.SalaryBaseDirector
  544 + + entity.SalaryBaseStoreManager + entity.SalaryBaseGeneralManager
  545 + + entity.SalaryCommissionHealthCoach + entity.SalaryCommissionAssistant + entity.SalaryCommissionDirector
  546 + + entity.SalaryCommissionStoreManager + entity.SalaryCommissionGeneralManager
  547 + + entity.SalaryManual + entity.SalaryAttendance + entity.SalaryPhoneCustody
  548 + + entity.SalaryHeadcountReward + entity.SalaryTZone;
  549 +
  550 + // 主营成本汇总
  551 + var totalCost = entity.CostProduct + entity.CostFutian + entity.CostTowel + entity.CostTechDept
  552 + + entity.CostManagementFee + entity.CostCooperation + entity.CostMajorProject + entity.CostOther;
  553 +
  554 + // 费用汇总
  555 + var totalExpense = entity.SocialSecurity + entity.StoreRent + entity.DormRent + entity.CurrentPeriodExpense
  556 + + entity.CurrentPeriodExpenseQin + entity.FinancialFee;
  557 +
  558 + // 奖励汇总
  559 + var totalReward = entity.RewardMedicalBeauty + entity.RewardOther + entity.RewardLargeOrder
  560 + + entity.RewardFirstOrder + entity.RewardGeneral;
  561 +
  562 + // 其他汇总
  563 + var totalOther = entity.Other1 + entity.Other2;
  564 +
  565 + // 利润 = 预收 - 实退 - 主营成本 - 人工工资 - 房租 - 费用 - 奖励 - 其他
  566 + entity.Profit = entity.AdvanceReceipt - entity.Refund - totalCost - totalSalary - totalExpense - totalReward - totalOther;
  567 +
  568 + // 财务利润 = 主营收入 + 其他收入 - 主营成本 - 人工工资 - 房租 - 费用 - 奖励 - 其他
  569 + entity.FinancialProfit = entity.MainIncome + entity.OtherIncome - totalCost - totalSalary - totalExpense - totalReward - totalOther;
  570 + }
  571 +
  572 + #endregion
  573 + }
  574 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Mvc;
  2 +using NCC.Common.Filter;
  3 +using NCC.Dependency;
  4 +using NCC.DynamicApiController;
  5 +using NCC.Extend.Entitys.Dto.LqShareStatisticsTechDept;
  6 +using NCC.Extend.Entitys.lq_share_statistics_tech_dept;
  7 +using NCC.Extend.Entitys.lq_mdxx;
  8 +using NCC.Extend.Entitys.lq_kd_pxmx;
  9 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  10 +using NCC.Extend.Entitys.lq_hytk_jksyj;
  11 +using NCC.Extend.Entitys.lq_xh_jksyj;
  12 +using SqlSugar;
  13 +using System;
  14 +using System.Collections.Generic;
  15 +using System.Linq;
  16 +using System.Threading.Tasks;
  17 +using Yitter.IdGenerator;
  18 +
  19 +namespace NCC.Extend
  20 +{
  21 + /// <summary>
  22 + /// 科技部股份统计服务
  23 + /// </summary>
  24 + [ApiDescriptionSettings(Tag = "科技部股份统计服务", Name = "LqShareStatisticsTechDept", Order = 401)]
  25 + [Route("api/Extend/[controller]")]
  26 + public class LqShareStatisticsTechDeptService : IDynamicApiController, ITransient
  27 + {
  28 + private readonly ISqlSugarClient _db;
  29 +
  30 + public LqShareStatisticsTechDeptService(ISqlSugarClient db)
  31 + {
  32 + _db = db;
  33 + }
  34 +
  35 + /// <summary>
  36 + /// 生成科技部股份统计数据
  37 + /// </summary>
  38 + /// <param name="input">生成参数</param>
  39 + /// <returns>生成结果</returns>
  40 + [HttpPost("generate")]
  41 + public async Task<dynamic> GenerateStatistics([FromBody] ShareStatisticsTechDeptGenerateInput input)
  42 + {
  43 + if (string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
  44 + {
  45 + return new { code = 500, msg = "统计月份格式错误,应为 YYYYMM" };
  46 + }
  47 +
  48 + var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
  49 + var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
  50 + var startDate = new DateTime(year, month, 1);
  51 + var endDate = startDate.AddMonths(1).AddDays(-1);
  52 +
  53 + // 确定要生成的部门列表
  54 + var departments = new List<string>();
  55 + if (!string.IsNullOrEmpty(input.DepartmentName))
  56 + {
  57 + departments.Add(input.DepartmentName);
  58 + }
  59 + else
  60 + {
  61 + departments.Add("科技一部");
  62 + departments.Add("科技二部");
  63 + }
  64 +
  65 + var generatedCount = 0;
  66 + var updatedCount = 0;
  67 +
  68 + foreach (var deptName in departments)
  69 + {
  70 + // 检查是否已存在
  71 + var existing = await _db.Queryable<LqShareStatisticsTechDeptEntity>()
  72 + .FirstAsync(x => x.DepartmentName == deptName && x.StatisticsMonth == input.StatisticsMonth);
  73 +
  74 + var entity = existing ?? new LqShareStatisticsTechDeptEntity
  75 + {
  76 + Id = YitIdHelper.NextId().ToString(),
  77 + DepartmentName = deptName,
  78 + StatisticsMonth = input.StatisticsMonth,
  79 + IsEffective = 1,
  80 + CreateTime = DateTime.Now,
  81 + CreateUser = "System"
  82 + };
  83 +
  84 + // 计算各项数据
  85 + await CalculateIncome(entity, deptName, startDate, endDate);
  86 + await CalculateCost(entity, deptName, startDate, endDate, input.StatisticsMonth);
  87 + CalculateProfit(entity);
  88 +
  89 + entity.UpdateTime = DateTime.Now;
  90 + entity.UpdateUser = "System";
  91 +
  92 + if (existing == null)
  93 + {
  94 + await _db.Insertable(entity).ExecuteCommandAsync();
  95 + generatedCount++;
  96 + }
  97 + else
  98 + {
  99 + await _db.Updateable(entity).ExecuteCommandAsync();
  100 + updatedCount++;
  101 + }
  102 + }
  103 +
  104 + return new
  105 + {
  106 + code = 200,
  107 + msg = "生成成功",
  108 + data = new
  109 + {
  110 + generatedCount,
  111 + updatedCount,
  112 + totalCount = departments.Count
  113 + }
  114 + };
  115 + }
  116 +
  117 + /// <summary>
  118 + /// 查询科技部股份统计列表
  119 + /// </summary>
  120 + /// <param name="input">查询参数</param>
  121 + /// <returns>统计列表</returns>
  122 + [HttpGet("list")]
  123 + public async Task<dynamic> GetList([FromQuery] ShareStatisticsTechDeptQueryInput input)
  124 + {
  125 + var query = _db.Queryable<LqShareStatisticsTechDeptEntity>()
  126 + .Where(x => x.IsEffective == 1);
  127 +
  128 + if (!string.IsNullOrEmpty(input.StatisticsMonth))
  129 + {
  130 + query = query.Where(x => x.StatisticsMonth == input.StatisticsMonth);
  131 + }
  132 +
  133 + if (!string.IsNullOrEmpty(input.DepartmentName))
  134 + {
  135 + query = query.Where(x => x.DepartmentName == input.DepartmentName);
  136 + }
  137 +
  138 + var list = await query.OrderBy(x => x.StatisticsMonth, OrderByType.Desc)
  139 + .OrderBy(x => x.DepartmentName)
  140 + .Select(x => new ShareStatisticsTechDeptOutput
  141 + {
  142 + Id = x.Id,
  143 + DepartmentName = x.DepartmentName,
  144 + StatisticsMonth = x.StatisticsMonth,
  145 + Income = x.Income,
  146 + CostReimbursement = x.CostReimbursement,
  147 + CostTeacherBase = x.CostTeacherBase,
  148 + CostTeacherManual = x.CostTeacherManual,
  149 + CostTeacherBillingComm = x.CostTeacherBillingComm,
  150 + CostTeacherConsumeComm = x.CostTeacherConsumeComm,
  151 + CostTeacherExpertComm = x.CostTeacherExpertComm,
  152 + CostTeacherOvertime = x.CostTeacherOvertime,
  153 + CostGMBase = x.CostGMBase,
  154 + CostGMComm = x.CostGMComm,
  155 + RewardTechDept = x.RewardTechDept,
  156 + CostOther1 = x.CostOther1,
  157 + CostOther2 = x.CostOther2,
  158 + Profit = x.Profit,
  159 + CreateTime = x.CreateTime,
  160 + UpdateTime = x.UpdateTime
  161 + })
  162 + .ToListAsync();
  163 +
  164 + return new { code = 200, msg = "查询成功", data = list };
  165 + }
  166 +
  167 + #region 私有计算方法
  168 +
  169 + /// <summary>
  170 + /// 计算收入部分
  171 + /// </summary>
  172 + private async Task CalculateIncome(LqShareStatisticsTechDeptEntity entity, string deptName, DateTime startDate, DateTime endDate)
  173 + {
  174 + // 1. 找到该科技部管辖的所有门店
  175 + var stores = await _db.Queryable<LqMdxxEntity>()
  176 + .Where(x => x.Kjb == deptName)
  177 + .Select(x => x.Id)
  178 + .ToListAsync();
  179 +
  180 + if (stores.Count == 0)
  181 + {
  182 + entity.Income = 0;
  183 + return;
  184 + }
  185 +
  186 + // 2. 统计这些门店的科美项目开单实付业绩
  187 + // 需要关联 lq_kd_kdjlb 来获取门店信息
  188 + var kemeiIncome = await _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity>((px, kd) => new JoinQueryInfos(
  189 + JoinType.Inner, px.Glkdbh == kd.Id))
  190 + .Where((px, kd) => stores.Contains(kd.Djmd) && px.Yjsj >= startDate && px.Yjsj <= endDate && px.IsEffective == 1)
  191 + .Where((px, kd) => px.ItemCategory == "科美")
  192 + .SumAsync((px, kd) => px.TotalPrice);
  193 +
  194 + // 3. 减去对应的科美项目实退金额
  195 + var kemeiRefund = await _db.Queryable<LqHytkJksyjEntity>()
  196 + .Where(x => stores.Contains(x.StoreId) && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1)
  197 + .Where(x => x.ItemCategory == "科美")
  198 + .SumAsync(x => x.Jksyj ?? 0);
  199 +
  200 + // 4. 结果 * 30%
  201 + entity.Income = (kemeiIncome - kemeiRefund) * 0.3m;
  202 + }
  203 +
  204 + /// <summary>
  205 + /// 计算成本部分
  206 + /// </summary>
  207 + private async Task CalculateCost(LqShareStatisticsTechDeptEntity entity, string deptName, DateTime startDate, DateTime endDate, string statisticsMonth)
  208 + {
  209 + // 1. 成本-报销 (TODO: 需要确认报销分类的具体判定方式)
  210 + entity.CostReimbursement = 0;
  211 +
  212 + // 2. 成本-人工-科技部老师底薪 (TODO: 需要确认科技部老师工资表名和字段)
  213 + entity.CostTeacherBase = 0;
  214 +
  215 + // 3. 成本-人工-科技部手工费
  216 + // 从消耗表中统计科技部老师的手工费
  217 + // TODO: 需要确认如何识别科技部老师
  218 + entity.CostTeacherManual = 0;
  219 +
  220 + // 4. 成本-人工-科技部开单提成 (TODO: 需要确认字段名)
  221 + entity.CostTeacherBillingComm = 0;
  222 +
  223 + // 5. 成本-人工-科技部消耗提成 (TODO: 需要确认字段名)
  224 + entity.CostTeacherConsumeComm = 0;
  225 +
  226 + // 6. 成本-人工-科技部总经理 (TODO: 需要确认科技部总经理工资表)
  227 + entity.CostGMBase = 0;
  228 + entity.CostGMComm = 0;
  229 +
  230 + // 保留字段
  231 + entity.CostTeacherExpertComm = 0;
  232 + entity.CostTeacherOvertime = 0;
  233 + entity.RewardTechDept = 0;
  234 + entity.CostOther1 = 0;
  235 + entity.CostOther2 = 0;
  236 + }
  237 +
  238 + /// <summary>
  239 + /// 计算利润
  240 + /// </summary>
  241 + private void CalculateProfit(LqShareStatisticsTechDeptEntity entity)
  242 + {
  243 + // 科技部利润 = 收入 - 成本报销 - 成本人工 - 其他
  244 + var totalCost = entity.CostReimbursement
  245 + + entity.CostTeacherBase
  246 + + entity.CostTeacherManual
  247 + + entity.CostTeacherBillingComm
  248 + + entity.CostTeacherConsumeComm
  249 + + entity.CostTeacherExpertComm
  250 + + entity.CostTeacherOvertime
  251 + + entity.CostGMBase
  252 + + entity.CostGMComm
  253 + + entity.RewardTechDept
  254 + + entity.CostOther1
  255 + + entity.CostOther2;
  256 +
  257 + entity.Profit = entity.Income - totalCost;
  258 + }
  259 +
  260 + #endregion
  261 + }
  262 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs
... ... @@ -66,10 +66,36 @@ namespace NCC.Extend.LqStoreExpense
66 66 .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
67 67 .FirstAsync();
68 68 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
69   - var output = entity.Adapt<LqStoreExpenseInfoOutput>();
  69 + var output = new LqStoreExpenseInfoOutput
  70 + {
  71 + id = entity.Id,
  72 + storeId = entity.StoreId,
  73 + storeName = entity.StoreName,
  74 + expenseCategoryId = entity.ExpenseCategoryId,
  75 + expenseCategoryName = entity.ExpenseCategoryName,
  76 + expenseDate = entity.ExpenseDate,
  77 + unitPrice = entity.UnitPrice,
  78 + quantity = entity.Quantity,
  79 + amount = entity.Amount,
  80 + memo = entity.Memo,
  81 + relatedReimbursementId = entity.RelatedReimbursementId,
  82 + relatedPurchaseRecordId = entity.RelatedPurchaseRecordId,
  83 + createUser = entity.CreateUser,
  84 + createTime = entity.CreateTime,
  85 + updateUser = entity.UpdateUser,
  86 + updateTime = entity.UpdateTime
  87 + };
  88 +
70 89 if (!string.IsNullOrEmpty(entity.Attachment))
71 90 {
72   - output.attachment = entity.Attachment.ToObject<List<NCC.Common.Model.FileControlsModel>>();
  91 + try
  92 + {
  93 + output.attachment = entity.Attachment.ToObject<List<NCC.Common.Model.FileControlsModel>>();
  94 + }
  95 + catch
  96 + {
  97 + output.attachment = new List<NCC.Common.Model.FileControlsModel>();
  98 + }
73 99 }
74 100 return output;
75 101 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs
... ... @@ -294,6 +294,7 @@ namespace NCC.Extend
294 294 .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ExpenseAmount ?? 0));
295 295  
296 296 // 1.12 洗毛巾费用统计(只统计送出的记录,F_FlowType = 0)
  297 + // 优先使用送出时间(F_SendTime),如果为空则使用创建时间(F_CreateTime)
297 298 var laundryCostSql = $@"
298 299 SELECT
299 300 F_StoreId as StoreId,
... ... @@ -301,7 +302,7 @@ namespace NCC.Extend
301 302 FROM lq_laundry_flow
302 303 WHERE F_IsEffective = 1
303 304 AND F_FlowType = 0
304   - AND DATE_FORMAT(F_CreateTime, '%Y%m') = @monthStr
  305 + AND DATE_FORMAT(COALESCE(F_SendTime, F_CreateTime), '%Y%m') = @monthStr
305 306 GROUP BY F_StoreId";
306 307  
307 308 var laundryCostData = await _db.Ado.SqlQueryAsync<dynamic>(laundryCostSql, new { monthStr });
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqYcsdJsjService.cs
... ... @@ -347,6 +347,18 @@ namespace NCC.Extend.LqYcsdJsj
347 347 }
348 348 }
349 349  
  350 + // 验证成员 UserID 不能为空
  351 + if (input.members != null && input.members.Count > 0)
  352 + {
  353 + foreach (var member in input.members)
  354 + {
  355 + if (string.IsNullOrEmpty(member.userId))
  356 + {
  357 + throw NCCException.Oh(ErrorCode.COM1000, $"成员【{member.userName}】的 UserID 不能为空");
  358 + }
  359 + }
  360 + }
  361 +
350 362 // 验证金三角名称是否已存在
351 363 var existingJsj = await _db.Queryable<LqYcsdJsjEntity>().Where(x => x.Yf == input.yf && x.Md == input.md && x.Jsj == input.jsj).FirstAsync();
352 364 if (existingJsj != null)
... ... @@ -434,7 +446,7 @@ namespace NCC.Extend.LqYcsdJsj
434 446 if (string.IsNullOrEmpty(input.jsjId))
435 447 throw NCCException.Oh("金三角ID不能为空");
436 448 if (string.IsNullOrEmpty(input.userId))
437   - throw NCCException.Oh("用户ID不能为空");
  449 + throw NCCException.Oh("用户ID不能为空(请确保选择了有效的系统用户)");
438 450 if (string.IsNullOrEmpty(input.userName))
439 451 throw NCCException.Oh("用户姓名不能为空");
440 452  
... ...
sql/主任工资表新增毛利相关字段.sql
... ... @@ -31,3 +31,4 @@ ADD COLUMN F_GrossProfit DECIMAL(18,2) DEFAULT 0.00 COMMENT &#39;毛利(销售业ç
31 31 ALTER TABLE lq_director_salary_statistics
32 32 MODIFY COLUMN F_StoreTotalPerformance DECIMAL(18,2) DEFAULT 0.00 COMMENT 'é—¨åº—æ€»ä¸šç»©ï¼ˆæ¯›åˆ©ï¼Œç”¨äºŽææˆè®¡ç®—ï¼‰';
33 33  
  34 +
... ...
sql/修复储扣表ItemCategory字段为空的数据.sql 0 → 100644
  1 +-- ============================================
  2 +-- 修复储扣表(lq_kd_deductinfo)中 F_ItemCategory 字段为空的数据
  3 +-- ============================================
  4 +-- 说明:此脚本用于修复储扣表中品项分类字段为空的历史数据
  5 +--
  6 +-- 问题原因:
  7 +-- 1. 更新开单时使用了错误的字段(item.DeductId 而不是 item.ItemId)来查询品项分类
  8 +-- 2. 如果 item.ItemId 为空或无效,查询会返回 null
  9 +--
  10 +-- 修复逻辑:
  11 +-- 通过 F_ItemId 关联 lq_xmzl 表,获取品项分类(qt2)并更新到 F_ItemCategory 字段
  12 +--
  13 +-- 注意事项:
  14 +-- - 只更新有效记录(F_IsEffective = 1)
  15 +-- - 只更新分类字段为空的记录
  16 +-- - 只更新品项分类存在且不为空的记录
  17 +
  18 +-- ============================================
  19 +-- 1. 查看需要修复的记录数
  20 +-- ============================================
  21 +SELECT
  22 + COUNT(*) as TotalNullCount,
  23 + COUNT(CASE WHEN item.qt2 IS NOT NULL THEN 1 END) as CanFixCount
  24 +FROM lq_kd_deductinfo deduct
  25 +LEFT JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
  26 +WHERE deduct.F_IsEffective = 1
  27 + AND deduct.F_ItemCategory IS NULL;
  28 +
  29 +-- ============================================
  30 +-- 2. 修复历史数据:从项目资料表中获取品项分类
  31 +-- ============================================
  32 +UPDATE lq_kd_deductinfo deduct
  33 +INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
  34 +SET deduct.F_ItemCategory = item.qt2
  35 +WHERE deduct.F_IsEffective = 1
  36 + AND deduct.F_ItemCategory IS NULL
  37 + AND item.qt2 IS NOT NULL;
  38 +
  39 +-- ============================================
  40 +-- 3. 验证修复结果
  41 +-- ============================================
  42 +-- 查看修复后的统计信息
  43 +SELECT
  44 + COUNT(*) as TotalCount,
  45 + COUNT(F_ItemCategory) as HasCategoryCount,
  46 + COUNT(*) - COUNT(F_ItemCategory) as NullCategoryCount
  47 +FROM lq_kd_deductinfo
  48 +WHERE F_IsEffective = 1;
  49 +
  50 +-- 查看仍然为空的记录(需要人工处理)
  51 +SELECT
  52 + deduct.F_Id,
  53 + deduct.F_BillingId,
  54 + deduct.F_ItemId,
  55 + deduct.F_ItemName,
  56 + deduct.F_ItemCategory,
  57 + item.qt2 as ItemQt2,
  58 + CASE
  59 + WHEN item.F_Id IS NULL THEN '品项不存在'
  60 + WHEN item.qt2 IS NULL THEN '品项分类为空'
  61 + ELSE '其他原因'
  62 + END as Reason
  63 +FROM lq_kd_deductinfo deduct
  64 +LEFT JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
  65 +WHERE deduct.F_IsEffective = 1
  66 + AND deduct.F_ItemCategory IS NULL
  67 +LIMIT 10;
  68 +
... ...
sql/创建合同成本按月统计表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建合同成本按月统计表(lq_contract_monthly_cost)
  3 +-- ============================================
  4 +-- 说明:用于按月统计每个合同的成本,便于后续统计和查询
  5 +--
  6 +-- 业务逻辑:
  7 +-- 1. 根据合同起始日期、结束日期,按月生成成本记录
  8 +-- 2. 每个月成本 = 缴租金额 / 交租周期
  9 +-- 3. 例如:合同一年,交费周期3个月,缴租金额3000元
  10 +-- - 每个月成本 = 3000 / 3 = 1000元
  11 +-- - 生成12个月的记录,每个月都是1000元
  12 +--
  13 +-- 字段说明:
  14 +-- F_Month:统计月份(格式:YYYY-MM-01,表示该月的第一天)
  15 +-- F_MonthlyCost:该月的合同成本(缴租金额 / 交租周期)
  16 +--
  17 +-- 索引设计:
  18 +-- - 主键:F_Id
  19 +-- - 合同ID索引:F_ContractId(用于查询某个合同的所有月份成本)
  20 +-- - 月份索引:F_Month(用于按月份统计)
  21 +-- - 门店ID索引:F_StoreId(用于按门店统计)
  22 +-- - 联合索引:(F_StoreId, F_Month) - 用于查询某个门店某个月的成本
  23 +-- - 联合索引:(F_ContractId, F_Month) - 用于查询某个合同某个月的成本
  24 +
  25 +CREATE TABLE IF NOT EXISTS `lq_contract_monthly_cost` (
  26 + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID',
  27 + `F_ContractId` VARCHAR(50) NOT NULL COMMENT '合同ID(关联lq_contract.F_Id)',
  28 + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id,冗余字段便于查询)',
  29 + `F_StoreName` VARCHAR(200) NOT NULL COMMENT '店名(冗余字段,便于查询)',
  30 + `F_Category` VARCHAR(100) DEFAULT NULL COMMENT '分类(冗余字段,便于按分类统计)',
  31 + `F_Month` DATETIME NOT NULL COMMENT '统计月份(格式:YYYY-MM-01,表示该月的第一天)',
  32 + `F_MonthlyCost` DECIMAL(18,2) NOT NULL COMMENT '该月的合同成本(缴租金额 / 交租周期)',
  33 + `F_PaymentCycle` INT NOT NULL COMMENT '交租周期(冗余字段,便于查询)',
  34 + `F_PaymentAmount` DECIMAL(18,2) NOT NULL COMMENT '缴租金额(冗余字段,便于查询)',
  35 + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1-有效,0-无效)',
  36 + `F_CreateUser` VARCHAR(50) NOT NULL COMMENT '创建人ID',
  37 + `F_CreateTime` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  38 + `F_UpdateTime` DATETIME DEFAULT NULL COMMENT '更新时间',
  39 + PRIMARY KEY (`F_Id`),
  40 + KEY `idx_contract_id` (`F_ContractId`) COMMENT '合同ID索引',
  41 + KEY `idx_month` (`F_Month`) COMMENT '月份索引',
  42 + KEY `idx_store_id` (`F_StoreId`) COMMENT '门店ID索引',
  43 + KEY `idx_category` (`F_Category`) COMMENT '分类索引',
  44 + KEY `idx_store_month` (`F_StoreId`, `F_Month`) COMMENT '门店+月份联合索引',
  45 + KEY `idx_contract_month` (`F_ContractId`, `F_Month`) COMMENT '合同+月份联合索引',
  46 + KEY `idx_category_month` (`F_Category`, `F_Month`) COMMENT '分类+月份联合索引',
  47 + KEY `idx_is_effective` (`F_IsEffective`) COMMENT '是否有效索引',
  48 + KEY `idx_create_time` (`F_CreateTime`) COMMENT '创建时间索引'
  49 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同成本按月统计表';
  50 +
  51 +-- ============================================
  52 +-- 数据示例说明
  53 +-- ============================================
  54 +-- 合同示例:
  55 +-- F_ContractId: '768041985045955845'
  56 +-- F_StoreId: '1649328471923847168'
  57 +-- F_StoreName: '绿纤总部'
  58 +-- F_ContractStartDate: '2025-01-01 00:00:00'
  59 +-- F_ContractEndDate: '2025-12-31 23:59:59'
  60 +-- F_PaymentAmount: 3000.00
  61 +-- F_PaymentCycle: 3
  62 +--
  63 +-- 每个月成本 = 3000 / 3 = 1000元
  64 +--
  65 +-- 对应的成本记录(自动生成):
  66 +-- 记录1: F_Month='2025-01-01', F_MonthlyCost=1000.00
  67 +-- 记录2: F_Month='2025-02-01', F_MonthlyCost=1000.00
  68 +-- 记录3: F_Month='2025-03-01', F_MonthlyCost=1000.00
  69 +-- 记录4: F_Month='2025-04-01', F_MonthlyCost=1000.00
  70 +-- ...(共12条记录)
  71 +
... ...
sql/合同成本表添加分类字段.sql 0 → 100644
  1 +-- ============================================
  2 +-- 为合同成本按月统计表(lq_contract_monthly_cost)添加分类字段
  3 +-- ============================================
  4 +-- 说明:此脚本为合同成本表添加分类字段,用于按分类统计合同成本
  5 +--
  6 +-- 字段说明:
  7 +-- F_Category:分类(冗余字段,便于按分类统计)
  8 +--
  9 +-- 业务含义:
  10 +-- - 分类字段用于区分不同类型的合同(如:租门店、员工宿舍、车辆、场所等)
  11 +-- - 便于后续按分类统计合同成本
  12 +--
  13 +-- 注意事项:
  14 +-- - 字段类型为VARCHAR(100),允许为NULL(历史数据可能没有分类)
  15 +-- - 字段位置:放在 F_StoreName 字段之后
  16 +-- - 创建后需要更新历史数据,从合同表中获取对应的分类
  17 +
  18 +-- ============================================
  19 +-- 1. 添加分类字段
  20 +-- ============================================
  21 +ALTER TABLE `lq_contract_monthly_cost`
  22 +ADD COLUMN `F_Category` VARCHAR(100) NULL COMMENT '分类(冗余字段,便于按分类统计)' AFTER `F_StoreName`;
  23 +
  24 +-- ============================================
  25 +-- 2. 添加分类索引
  26 +-- ============================================
  27 +ALTER TABLE `lq_contract_monthly_cost`
  28 +ADD INDEX `idx_category` (`F_Category`) COMMENT '分类索引';
  29 +
  30 +-- ============================================
  31 +-- 3. 添加分类+月份联合索引
  32 +-- ============================================
  33 +ALTER TABLE `lq_contract_monthly_cost`
  34 +ADD INDEX `idx_category_month` (`F_Category`, `F_Month`) COMMENT '分类+月份联合索引';
  35 +
  36 +-- ============================================
  37 +-- 4. 更新历史数据:从合同表中获取分类
  38 +-- ============================================
  39 +UPDATE `lq_contract_monthly_cost` cost
  40 +INNER JOIN `lq_contract` contract ON cost.F_ContractId = contract.F_Id
  41 +SET cost.F_Category = contract.F_Category
  42 +WHERE cost.F_Category IS NULL
  43 + AND contract.F_Category IS NOT NULL;
  44 +
  45 +-- ============================================
  46 +-- 5. 验证更新结果
  47 +-- ============================================
  48 +-- 查看更新后的统计信息
  49 +SELECT
  50 + COUNT(*) as TotalCount,
  51 + COUNT(F_Category) as HasCategoryCount,
  52 + COUNT(*) - COUNT(F_Category) as NullCategoryCount
  53 +FROM lq_contract_monthly_cost
  54 +WHERE F_IsEffective = 1;
  55 +
  56 +-- 查看各分类的统计信息
  57 +SELECT
  58 + F_Category,
  59 + COUNT(*) as RecordCount,
  60 + SUM(F_MonthlyCost) as TotalCost
  61 +FROM lq_contract_monthly_cost
  62 +WHERE F_IsEffective = 1
  63 +GROUP BY F_Category
  64 +ORDER BY TotalCost DESC;
  65 +
  66 +
... ...
sql/开单扣减信息表添加开单时间字段.sql 0 → 100644
  1 +-- ============================================
  2 +-- 为开单扣减信息表(lq_kd_deductinfo)添加开单时间字段
  3 +-- ============================================
  4 +-- 说明:此脚本为开单扣减信息表添加开单时间字段,用于存储对应的开单时间
  5 +--
  6 +-- 字段说明:
  7 +-- F_BillingTime:开单时间,用于存储对应的开单记录的开单时间(kdrq)
  8 +--
  9 +-- 业务含义:
  10 +-- - 开单时间用于记录储扣对应的开单时间,便于统计和查询
  11 +-- - 开单时间来源于开单记录表(lq_kd_kdjlb)的 kdrq 字段
  12 +--
  13 +-- 注意事项:
  14 +-- - 字段类型为DATETIME,允许为NULL(历史数据可能没有开单时间)
  15 +-- - 字段位置:放在 F_BillingId 字段之后
  16 +-- - 创建后需要更新历史数据,从开单记录表中获取对应的开单时间
  17 +
  18 +-- ============================================
  19 +-- 1. 添加开单时间字段
  20 +-- ============================================
  21 +ALTER TABLE `lq_kd_deductinfo`
  22 +ADD COLUMN `F_BillingTime` DATETIME NULL COMMENT '开单时间' AFTER `F_BillingId`;
  23 +
  24 +-- ============================================
  25 +-- 2. 更新历史数据:从开单记录表中获取开单时间
  26 +-- ============================================
  27 +UPDATE `lq_kd_deductinfo` deduct
  28 +INNER JOIN `lq_kd_kdjlb` billing ON deduct.F_BillingId = billing.F_Id
  29 +SET deduct.F_BillingTime = billing.kdrq
  30 +WHERE deduct.F_BillingTime IS NULL
  31 + AND billing.kdrq IS NOT NULL;
  32 +
  33 +-- ============================================
  34 +-- 3. 验证更新结果
  35 +-- ============================================
  36 +-- 查看更新后的统计信息
  37 +SELECT
  38 + COUNT(*) as TotalCount,
  39 + COUNT(F_BillingTime) as HasBillingTimeCount,
  40 + COUNT(*) - COUNT(F_BillingTime) as NullBillingTimeCount
  41 +FROM lq_kd_deductinfo;
  42 +
  43 +-- 查看有开单时间但开单记录不存在的记录(数据异常检查)
  44 +SELECT
  45 + deduct.F_Id,
  46 + deduct.F_BillingId,
  47 + deduct.F_BillingTime
  48 +FROM lq_kd_deductinfo deduct
  49 +LEFT JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
  50 +WHERE deduct.F_BillingTime IS NOT NULL
  51 + AND billing.F_Id IS NULL
  52 +LIMIT 10;
  53 +
... ...
sql/排查生美业绩统计差异-简化版.sql 0 → 100644
  1 +-- ============================================
  2 +-- 排查生美业绩统计差异 - 简化版
  3 +-- ============================================
  4 +-- 问题:品项明细表统计生美数据是1869781.81,但日报天王团统计教育一部+教育二部合计是1876061.21,差异6279.40
  5 +--
  6 +-- 分析思路:
  7 +-- 1. 检查是否有门店在lq_md_target表中有多条记录(同一月份)
  8 +-- 2. 对比品项明细表统计和日报天王团统计的差异
  9 +-- 3. 检查是否有数据被重复统计
  10 +
  11 +-- ============================================
  12 +-- 1. 品项明细表统计生美业绩(所有门店,不限制部门归属)
  13 +-- ============================================
  14 +SELECT
  15 + '品项明细表统计' AS 统计来源,
  16 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
  17 +FROM lq_kd_pxmx pxmx
  18 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  19 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  20 +WHERE pxmx.F_IsEffective = 1
  21 + AND billing.F_IsEffective = 1
  22 + AND item.F_IsEffective = 1
  23 + AND item.qt2 = '生美'
  24 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  25 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH);
  26 +
  27 +-- ============================================
  28 +-- 2. 日报天王团统计生美业绩(只统计有部门归属的门店,按部门分组)
  29 +-- ============================================
  30 +SELECT
  31 + '日报天王团统计' AS 统计来源,
  32 + target.F_EducationDepartment as 部门ID,
  33 + dept.F_FullName as 部门名称,
  34 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩
  35 +FROM lq_kd_pxmx pxmx
  36 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  37 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  38 +INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  39 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  40 +LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
  41 +WHERE pxmx.F_IsEffective = 1
  42 + AND billing.F_IsEffective = 1
  43 + AND item.F_IsEffective = 1
  44 + AND item.qt2 = '生美'
  45 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  46 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  47 + AND target.F_EducationDepartment IS NOT NULL
  48 + AND target.F_EducationDepartment != ''
  49 +GROUP BY target.F_EducationDepartment, dept.F_FullName;
  50 +
  51 +-- ============================================
  52 +-- 3. 检查是否有门店在lq_md_target表中有多条记录(同一月份)
  53 +-- ============================================
  54 +SELECT
  55 + F_StoreId,
  56 + F_Month,
  57 + COUNT(*) as record_count
  58 +FROM lq_md_target
  59 +WHERE F_Month = DATE_FORMAT(NOW(), '%Y%m')
  60 + AND (F_EducationDepartment IS NOT NULL AND F_EducationDepartment != '')
  61 +GROUP BY F_StoreId, F_Month
  62 +HAVING COUNT(*) > 1;
  63 +
  64 +-- ============================================
  65 +-- 4. 检查是否有生美品项的开单记录,但门店在lq_md_target表中没有设置F_EducationDepartment
  66 +-- ============================================
  67 +SELECT
  68 + '未归属门店的生美业绩' AS 统计来源,
  69 + COUNT(DISTINCT billing.djmd) as 门店数量,
  70 + COUNT(*) as 开单记录数,
  71 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
  72 +FROM lq_kd_pxmx pxmx
  73 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  74 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  75 +LEFT JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  76 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  77 +WHERE pxmx.F_IsEffective = 1
  78 + AND billing.F_IsEffective = 1
  79 + AND item.F_IsEffective = 1
  80 + AND item.qt2 = '生美'
  81 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  82 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  83 + AND (target.F_StoreId IS NULL
  84 + OR target.F_EducationDepartment IS NULL
  85 + OR target.F_EducationDepartment = '');
  86 +
  87 +-- ============================================
  88 +-- 5. 关键检查:查看每个门店的生美业绩,看看是否有重复统计
  89 +-- ============================================
  90 +SELECT
  91 + billing.djmd as 门店ID,
  92 + md.Dm as 门店名称,
  93 + target.F_EducationDepartment as 教育部门ID,
  94 + dept.F_FullName as 教育部门名称,
  95 + COUNT(*) as 开单记录数,
  96 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩,
  97 + COUNT(DISTINCT target.F_Id) as 目标表记录数
  98 +FROM lq_kd_pxmx pxmx
  99 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  100 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  101 +INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  102 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  103 +LEFT JOIN lq_mdxx md ON billing.djmd = md.F_Id
  104 +LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
  105 +WHERE pxmx.F_IsEffective = 1
  106 + AND billing.F_IsEffective = 1
  107 + AND item.F_IsEffective = 1
  108 + AND item.qt2 = '生美'
  109 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  110 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  111 + AND target.F_EducationDepartment IS NOT NULL
  112 + AND target.F_EducationDepartment != ''
  113 +GROUP BY billing.djmd, md.Dm, target.F_EducationDepartment, dept.F_FullName
  114 +HAVING COUNT(DISTINCT target.F_Id) > 1
  115 +ORDER BY 生美业绩 DESC;
  116 +
  117 +
  118 +
  119 +
  120 +
  121 +
  122 +
... ...
sql/排查生美业绩统计差异详细.sql 0 → 100644
  1 +-- ============================================
  2 +-- 排查生美业绩统计差异详细分析
  3 +-- ============================================
  4 +-- 问题:品项明细表统计生美数据是1869781.81,但日报天王团统计教育一部+教育二部合计是1876061.21,差异6279.40
  5 +--
  6 +-- 分析思路:
  7 +-- 1. 检查是否有门店在lq_md_target表中有多条记录(同一月份)
  8 +-- 2. 对比品项明细表统计和日报天王团统计的差异
  9 +-- 3. 检查是否有数据被重复统计
  10 +
  11 +-- ============================================
  12 +-- 1. 检查lq_md_target表中是否有重复记录(同一门店同一月份多条记录)
  13 +-- ============================================
  14 +SELECT
  15 + F_StoreId,
  16 + F_Month,
  17 + COUNT(*) as record_count,
  18 + GROUP_CONCAT(DISTINCT F_EducationDepartment ORDER BY F_EducationDepartment) as education_depts
  19 +FROM lq_md_target
  20 +WHERE F_Month = DATE_FORMAT(NOW(), '%Y%m')
  21 + AND (F_EducationDepartment IS NOT NULL AND F_EducationDepartment != '')
  22 +GROUP BY F_StoreId, F_Month
  23 +HAVING COUNT(*) > 1;
  24 +
  25 +-- ============================================
  26 +-- 2. 品项明细表统计生美业绩(所有门店,不限制部门归属)
  27 +-- ============================================
  28 +SELECT
  29 + '品项明细表统计' AS 统计来源,
  30 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
  31 +FROM lq_kd_pxmx pxmx
  32 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  33 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  34 +WHERE pxmx.F_IsEffective = 1
  35 + AND billing.F_IsEffective = 1
  36 + AND item.F_IsEffective = 1
  37 + AND item.qt2 = '生美'
  38 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  39 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH);
  40 +
  41 +-- ============================================
  42 +-- 3. 日报天王团统计生美业绩(只统计有部门归属的门店,按部门分组)
  43 +-- ============================================
  44 +SELECT
  45 + '日报天王团统计' AS 统计来源,
  46 + target.F_EducationDepartment as 部门ID,
  47 + dept.F_FullName as 部门名称,
  48 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩
  49 +FROM lq_kd_pxmx pxmx
  50 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  51 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  52 +INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  53 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  54 +LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
  55 +WHERE pxmx.F_IsEffective = 1
  56 + AND billing.F_IsEffective = 1
  57 + AND item.F_IsEffective = 1
  58 + AND item.qt2 = '生美'
  59 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  60 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  61 + AND target.F_EducationDepartment IS NOT NULL
  62 + AND target.F_EducationDepartment != ''
  63 +GROUP BY target.F_EducationDepartment, dept.F_FullName;
  64 +
  65 +-- ============================================
  66 +-- 4. 检查是否有门店在lq_md_target表中有多条记录,导致重复统计
  67 +-- ============================================
  68 +SELECT
  69 + billing.djmd as 门店ID,
  70 + md.Dm as 门店名称,
  71 + COUNT(DISTINCT target.F_Id) as 目标表记录数,
  72 + COUNT(*) as 开单记录数,
  73 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额,
  74 + GROUP_CONCAT(DISTINCT target.F_EducationDepartment ORDER BY target.F_EducationDepartment) as 教育部门列表
  75 +FROM lq_kd_pxmx pxmx
  76 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  77 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  78 +INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  79 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  80 +LEFT JOIN lq_mdxx md ON billing.djmd = md.F_Id
  81 +WHERE pxmx.F_IsEffective = 1
  82 + AND billing.F_IsEffective = 1
  83 + AND item.F_IsEffective = 1
  84 + AND item.qt2 = '生美'
  85 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  86 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  87 + AND target.F_EducationDepartment IS NOT NULL
  88 + AND target.F_EducationDepartment != ''
  89 +GROUP BY billing.djmd, md.Dm
  90 +HAVING COUNT(DISTINCT target.F_Id) > 1;
  91 +
  92 +-- ============================================
  93 +-- 5. 检查是否有生美品项的开单记录,但门店在lq_md_target表中没有设置F_EducationDepartment
  94 +-- ============================================
  95 +SELECT
  96 + '未归属门店的生美业绩' AS 统计来源,
  97 + COUNT(DISTINCT billing.djmd) as 门店数量,
  98 + COUNT(*) as 开单记录数,
  99 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
  100 +FROM lq_kd_pxmx pxmx
  101 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  102 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  103 +LEFT JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  104 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  105 +WHERE pxmx.F_IsEffective = 1
  106 + AND billing.F_IsEffective = 1
  107 + AND item.F_IsEffective = 1
  108 + AND item.qt2 = '生美'
  109 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  110 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  111 + AND (target.F_StoreId IS NULL
  112 + OR target.F_EducationDepartment IS NULL
  113 + OR target.F_EducationDepartment = '');
  114 +
  115 +-- ============================================
  116 +-- 6. 关键检查:查看每个门店的生美业绩,看看是否有重复统计
  117 +-- ============================================
  118 +SELECT
  119 + billing.djmd as 门店ID,
  120 + md.Dm as 门店名称,
  121 + target.F_EducationDepartment as 教育部门ID,
  122 + dept.F_FullName as 教育部门名称,
  123 + COUNT(*) as 开单记录数,
  124 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩,
  125 + COUNT(DISTINCT target.F_Id) as 目标表记录数
  126 +FROM lq_kd_pxmx pxmx
  127 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  128 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  129 +INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  130 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  131 +LEFT JOIN lq_mdxx md ON billing.djmd = md.F_Id
  132 +LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
  133 +WHERE pxmx.F_IsEffective = 1
  134 + AND billing.F_IsEffective = 1
  135 + AND item.F_IsEffective = 1
  136 + AND item.qt2 = '生美'
  137 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  138 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  139 + AND target.F_EducationDepartment IS NOT NULL
  140 + AND target.F_EducationDepartment != ''
  141 +GROUP BY billing.djmd, md.Dm, target.F_EducationDepartment, dept.F_FullName
  142 +ORDER BY 生美业绩 DESC;
  143 +
  144 +-- ============================================
  145 +-- 7. 检查是否有门店在lq_md_target表中有多条记录(不同月份,但查询时可能有问题)
  146 +-- ============================================
  147 +SELECT
  148 + F_StoreId,
  149 + COUNT(DISTINCT F_Month) as 月份数,
  150 + GROUP_CONCAT(DISTINCT F_Month ORDER BY F_Month) as 月份列表,
  151 + COUNT(*) as 总记录数
  152 +FROM lq_md_target
  153 +WHERE F_StoreId IN (
  154 + SELECT DISTINCT billing.djmd
  155 + FROM lq_kd_pxmx pxmx
  156 + INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  157 + INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  158 + WHERE pxmx.F_IsEffective = 1
  159 + AND billing.F_IsEffective = 1
  160 + AND item.F_IsEffective = 1
  161 + AND item.qt2 = '生美'
  162 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  163 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  164 +)
  165 + AND (F_EducationDepartment IS NOT NULL AND F_EducationDepartment != '')
  166 +GROUP BY F_StoreId
  167 +HAVING COUNT(*) > 1;
  168 +
  169 +
  170 +
  171 +
  172 +
  173 +
  174 +
... ...
sql/检查生美业绩统计差异.sql 0 → 100644
  1 +-- ============================================
  2 +-- 检查生美业绩统计差异
  3 +-- ============================================
  4 +-- 问题:品项明细表统计生美数据是1869781.81,但日报天王团统计教育一部+教育二部合计是1876061.21,差异6279.40
  5 +--
  6 +-- 可能原因:
  7 +-- 1. 门店在lq_md_target表中有重复记录(同一月份多条记录)
  8 +-- 2. 统计范围不一致(时间范围或门店范围)
  9 +-- 3. 数据关联逻辑问题
  10 +
  11 +-- ============================================
  12 +-- 1. 检查lq_md_target表中是否有重复记录(同一门店同一月份多条记录)
  13 +-- ============================================
  14 +SELECT
  15 + F_StoreId,
  16 + F_Month,
  17 + COUNT(*) as record_count,
  18 + GROUP_CONCAT(DISTINCT F_EducationDepartment) as education_depts
  19 +FROM lq_md_target
  20 +WHERE F_Month = DATE_FORMAT(NOW(), '%Y%m')
  21 + AND (F_EducationDepartment IS NOT NULL AND F_EducationDepartment != '')
  22 +GROUP BY F_StoreId, F_Month
  23 +HAVING COUNT(*) > 1;
  24 +
  25 +-- ============================================
  26 +-- 2. 检查品项明细表统计生美业绩(所有门店,不限制部门归属)
  27 +-- ============================================
  28 +SELECT
  29 + '品项明细表统计' AS 统计来源,
  30 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
  31 +FROM lq_kd_pxmx pxmx
  32 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  33 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  34 +WHERE pxmx.F_IsEffective = 1
  35 + AND billing.F_IsEffective = 1
  36 + AND item.F_IsEffective = 1
  37 + AND item.qt2 = '生美'
  38 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  39 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH);
  40 +
  41 +-- ============================================
  42 +-- 3. 检查日报天王团统计生美业绩(只统计有部门归属的门店,按部门分组)
  43 +-- ============================================
  44 +SELECT
  45 + '日报天王团统计' AS 统计来源,
  46 + target.F_EducationDepartment as 部门ID,
  47 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩
  48 +FROM lq_kd_pxmx pxmx
  49 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  50 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  51 +INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  52 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  53 +WHERE pxmx.F_IsEffective = 1
  54 + AND billing.F_IsEffective = 1
  55 + AND item.F_IsEffective = 1
  56 + AND item.qt2 = '生美'
  57 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  58 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  59 + AND target.F_EducationDepartment IS NOT NULL
  60 + AND target.F_EducationDepartment != ''
  61 +GROUP BY target.F_EducationDepartment;
  62 +
  63 +-- ============================================
  64 +-- 4. 检查是否有生美品项的开单记录,但门店在lq_md_target表中没有设置F_EducationDepartment
  65 +-- ============================================
  66 +SELECT
  67 + '未归属门店的生美业绩' AS 统计来源,
  68 + COUNT(DISTINCT billing.djmd) as 门店数量,
  69 + COUNT(*) as 开单记录数,
  70 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额
  71 +FROM lq_kd_pxmx pxmx
  72 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  73 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  74 +LEFT JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  75 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  76 +WHERE pxmx.F_IsEffective = 1
  77 + AND billing.F_IsEffective = 1
  78 + AND item.F_IsEffective = 1
  79 + AND item.qt2 = '生美'
  80 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  81 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  82 + AND (target.F_StoreId IS NULL
  83 + OR target.F_EducationDepartment IS NULL
  84 + OR target.F_EducationDepartment = '');
  85 +
  86 +-- ============================================
  87 +-- 5. 检查是否有门店在lq_md_target表中有多条记录(可能导致重复统计)
  88 +-- ============================================
  89 +SELECT
  90 + billing.djmd as 门店ID,
  91 + COUNT(DISTINCT target.F_Id) as 目标表记录数,
  92 + COUNT(*) as 开单记录数,
  93 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩总额,
  94 + GROUP_CONCAT(DISTINCT target.F_EducationDepartment) as 教育部门列表
  95 +FROM lq_kd_pxmx pxmx
  96 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  97 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  98 +INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  99 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  100 +WHERE pxmx.F_IsEffective = 1
  101 + AND billing.F_IsEffective = 1
  102 + AND item.F_IsEffective = 1
  103 + AND item.qt2 = '生美'
  104 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  105 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  106 + AND target.F_EducationDepartment IS NOT NULL
  107 + AND target.F_EducationDepartment != ''
  108 +GROUP BY billing.djmd
  109 +HAVING COUNT(DISTINCT target.F_Id) > 1;
  110 +
  111 +-- ============================================
  112 +-- 6. 详细检查:查看每个门店的生美业绩和部门归属情况
  113 +-- ============================================
  114 +SELECT
  115 + billing.djmd as 门店ID,
  116 + md.Dm as 门店名称,
  117 + target.F_EducationDepartment as 教育部门ID,
  118 + dept.F_FullName as 教育部门名称,
  119 + COUNT(*) as 开单记录数,
  120 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as 生美业绩
  121 +FROM lq_kd_pxmx pxmx
  122 +INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  123 +INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  124 +INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId
  125 + AND target.F_Month = DATE_FORMAT(NOW(), '%Y%m')
  126 +LEFT JOIN lq_mdxx md ON billing.djmd = md.F_Id
  127 +LEFT JOIN base_organize dept ON target.F_EducationDepartment = dept.F_Id
  128 +WHERE pxmx.F_IsEffective = 1
  129 + AND billing.F_IsEffective = 1
  130 + AND item.F_IsEffective = 1
  131 + AND item.qt2 = '生美'
  132 + AND billing.kdrq >= DATE_FORMAT(NOW(), '%Y-%m-01')
  133 + AND billing.kdrq < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
  134 + AND target.F_EducationDepartment IS NOT NULL
  135 + AND target.F_EducationDepartment != ''
  136 +GROUP BY billing.djmd, md.Dm, target.F_EducationDepartment, dept.F_FullName
  137 +ORDER BY 生美业绩 DESC;
  138 +
  139 +
  140 +
  141 +
  142 +
  143 +
  144 +
... ...
sql/清洗流水表添加送出送回时间字段.sql 0 → 100644
  1 +-- ============================================
  2 +-- 为清洗流水表(lq_laundry_flow)添加送出/送回时间字段
  3 +-- ============================================
  4 +-- 说明:此脚本为清洗流水表添加送出时间和送回时间字段,用于记录实际的送出/送回时间
  5 +--
  6 +-- 字段说明:
  7 +-- F_SendTime:送出时间,用于记录实际的送出时间(流水类型为0时使用)
  8 +-- F_ReturnTime:送回时间,用于记录实际的送回时间(流水类型为1时使用)
  9 +--
  10 +-- 业务含义:
  11 +-- - 送出时间:记录实际送出清洗的时间,便于后续统计
  12 +-- - 送回时间:记录实际送回清洗的时间,便于后续统计
  13 +-- - F_CreateTime:保持为记录创建时间(系统时间),用于记录数据录入时间
  14 +--
  15 +-- 注意事项:
  16 +-- - 字段类型为DATETIME,允许为NULL(历史数据可能没有这些时间)
  17 +-- - 字段位置:放在 F_CreateTime 字段之后
  18 +-- - 创建后需要更新历史数据,将 F_CreateTime 的值复制到对应的时间字段
  19 +
  20 +-- ============================================
  21 +-- 1. 添加送出时间字段
  22 +-- ============================================
  23 +ALTER TABLE `lq_laundry_flow`
  24 +ADD COLUMN `F_SendTime` DATETIME NULL COMMENT '送出时间(流水类型为0时使用)' AFTER `F_CreateTime`;
  25 +
  26 +-- ============================================
  27 +-- 2. 添加送回时间字段
  28 +-- ============================================
  29 +ALTER TABLE `lq_laundry_flow`
  30 +ADD COLUMN `F_ReturnTime` DATETIME NULL COMMENT '送回时间(流水类型为1时使用)' AFTER `F_SendTime`;
  31 +
  32 +-- ============================================
  33 +-- 3. 更新历史数据:将创建时间复制到对应的时间字段
  34 +-- ============================================
  35 +-- 更新送出记录:将创建时间复制到送出时间
  36 +UPDATE `lq_laundry_flow`
  37 +SET `F_SendTime` = `F_CreateTime`
  38 +WHERE `F_FlowType` = 0
  39 + AND `F_SendTime` IS NULL
  40 + AND `F_CreateTime` IS NOT NULL;
  41 +
  42 +-- 更新送回记录:将创建时间复制到送回时间
  43 +UPDATE `lq_laundry_flow`
  44 +SET `F_ReturnTime` = `F_CreateTime`
  45 +WHERE `F_FlowType` = 1
  46 + AND `F_ReturnTime` IS NULL
  47 + AND `F_CreateTime` IS NOT NULL;
  48 +
  49 +-- ============================================
  50 +-- 4. 验证更新结果
  51 +-- ============================================
  52 +-- 查看更新后的统计信息
  53 +SELECT
  54 + F_FlowType,
  55 + CASE WHEN F_FlowType = 0 THEN '送出' ELSE '送回' END as FlowTypeName,
  56 + COUNT(*) as TotalCount,
  57 + COUNT(CASE WHEN F_FlowType = 0 THEN F_SendTime END) as HasSendTimeCount,
  58 + COUNT(CASE WHEN F_FlowType = 1 THEN F_ReturnTime END) as HasReturnTimeCount
  59 +FROM lq_laundry_flow
  60 +WHERE F_IsEffective = 1
  61 +GROUP BY F_FlowType;
  62 +
  63 +
... ...
test_tianwang_api.py 0 → 100644
  1 +#!/usr/bin/env python3
  2 +# -*- coding: utf-8 -*-
  3 +import json
  4 +import sys
  5 +from datetime import datetime
  6 +
  7 +# 测试接口
  8 +import subprocess
  9 +
  10 +# 测试2025年1月的数据
  11 +cmd1 = [
  12 + 'curl', '-s', '-X', 'POST',
  13 + 'http://localhost:2011/api/Extend/LqDailyReport/get-tianwang-group-performance-completion',
  14 + '-H', 'Content-Type: application/json',
  15 + '-d', '{"startTime": "2025-01-01", "endTime": "2025-01-31"}'
  16 +]
  17 +
  18 +result1 = subprocess.run(cmd1, capture_output=True, text=True)
  19 +data1 = json.loads(result1.stdout)
  20 +
  21 +print("=" * 60)
  22 +print("2025年1月数据")
  23 +print("=" * 60)
  24 +print(f"接口状态: {data1.get('code')}")
  25 +print(f"返回消息: {data1.get('msg')}")
  26 +
  27 +depts1 = [d for d in data1.get('data', []) if '教育' in d.get('DepartmentName', '')]
  28 +print("\n=== 教育部数据 ===")
  29 +for d in depts1:
  30 + print(f"{d.get('DepartmentName')}:")
  31 + print(f" BillingPerformance: {d.get('BillingPerformance')}")
  32 + print(f" RefundPerformance: {d.get('RefundPerformance')}")
  33 + print(f" DeductAmount: {d.get('DeductAmount')}")
  34 + print(f" CompletedPerformance: {d.get('CompletedPerformance')}")
  35 + print(f" StoreCount: {d.get('StoreCount')}")
  36 +
  37 +total1 = sum([d.get('BillingPerformance', 0) for d in depts1])
  38 +print(f"\n教育一部+教育二部合计 BillingPerformance: {total1}")
  39 +
  40 +# 测试当前月份的数据
  41 +current_month = datetime.now().strftime("%Y-%m")
  42 +start_date = f"{current_month}-01"
  43 +end_date = datetime.now().strftime("%Y-%m-%d")
  44 +
  45 +cmd2 = [
  46 + 'curl', '-s', '-X', 'POST',
  47 + 'http://localhost:2011/api/Extend/LqDailyReport/get-tianwang-group-performance-completion',
  48 + '-H', 'Content-Type: application/json',
  49 + '-d', f'{{"startTime": "{start_date}", "endTime": "{end_date}"}}'
  50 +]
  51 +
  52 +result2 = subprocess.run(cmd2, capture_output=True, text=True)
  53 +data2 = json.loads(result2.stdout)
  54 +
  55 +print("\n" + "=" * 60)
  56 +print(f"当前月份 ({current_month}) 数据")
  57 +print("=" * 60)
  58 +print(f"接口状态: {data2.get('code')}")
  59 +
  60 +depts2 = [d for d in data2.get('data', []) if '教育' in d.get('DepartmentName', '')]
  61 +print("\n=== 教育部数据 ===")
  62 +for d in depts2:
  63 + print(f"{d.get('DepartmentName')}:")
  64 + print(f" BillingPerformance: {d.get('BillingPerformance')}")
  65 + print(f" RefundPerformance: {d.get('RefundPerformance')}")
  66 + print(f" DeductAmount: {d.get('DeductAmount')}")
  67 + print(f" CompletedPerformance: {d.get('CompletedPerformance')}")
  68 + print(f" StoreCount: {d.get('StoreCount')}")
  69 +
  70 +total2 = sum([d.get('BillingPerformance', 0) for d in depts2])
  71 +print(f"\n教育一部+教育二部合计 BillingPerformance: {total2}")
  72 +
  73 +
  74 +
  75 +
  76 +
  77 +
... ...
主任工资毛利计算逻辑梳理.md
... ... @@ -300,3 +300,4 @@ ADD COLUMN F_GrossProfit DECIMAL(18,2) DEFAULT 0.00 COMMENT &#39;毛利(销售业ç
300 300 4. **业绩达标判断**:基于毛利是å¦â‰¥ç”Ÿå‘½çº¿
301 301 5. **ææˆè®¡ç®—**ï¼šåŸºäºŽæ¯›åˆ©ï¼Œä¸æ˜¯åŸºäºŽé”€å”®ä¸šç»©
302 302  
  303 +
... ...
储扣表ItemCategory字段为空问题梳理.md 0 → 100644
  1 +# 储扣表 F_ItemCategory 字段为空问题梳理
  2 +
  3 +## 问题描述
  4 +储扣表(lq_kd_deductinfo)中的 `F_ItemCategory` 字段有时候是空的。
  5 +
  6 +## 数据库统计
  7 +- 总有效记录数:1865
  8 +- 有分类字段的记录数:1863
  9 +- 分类字段为空的记录数:2
  10 +
  11 +## 问题分析
  12 +
  13 +### 1. 创建开单时的逻辑(第910行)
  14 +```csharp
  15 +ItemCategory = await _db.Queryable<LqXmzlEntity>()
  16 + .Where(x => x.Id == item.ItemId)
  17 + .Select(x => x.Qt2)
  18 + .FirstAsync(),
  19 +```
  20 +- 使用 `item.ItemId` 查询品项分类
  21 +- 如果 `item.ItemId` 为空或无效,查询会返回 null
  22 +
  23 +### 2. 更新开单时的逻辑(第1257行)
  24 +```csharp
  25 +ItemCategory = await _db.Queryable<LqXmzlEntity>()
  26 + .Where(x => x.Id == item.DeductId) // ❌ 错误:应该使用 item.ItemId
  27 + .Select(x => x.Qt2)
  28 + .FirstAsync()
  29 +```
  30 +- **问题**:使用了 `item.DeductId` 而不是 `item.ItemId`
  31 +- `DeductId` 是品项明细表(lq_kd_pxmx)的ID,不是项目资料表(lq_xmzl)的ID
  32 +- 这会导致查询不到结果,返回 null
  33 +
  34 +### 3. 字段说明
  35 +- **DeductId**:扣减品项关联ID,对应 `lq_kd_pxmx.F_Id`(品项明细表的主键)
  36 +- **ItemId**:品项id,对应 `lq_xmzl.F_Id`(项目资料表的主键)
  37 +- **ItemCategory**:品项分类,应该从 `lq_xmzl.qt2` 获取
  38 +
  39 +### 4. 导致空值的原因
  40 +1. **更新开单时使用了错误的字段**:使用 `item.DeductId` 而不是 `item.ItemId`
  41 +2. **查询不到结果**:如果 `item.ItemId` 为空或无效,`FirstAsync()` 会返回 null
  42 +3. **品项不存在或无效**:如果品项在 `lq_xmzl` 表中不存在或无效,查询也会返回 null
  43 +
  44 +## 解决方案
  45 +
  46 +### ✅ 方案1:修复更新开单时的逻辑(已修复)
  47 +将第1257行的 `item.DeductId` 改为 `item.ItemId`,与创建开单时的逻辑保持一致。
  48 +
  49 +**修复代码**:
  50 +```csharp
  51 +// 修复前(错误)
  52 +ItemCategory = await _db.Queryable<LqXmzlEntity>()
  53 + .Where(x => x.Id == item.DeductId) // ❌ 错误
  54 + .Select(x => x.Qt2)
  55 + .FirstAsync()
  56 +
  57 +// 修复后(正确)
  58 +ItemCategory = await _db.Queryable<LqXmzlEntity>()
  59 + .Where(x => x.Id == item.ItemId) // ✅ 正确
  60 + .Select(x => x.Qt2)
  61 + .FirstAsync()
  62 +```
  63 +
  64 +### ✅ 方案2:修复历史数据(已提供SQL脚本)
  65 +对于已经存在的空值记录,执行 `sql/修复储扣表ItemCategory字段为空的数据.sql` 脚本修复。
  66 +
  67 +### 方案3:增加容错处理(可选)
  68 +在查询时增加容错处理,如果查询不到结果,尝试从其他途径获取分类。
  69 +
  70 +## 验证
  71 +从数据库查询结果看:
  72 +- F_ItemId = '76',对应的 lq_xmzl 表中 qt2 = '科美'(存在且有效)
  73 +- F_ItemId = '100024',对应的 lq_xmzl 表中 qt2 = '科美'(存在且有效)
  74 +
  75 +说明这些记录的 `ItemId` 是有效的,问题应该是在更新开单时使用了错误的字段。
  76 +
... ...
总部股份统计说明.md 0 → 100644
  1 +# 总部股份统计说明 (Headquarters Share Statistics)
  2 +
  3 +## 1. 概述
  4 +本以文档详细说明总部股份统计的逻辑。
  5 +
  6 +---
  7 +
  8 +## 2. 收入 (Income)
  9 +
  10 +### 2.1 收入 (General Income)
  11 +* **说明**: 全部开单业绩的 9%(需减去退款)。
  12 +* **计算逻辑**: (所有门店的总开单实付业绩 - 所有门店的总实退金额) * 9%。
  13 +* **数据来源**: `lq_kd_kdjlb`, `lq_hytk_hytk`。
  14 +
  15 +### 2.2 收入-科技部 (Tech Dept Income)
  16 +* **说明**: 科美开单的 30% 里面的 9%。
  17 +* **计算逻辑**: (所有门店的科美开单业绩 * 30%) * 9%。
  18 + * 即: `(总科美业绩 - 总科美退款) * 0.3 * 0.09`。
  19 +* **数据来源**: `lq_kd_pxmx` (筛选科美) 关联开单和退款。
  20 +
  21 +---
  22 +
  23 +## 3. 成本 (Cost)
  24 +
  25 +### 3.1 成本-报销 (Headquarters Expenses)
  26 +* **说明**: 报销里面的总部的费用。
  27 +* **计算逻辑**: 筛选报销申请中,归属于“总部”的报销。
  28 +* **数据来源**:
  29 + * 表: `lq_reimbursement_application`。
  30 + * **判定方式**: 根据 `F_ApplicationStoreId` 是否为总部机构,或 报销分类是否标记为总部费用。
  31 +
  32 +### 3.2 成本-人工 (Labor)
  33 +* **说明**: 不处理(保留字段)。
  34 +
  35 +### 3.3 成本-教育部房租 (Education Dept Rent)
  36 +* **说明**: 合同里面获取。
  37 +* **数据来源**:
  38 + * 表: `lq_contract_rent_detail`。
  39 + * **判定**: 关联 `lq_contract`,筛选 合同类型/归属 为“教育部” 的合同。
  40 +
  41 +### 3.4 成本-仓库房租 (Warehouse Rent)
  42 +* **说明**: 合同里面获取。
  43 +* **数据来源**:
  44 + * 表: `lq_contract_rent_detail`。
  45 + * **判定**: 关联 `lq_contract`,筛选 合同类型/归属 为“仓库” 的合同。
  46 +
  47 +### 3.5 成本-总部房租 (HQ Rent)
  48 +* **说明**: 合同里面获取。
  49 +* **数据来源**:
  50 + * 表: `lq_contract_rent_detail`。
  51 + * **判定**: 关联 `lq_contract`,筛选 合同类型/归属 为“总部” 的合同。
  52 +
  53 +---
  54 +
  55 +## 4. 利润计算 (Profit Calculation)
  56 +
  57 +### 4.1 总部运营利润
  58 +```
  59 +总部运营利润 = 收入(门店9%)
  60 + + 收入(科技部9%)
  61 + - 成本(报销)
  62 + - 成本(人工)
  63 + - 成本(总部房租 + 教育部房租 + 仓库房租)
  64 +```
... ...
测试储扣接口.sh 0 → 100755
  1 +#!/bin/bash
  2 +
  3 +# 测试储扣相关接口
  4 +
  5 +# 1. 获取token
  6 +echo "=== 1. 获取Token ==="
  7 +TOKEN_RESPONSE=$(curl -s -X POST "http://localhost:2011/api/oauth/Login" \
  8 + -H "Content-Type: application/x-www-form-urlencoded" \
  9 + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e")
  10 +
  11 +echo "$TOKEN_RESPONSE" | python3 -m json.tool
  12 +
  13 +TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data['data']['token'])")
  14 +
  15 +if [ -z "$TOKEN" ]; then
  16 + echo "❌ Token获取失败"
  17 + exit 1
  18 +fi
  19 +
  20 +echo "✅ Token获取成功: ${TOKEN:0:50}..."
  21 +echo ""
  22 +
  23 +# 2. 测试储扣金额统计接口(按品项分类)
  24 +echo "=== 2. 测试储扣金额统计接口(get-deduct-amount-statistics)==="
  25 +echo "请求参数: {\"startTime\": \"2025-11-01\", \"endTime\": \"2025-11-30\"}"
  26 +echo ""
  27 +
  28 +DEDUCT_RESPONSE=$(curl -s -X POST "http://localhost:2011/api/Extend/LqDailyReport/get-deduct-amount-statistics" \
  29 + -H "Authorization: $TOKEN" \
  30 + -H "Content-Type: application/json" \
  31 + -d '{"startTime": "2025-11-01", "endTime": "2025-11-30"}')
  32 +
  33 +echo "$DEDUCT_RESPONSE" | python3 -m json.tool
  34 +
  35 +# 检查返回结果
  36 +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
  37 + echo "✅ 储扣金额统计接口调用成功"
  38 +
  39 + # 提取数据
  40 + YIMEI=$(echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('data', {}).get('yiMeiAmount', 0))" 2>/dev/null)
  41 + SHENGMEI=$(echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('data', {}).get('shengMeiAmount', 0))" 2>/dev/null)
  42 + KEMEI=$(echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('data', {}).get('keMeiAmount', 0))" 2>/dev/null)
  43 + TOTAL=$(echo "$DEDUCT_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('data', {}).get('totalAmount', 0))" 2>/dev/null)
  44 +
  45 + echo "医美储扣金额: $YIMEI"
  46 + echo "生美储扣金额: $SHENGMEI"
  47 + echo "科美储扣金额: $KEMEI"
  48 + echo "总储扣金额: $TOTAL"
  49 +else
  50 + echo "❌ 储扣金额统计接口调用失败"
  51 +fi
  52 +
  53 +echo ""
  54 +echo ""
  55 +
  56 +# 3. 测试部门业绩完成情况接口(包含储扣统计)
  57 +echo "=== 3. 测试部门业绩完成情况接口(get-tianwang-group-performance-completion)==="
  58 +echo "请求参数: {\"startTime\": \"2025-11-01\", \"endTime\": \"2025-11-30\"}"
  59 +echo ""
  60 +
  61 +DEPT_RESPONSE=$(curl -s -X POST "http://localhost:2011/api/Extend/LqDailyReport/get-tianwang-group-performance-completion" \
  62 + -H "Authorization: $TOKEN" \
  63 + -H "Content-Type: application/json" \
  64 + -d '{"startTime": "2025-11-01", "endTime": "2025-11-30"}')
  65 +
  66 +echo "$DEPT_RESPONSE" | python3 -m json.tool
  67 +
  68 +# 检查返回结果
  69 +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
  70 + echo "✅ 部门业绩完成情况接口调用成功"
  71 +
  72 + # 提取第一个部门的数据
  73 + 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)
  74 +
  75 + if [ ! -z "$FIRST_DEPT" ] && [ "$FIRST_DEPT" != "{}" ]; then
  76 + echo ""
  77 + echo "第一个部门的数据:"
  78 + echo "$FIRST_DEPT"
  79 + fi
  80 +else
  81 + echo "❌ 部门业绩完成情况接口调用失败"
  82 +fi
  83 +
  84 +echo ""
  85 +echo "=== 测试完成 ==="
  86 +
... ...
科技部股份统计说明.md 0 → 100644
  1 +# 科技部股份统计说明 (Tech Department Share Statistics)
  2 +
  3 +## 1. 概述
  4 +本以文档详细说明科技部股份统计的逻辑。科技部需区分 **科技一部** 和 **科技二部**,根据管理的门店归属进行区分。
  5 +
  6 +---
  7 +
  8 +## 2. 收入 (Income)
  9 +
  10 +### 2.1 收入
  11 +* **说明**: 各个门店科美开单的 30%(需减去退款)。
  12 +* **计算逻辑**:
  13 + 1. 找到该科技部(一部或二部)管辖的所有门店。
  14 + 2. 统计这些门店的 **科美项目** 开单实付业绩。
  15 + 3. 减去 对应的科美项目实退金额。
  16 + 4. 结果 * 30%。
  17 +* **数据来源**:
  18 + * 门店归属: `lq_mdxx` (门店信息表) 中的 `kjb` (科技部) 字段或其他归属字段。
  19 + * 开单: `lq_kd_pxmx` (`F_BeautyType` = 科美) 关联 `lq_kd_kdjlb`.
  20 + * 退款: `lq_hytk_jksyj` (包含退款品项信息) 或 `lq_hytk_hytk`。
  21 +
  22 +---
  23 +
  24 +## 3. 成本 (Cost)
  25 +
  26 +### 3.1 成本-报销 (Reimbursement)
  27 +* **说明**: 报销费用里面的“科技部报销”。
  28 +* **计算逻辑**:
  29 + * 筛选一级分类为“科技部报销”的申请。
  30 + * 区分一部/二部:根据报销单的 `F_ApplicationStoreId` (申请门店) 的归属。
  31 +* **数据来源**:
  32 + * 表: `lq_reimbursement_application`, `lq_reimbursement_category`。
  33 +
  34 +### 3.2 成本-人工-科技部老师 (Teacher Base Salary)
  35 +* **说明**: 科技部老师底薪。
  36 +* **数据来源**:
  37 + * 表: `lq_tech_teacher_salary_statistics` (科技部老师工资统计表 - 需确认表名)。
  38 + * 字段: `F_ActualBaseSalary`。
  39 +
  40 +### 3.3 成本-人工-科技部手工费 (Teacher Manual Fee)
  41 +* **说明**: 来源于消耗里面科技部老师业绩的手工费。
  42 +* **数据来源**:
  43 + * 表: `lq_xh_jksyj` (消耗表)。
  44 + * 条件: `Jks` (健康师) 为科技部老师 且 `F_BeautyType` = 科美。
  45 + * 字段: `F_LaborCost`。
  46 +
  47 +### 3.4 成本-人工-科技部开单提成 (Teacher Billing Commission)
  48 +* **说明**: 科技部老师的卡单提成。
  49 +* **数据来源**:
  50 + * 表: `lq_tech_teacher_salary_statistics`。
  51 + * 字段: `F_BillingCommission` (开单提成 - 需确认具体字段名)。
  52 +
  53 +### 3.5 成本-人工-科技部消耗提成 (Teacher Consumption Commission)
  54 +* **说明**: 科技部老师的消耗提成。
  55 +* **数据来源**:
  56 + * 表: `lq_tech_teacher_salary_statistics`。
  57 + * 字段: `F_ConsumptionCommission` (消耗提成 - 需确认具体字段名)。
  58 +
  59 +### 3.6 成本-人工-科技部总经理 (General Manager Base Salary)
  60 +* **说明**: 科技部总经理底薪。
  61 +* **数据来源**:
  62 + * 表: `lq_tech_general_manager_salary_statistics` (科技部总经理工资表)。
  63 + * 字段: `F_ActualBaseSalary`。
  64 +
  65 +### 3.7 成本-人工-科技部提成 (General Manager Commission)
  66 +* **说明**: 科技部总经理提成。
  67 +* **数据来源**:
  68 + * 表: `lq_tech_general_manager_salary_statistics`。
  69 + * 字段: `F_TotalCommissionAmount`。
  70 +
  71 +### 3.8 保留/不处理字段
  72 +* 成本-人工-科技部专家提成
  73 +* 成本-人工-科技部加班
  74 +* 奖励-科技部
  75 +* 成本-其他1, 其他2
  76 +
  77 +---
  78 +
  79 +## 4. 利润计算 (Profit Calculation)
  80 +
  81 +### 4.1 科技部利润
  82 +```
  83 +科技部利润 = 收入
  84 + - 成本报销
  85 + - 成本人工(底薪 + 手工 + 开单提成 + 消耗提成 + 专家提成 + 加班 + 总经理底薪 + 总经理提成)
  86 + - 其他
  87 +```
... ...
股份统计表结构.sql 0 → 100644
  1 +/*
  2 + 股份统计相关表结构设计
  3 + Created Date: 2025-12-16
  4 + Description: 用于存储门店、科技部、总部的股份统计数据
  5 +*/
  6 +
  7 +-- ----------------------------
  8 +-- 1. 门店股份统计表 (lq_share_statistics_store)
  9 +-- ----------------------------
  10 +DROP TABLE IF EXISTS `lq_share_statistics_store`;
  11 +CREATE TABLE `lq_share_statistics_store` (
  12 + `F_Id` varchar(50) NOT NULL COMMENT '主键ID',
  13 + `F_StoreId` varchar(50) NOT NULL COMMENT '门店ID',
  14 + `F_StoreName` varchar(100) DEFAULT NULL COMMENT '门店名称',
  15 + `F_StatisticsMonth` varchar(50) NOT NULL COMMENT '统计月份(YYYYMM)',
  16 +
  17 + -- 收入部分
  18 + `F_MainIncome` decimal(18,2) DEFAULT '0.00' COMMENT '主营收入(消耗总业绩, 不扣减退款)',
  19 + `F_ConsumeLifeBeauty` decimal(18,2) DEFAULT '0.00' COMMENT '生美消耗业绩',
  20 + `F_ConsumeTechBeauty` decimal(18,2) DEFAULT '0.00' COMMENT '科美消耗业绩',
  21 + `F_ConsumeMedicalBeauty` decimal(18,2) DEFAULT '0.00' COMMENT '医美消耗业绩',
  22 + `F_ConsumeCooperation` decimal(18,2) DEFAULT '0.00' COMMENT '合作消费业绩',
  23 + `F_ConsumeProduct` decimal(18,2) DEFAULT '0.00' COMMENT '产品消耗业绩',
  24 + `F_ConsumeOther` decimal(18,2) DEFAULT '0.00' COMMENT '其他消耗业绩',
  25 + `F_OtherIncome` decimal(18,2) DEFAULT '0.00' COMMENT '其他收入(退款差额>0)',
  26 + `F_AdvanceReceipt` decimal(18,2) DEFAULT '0.00' COMMENT '预收款(开单实付)',
  27 + `F_Refund` decimal(18,2) DEFAULT '0.00' COMMENT '实际退款(实退业绩)',
  28 + `F_Receivables` decimal(18,2) DEFAULT '0.00' COMMENT '应收(合作医院)',
  29 + `F_BankDeposit` decimal(18,2) DEFAULT '0.00' COMMENT '银行存款(开单实付-应收)',
  30 +
  31 + -- 成本部分
  32 + `F_CostProduct` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-产品(仓库领取)',
  33 + `F_CostFutian` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-福田(福田仓库领取)',
  34 + `F_CostTowel` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-毛巾(清洗送出)',
  35 + `F_CostTechDept` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-科技部(科美业绩30%)',
  36 + `F_CostManagementFee` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-管理费(总业绩9%)',
  37 + `F_CostCooperation` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-合作(保留)',
  38 + `F_CostMajorProject` decimal(18,2) DEFAULT '0.00' COMMENT '主营成本-大项目(保留)',
  39 + `F_CostOther` decimal(18,2) DEFAULT '0.00' COMMENT '其他成本(退款差额<0)',
  40 +
  41 + -- 人工工资部分
  42 + `F_SalaryBaseHealthCoach` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-健康师底薪',
  43 + `F_SalaryBaseAssistant` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店助底薪',
  44 + `F_SalaryBaseDirector` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店助主任底薪',
  45 + `F_SalaryBaseStoreManager` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店长底薪',
  46 + `F_SalaryBaseGeneralManager` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-总经理/经理底薪',
  47 +
  48 + `F_SalaryCommissionHealthCoach` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-健康师提成',
  49 + `F_SalaryCommissionAssistant` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店助提成',
  50 + `F_SalaryCommissionDirector` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店助主任提成',
  51 + `F_SalaryCommissionStoreManager` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-店长提成',
  52 + `F_SalaryCommissionGeneralManager` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-总经理/经理提成',
  53 + `F_SalaryManual` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-手工',
  54 + `F_SalaryAttendance` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-出勤(保留)',
  55 + `F_SalaryPhoneCustody` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-岗位-手机保管费',
  56 + `F_SalaryHeadcountReward` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-岗位-人头',
  57 + `F_SalaryTZone` decimal(18,2) DEFAULT '0.00' COMMENT '人工工资-门店T区',
  58 +
  59 + -- 费用与其他
  60 + `F_SocialSecurity` decimal(18,2) DEFAULT '0.00' COMMENT '社保(保留)',
  61 + `F_StoreRent` decimal(18,2) DEFAULT '0.00' COMMENT '门店房租',
  62 + `F_DormRent` decimal(18,2) DEFAULT '0.00' COMMENT '宿舍房租(保留)',
  63 + `F_CurrentPeriodExpense` decimal(18,2) DEFAULT '0.00' COMMENT '当期费用(报销)',
  64 + `F_CurrentPeriodExpenseQin` decimal(18,2) DEFAULT '0.00' COMMENT '当前费用-秦董(保留)',
  65 + `F_FinancialFee` decimal(18,2) DEFAULT '0.00' COMMENT '财务费用(保留)',
  66 +
  67 + -- 奖励
  68 + `F_RewardMedicalBeauty` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-医美(保留)',
  69 + `F_RewardOther` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-其他(保留)',
  70 + `F_RewardLargeOrder` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-大单奖(保留)',
  71 + `F_RewardFirstOrder` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-首单奖(保留)',
  72 + `F_RewardGeneral` decimal(18,2) DEFAULT '0.00' COMMENT '奖励(保留)',
  73 +
  74 + -- 其他保留字段
  75 + `F_Other1` decimal(18,2) DEFAULT '0.00' COMMENT '其他1(保留)',
  76 + `F_Other2` decimal(18,2) DEFAULT '0.00' COMMENT '其他2(保留)',
  77 +
  78 + -- 利润结果
  79 + `F_Profit` decimal(18,2) DEFAULT '0.00' COMMENT '利润(预收-实退-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)',
  80 + `F_FinancialProfit` decimal(18,2) DEFAULT '0.00' COMMENT '财务利润(主营-其他收入-主营成本-人工工资[分项汇总]-房租-费用-奖励-其他)',
  81 +
  82 + -- 基础字段
  83 + `F_CreateTime` datetime DEFAULT NULL COMMENT '创建时间',
  84 + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
  85 + `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人',
  86 + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人',
  87 + `F_IsEffective` int(1) DEFAULT '1' COMMENT '是否有效(1:有效 0:无效)',
  88 +
  89 + PRIMARY KEY (`F_Id`),
  90 + KEY `Idx_StoreId` (`F_StoreId`),
  91 + KEY `Idx_Month` (`F_StatisticsMonth`)
  92 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='门店股份统计表';
  93 +
  94 +
  95 +-- ----------------------------
  96 +-- 2. 科技部股份统计表 (lq_share_statistics_tech_dept)
  97 +-- ----------------------------
  98 +DROP TABLE IF EXISTS `lq_share_statistics_tech_dept`;
  99 +CREATE TABLE `lq_share_statistics_tech_dept` (
  100 + `F_Id` varchar(50) NOT NULL COMMENT '主键ID',
  101 + `F_DepartmentName` varchar(100) DEFAULT NULL COMMENT '部门名称(科技一部/二部)',
  102 + `F_StatisticsMonth` varchar(50) NOT NULL COMMENT '统计月份(YYYYMM)',
  103 +
  104 + -- 收入
  105 + `F_Income` decimal(18,2) DEFAULT '0.00' COMMENT '收入(门店科美开单30%)',
  106 +
  107 + -- 成本
  108 + `F_CostReimbursement` decimal(18,2) DEFAULT '0.00' COMMENT '成本-报销',
  109 + `F_CostTeacherBase` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部老师底薪',
  110 + `F_CostTeacherManual` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部手工费',
  111 + `F_CostTeacherBillingComm` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部开单提成',
  112 + `F_CostTeacherConsumeComm` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部消耗提成',
  113 + `F_CostTeacherExpertComm` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部专家提成(保留)',
  114 + `F_CostTeacherOvertime` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部加班(保留)',
  115 + `F_CostGMBase` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部总经理底薪',
  116 + `F_CostGMComm` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工-科技部总经理提成',
  117 + `F_RewardTechDept` decimal(18,2) DEFAULT '0.00' COMMENT '奖励-科技部(保留)',
  118 +
  119 + -- 其他
  120 + `F_CostOther1` decimal(18,2) DEFAULT '0.00' COMMENT '成本-其他1(保留)',
  121 + `F_CostOther2` decimal(18,2) DEFAULT '0.00' COMMENT '成本-其他2(保留)',
  122 +
  123 + -- 利润结果
  124 + `F_Profit` decimal(18,2) DEFAULT '0.00' COMMENT '科技部利润',
  125 +
  126 + -- 基础字段
  127 + `F_CreateTime` datetime DEFAULT NULL COMMENT '创建时间',
  128 + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
  129 + `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人',
  130 + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人',
  131 + `F_IsEffective` int(1) DEFAULT '1' COMMENT '是否有效(1:有效 0:无效)',
  132 +
  133 + PRIMARY KEY (`F_Id`),
  134 + KEY `Idx_Month` (`F_StatisticsMonth`)
  135 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='科技部股份统计表';
  136 +
  137 +
  138 +-- ----------------------------
  139 +-- 3. 总部股份统计表 (lq_share_statistics_hq)
  140 +-- ----------------------------
  141 +DROP TABLE IF EXISTS `lq_share_statistics_hq`;
  142 +CREATE TABLE `lq_share_statistics_hq` (
  143 + `F_Id` varchar(50) NOT NULL COMMENT '主键ID',
  144 + `F_StatisticsMonth` varchar(50) NOT NULL COMMENT '统计月份(YYYYMM)',
  145 +
  146 + -- 收入
  147 + `F_IncomeGeneral` decimal(18,2) DEFAULT '0.00' COMMENT '收入-全部(开单业绩9%)',
  148 + `F_IncomeTechDept` decimal(18,2) DEFAULT '0.00' COMMENT '收入-科技部(科美30%的9%)',
  149 +
  150 + -- 成本
  151 + `F_CostReimbursement` decimal(18,2) DEFAULT '0.00' COMMENT '成本-报销(总部费用)',
  152 + `F_CostLabor` decimal(18,2) DEFAULT '0.00' COMMENT '成本-人工(保留)',
  153 + `F_CostEducationRent` decimal(18,2) DEFAULT '0.00' COMMENT '成本-教育部房租',
  154 + `F_CostWarehouseRent` decimal(18,2) DEFAULT '0.00' COMMENT '成本-仓库房租',
  155 + `F_CostHQRent` decimal(18,2) DEFAULT '0.00' COMMENT '成本-总部房租',
  156 +
  157 + -- 利润结果
  158 + `F_OperationalProfit` decimal(18,2) DEFAULT '0.00' COMMENT '总部运营利润',
  159 +
  160 + -- 基础字段
  161 + `F_CreateTime` datetime DEFAULT NULL COMMENT '创建时间',
  162 + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
  163 + `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人',
  164 + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人',
  165 + `F_IsEffective` int(1) DEFAULT '1' COMMENT '是否有效(1:有效 0:无效)',
  166 +
  167 + PRIMARY KEY (`F_Id`),
  168 + KEY `Idx_Month` (`F_StatisticsMonth`)
  169 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='总部股份统计表';
... ...
门店股份统计说明.md 0 → 100644
  1 +# 门店股份统计说明 (Store Share Statistics)
  2 +
  3 +## 1. 概述
  4 +本以文档详细说明门店股份统计的各个组成部分,包括主营收入、其他收入、各项成本、人工工资及费用的计算逻辑和数据来源。
  5 +
  6 +---
  7 +
  8 +## 2. 收入 (Income)
  9 +
  10 +### 2.1 主营收入 (Main Income)
  11 +* **说明**: 按照消耗业绩统计(需区分生美、科美、医美、产品等)。
  12 +* **计算逻辑**: 统计当月 `lq_xh_jksyj` (耗卡健康师业绩表) 中的 `jksyj` (健康师业绩)。
  13 +* **数据来源**:
  14 + * 表: `lq_xh_jksyj`
  15 + * 字段: `jksyj` (业绩金额), `F_ItemCategory` (品项分类 - 生美/科美/医美/产品)
  16 + * **注意**: 需关联 `lq_kd_pxmx` (开单品项明细) 或通过 `F_ItemId` 获取更详细属性。
  17 + * **退款扣除**: 主营收入计算时,应为 **总消耗业绩 - 对应品项的消耗退款**。即 `(开单消耗业绩 - 退款消耗业绩)`。需确保“主营收入”是净收入。
  18 +
  19 +### 2.2 其他收入 (Other Income)
  20 +* **说明**: 退款差额 > 0 的部分。
  21 +* **计算逻辑**:
  22 + 1. 计算单笔退款的差额 = `应退金额` - `实退金额`。
  23 + 2. 若差额 > 0,计入其他收入。
  24 +* **数据来源**:
  25 + * 表: `lq_hytk_hytk` (会员退款表)
  26 + * 字段: `tkje` (应退金额/退卡金额), `F_ActualRefundAmount` (实退金额)
  27 + * 计算: `SUM(CASE WHEN (tkje - F_ActualRefundAmount) > 0 THEN (tkje - F_ActualRefundAmount) ELSE 0 END)`
  28 +
  29 +### 2.3 预收款 (Advance Receipt)
  30 +* **说明**: 来源于开单的实收业绩。
  31 +* **数据来源**:
  32 + * 表: `lq_kd_kdjlb` (开单记录表)
  33 + * 字段: `sfyj` (实付业绩)
  34 +
  35 +### 2.4 退款 (Refund)
  36 +* **说明**: 按照退款单里面的实退业绩来计算。
  37 +* **数据来源**:
  38 + * 表: `lq_hytk_hytk`
  39 + * 字段: `F_ActualRefundAmount` (实退金额)
  40 +
  41 +### 2.5 退款差额 (Refund Difference)
  42 +* **说明**: `应退金额` - `实退金额`。
  43 + * `> 0`: 归纳到 **其他收入**
  44 + * `< 0`: 归纳到 **其他成本**
  45 +* **数据来源**: 同 2.2。
  46 +
  47 +### 2.6 应收 (Receivables)
  48 +* **说明**: 开单时选择了合作医院的款项。
  49 +* **计算逻辑**: 统计支付方式或关联机构为“合作医院”的开单金额。
  50 +* **数据来源**:
  51 + * 表: `lq_kd_kdjlb`
  52 + * 字段: `Hgjg` (合作机构) 或 支付方式字段。
  53 + * **判定**: `Hgjg` 不为空 且 属于医院类型。
  54 +
  55 +### 2.7 银行存款 (Bank Deposit)
  56 +* **说明**: 开单时,**除了**选择了合作医院以外的所有款项。
  57 +* **计算逻辑**: 总实付业绩 - 应收。
  58 +
  59 +---
  60 +
  61 +## 3. 成本 (Cost)
  62 +
  63 +### 3.1 主营成本-产品 (product)
  64 +* **说明**: 仓库领取数据,算上个月成本。
  65 +* **数据来源**:
  66 + * 表: `lq_inventory_usage` (库存使用记录)
  67 + * 字段: `F_TotalAmount` (合计金额)
  68 + * **条件**: `F_UsageTime` 在上个月范围内。
  69 +
  70 +### 3.2 主营成本-福田 (futian)
  71 +* **说明**: 仓库地址是“福田”的领取数据,算上个月成本。
  72 +* **数据来源**:
  73 + * 表: `lq_inventory_usage`
  74 + * **待确认**: 需确认“福田”仓库的 `StoreId` 或如何标识。暂时需通过 `StoreId` 或 `ProductId` 对应的仓库属性识别。
  75 +
  76 +### 3.3 主营成本-毛巾 (towel)
  77 +* **说明**: 清洗记录里面的送出。
  78 +* **数据来源**:
  79 + * 表: `lq_laundry_flow` (清洗流水表)
  80 + * 字段: `F_TotalPrice`
  81 + * **条件**: `F_FlowType` = 0 (送出)。
  82 +
  83 +### 3.4 主营成本-科技部 (tech_dept)
  84 +* **说明**: 门店开单中属于科美的(总业绩 - 退款业绩) * 30%。
  85 +* **计算逻辑**:
  86 + 1. 筛选科美项目 (`lq_kd_pxmx.F_BeautyType` 为科美 或 `ItemCategory` 为科美)。
  87 + 2. 计算科美项目的实付业绩总和 - 对应退款。
  88 + 3. 乘以 30%。
  89 +
  90 +### 3.5 主营成本-管理费 (management_fee)
  91 +* **说明**: 门店所有开单(总业绩 - 退款业绩) * 9%。
  92 +* **计算逻辑**: (`lq_kd_kdjlb.sfyj` 总和 - `lq_hytk_hytk.F_ActualRefundAmount` 总和) * 9%。
  93 +
  94 +### 3.6 主营成本-合作 (cooperation)
  95 +* **说明**: 不处理(保留字段)。
  96 +
  97 +### 3.7 主营成本-大项目 (major_project)
  98 +* **说明**: 不处理(保留字段)。
  99 +
  100 +---
  101 +
  102 +## 4. 人工工资 (Labor Cost)
  103 +
  104 +### 4.1 人工工资-底薪 (base_salary)
  105 +* **人工工资-健康师底薪**: 来源于 `lq_salary_statistics.F_ActualBaseSalary`。
  106 +* **人工工资-店助底薪**: 来源于 `lq_assistant_salary_statistics.F_ActualBaseSalary` (岗位为“店助”)。
  107 +* **人工工资-店助主任底薪**: 来源于 `lq_director_salary_statistics.F_ActualBaseSalary` (岗位为“店助主任”)。
  108 +* **人工工资-店长底薪**: 来源于 `lq_store_manager_salary_statistics.F_ActualBaseSalary`。
  109 +* **人工工资-总经理/经理底薪**: 来源于事业部总经理/经理工资表 `F_ActualBaseSalary` (按门店平均分摊 - 底薪固定4000,按管理门店数平均)。
  110 +
  111 +### 4.2 人工工资-提成 (commission)
  112 +* **人工工资-健康师提成**: 来源于 `lq_salary_statistics.F_TotalCommissionAmount`。
  113 +* **人工工资-店助提成**: 来源于 `lq_assistant_salary_statistics.F_TotalCommissionAmount`。
  114 +* **人工工资-店助主任提成**: 来源于 `lq_director_salary_statistics.F_TotalCommissionAmount`。
  115 +* **人工工资-店长提成**: 来源于 `lq_store_manager_salary_statistics.F_TotalCommissionAmount`。
  116 +* **人工工资-总经理/经理提成**: 来源于事业部总经理/经理工资表中该门店对应的提成金额。
  117 + * **注意**: 总经理提成是按门店单独计算汇总的,工资表中需记录分店提成明细或能反查单店提成。
  118 + * 根据规则:`总提成 = SUM(各门店提成金额)`。统计时需提取该门店贡献的部分。
  119 +
  120 +### 4.3 人工工资-手工 (manual_fee)
  121 +* **说明**: 健康师手工费(消耗表)。
  122 +* **数据来源**:
  123 + * 表: `lq_xh_jksyj`
  124 + * 字段: `F_LaborCost` (手工费)
  125 + * **或者**: 直接取工资表中的 `F_HandworkFee` (如果有统计)。推荐使用消耗表累加更精准。
  126 +
  127 +### 4.4 人工工资-岗位-手机保管费 (phone_custody)
  128 +* **说明**: 店助的手机保管费。
  129 +* **数据来源**:
  130 + * 表: `lq_assistant_salary_statistics`
  131 + * 字段: `F_PhoneManagementFee` (手机保管费,已确认字段存在)。
  132 +
  133 +### 4.5 人工工资-岗位-人头 (headcount_reward)
  134 +* **说明**: 店助的人头阶段奖励。
  135 +* **数据来源**:
  136 + * 表: `lq_assistant_salary_statistics`
  137 + * 字段: 需确认具体的奖励字段,可能是 `F_HeadcountAward` 或包含在奖金中。
  138 +
  139 +### 4.6 人工工资-门店T区 (t_zone)
  140 +* **说明**: 健康师工资表里面T区的工资金额。
  141 +* **数据来源**:
  142 + * 表: `lq_salary_statistics`
  143 + * 字段: 筛选 T区员工的 `F_ActualSalary` 或 特定 T区 补贴字段。
  144 +
  145 +---
  146 +
  147 +## 5. 费用与其他 (Expenses & Others)
  148 +
  149 +### 5.1 门店房租 (store_rent)
  150 +* **说明**: 来源于合同对应的每月房租明细。
  151 +* **数据来源**:
  152 + * 表: `lq_contract_rent_detail` (月租明细表)
  153 + * 字段: `F_DueAmount` (应缴金额)
  154 + * 条件: `F_PaymentMonth` 匹配当月。
  155 +
  156 +### 5.2 当期费用 (current_period_expense)
  157 +* **说明**: 报销费用 -> 费用分类 = "当期费用"。
  158 +* **数据来源**:
  159 + * 表: `lq_reimbursement_application` (报销申请) 关联 `lq_reimbursement_category` (报销分类)。
  160 + * 条件: 通过分类表筛选出一级分类或二级分类为“当期费用”的记录。
  161 +
  162 +### 5.3 其他成本 (other_cost)
  163 +* **说明**: 退款差额 < 0 的部分 (即 `(应退 - 实退) < 0`,多退了)。
  164 +* **计算逻辑**: 绝对值(`应退` - `实退`)。
  165 +
  166 +### 5.4 保留/不处理字段
  167 +* 人工工资-出勤
  168 +* 社保
  169 +* 宿舍房租
  170 +* 当前费用-秦董
  171 +* 财务费用
  172 +* 各项奖励 (医美/其他/大单/首单)
  173 +* 其他1, 其他2
  174 +
  175 +---
  176 +
  177 +## 6. 利润计算 (Profit Calculation)
  178 +
  179 +### 6.1 利润 (Profit)
  180 +```
  181 +利润 = 预收款
  182 + - 实际退款
  183 + - 主营成本(产品 + 福田 + 毛巾 + 合作 + 大项目 + 科技部 + 管理费)
  184 + - 人工工资(底薪 + 提成 + 手工 + 出勤 + 岗位 + T区)
  185 + - 社保(公司部分)
  186 + - 门店房租
  187 + - 宿舍房租
  188 + - 当前费用
  189 + - 当前费用(秦董)
  190 + - 财务费用
  191 + - 奖励(各项)
  192 + - 其他1 - 其他2
  193 +```
  194 +
  195 +### 6.2 财务利润 (Financial Profit)
  196 +```
  197 +财务利润 = 主营收入
  198 + - 其他收入
  199 + - 主营成本(...)
  200 + - 人工工资(...)
  201 + - 社保(...)
  202 + - 门店房租
  203 + - ... (同上扣除项)
  204 +```
... ...