Commit 7b15acce33409eba8e92f3fb41f1595387daa710
1 parent
7718b4dc
feat: 添加拓客活动漏斗统计和门店顾客详情接口
- 新增GetFunnelStatistics接口:获取按门店分组的漏斗统计数据 - 新增GetOverallFunnelStatistics接口:获取总体漏斗统计数据 - 新增GetStoreCustomerDetails接口:获取门店顾客详情(不分页) - 新增GetStoreCustomerDetailsPaged接口:获取门店顾客详情(分页) - 修正SQL字段名:将yy.F_AppointmentTime改为yy.yysj - 支持拓客数量、预约人数、耗卡人数、耗卡金额统计 - 支持预约转化率和耗卡转化率计算 - 支持按拓客时间倒序排列
Showing
1 changed file
with
239 additions
and
2 deletions
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
| ... | ... | @@ -169,6 +169,8 @@ namespace NCC.Extend.LqTkjlb |
| 169 | 169 | { |
| 170 | 170 | throw NCCException.Oh("未找到对应的拓客活动用户信息,请确认活动ID和用户ID是否正确"); |
| 171 | 171 | } |
| 172 | + var MemberNumber = "LQ" + DateTime.Now.ToString("yyyyMMddHHmmssfff"); | |
| 173 | + var MemberId = YitIdHelper.NextId().ToString(); | |
| 172 | 174 | var eventUserInfo = eventUserInfoList.First(); |
| 173 | 175 | // 创建拓客记录 |
| 174 | 176 | var entity = input.Adapt<LqTkjlbEntity>(); |
| ... | ... | @@ -177,16 +179,17 @@ namespace NCC.Extend.LqTkjlb |
| 177 | 179 | entity.StoreId = eventUserInfo.StoreId; |
| 178 | 180 | entity.DepId = eventUserInfo.DepId; |
| 179 | 181 | entity.ExpansionTime = DateTime.Now; |
| 182 | + entity.MemberId = MemberId; | |
| 180 | 183 | var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); |
| 181 | 184 | if (!(isOk > 0)) |
| 182 | 185 | throw NCCException.Oh("创建拓客记录失败"); |
| 183 | 186 | // 创建客户信息 |
| 184 | 187 | LqKhxxEntity MemberInfo = new LqKhxxEntity(); |
| 185 | - MemberInfo.Id = YitIdHelper.NextId().ToString(); | |
| 188 | + MemberInfo.Id = MemberId; | |
| 186 | 189 | MemberInfo.Khmc = entity.CustomerName; |
| 187 | 190 | MemberInfo.Sjh = input.customerPhone; // 设置手机号 |
| 188 | 191 | MemberInfo.Khlx = MemberTypeEnum.线索.GetHashCode().ToString(); |
| 189 | - MemberInfo.Dah = "LQ" + DateTime.Now.ToString("yyyyMMddHHmmssfff"); | |
| 192 | + MemberInfo.Dah = MemberNumber; | |
| 190 | 193 | MemberInfo.Jdqd = "19.9卡"; |
| 191 | 194 | |
| 192 | 195 | //找到input.expansionUserId的用户信息 |
| ... | ... | @@ -526,5 +529,239 @@ namespace NCC.Extend.LqTkjlb |
| 526 | 529 | } |
| 527 | 530 | #endregion |
| 528 | 531 | |
| 532 | + #region 漏斗统计 | |
| 533 | + /// <summary> | |
| 534 | + /// 获取拓客活动漏斗统计数据 | |
| 535 | + /// </summary> | |
| 536 | + /// <param name="eventId">活动ID</param> | |
| 537 | + /// <returns>漏斗统计数据</returns> | |
| 538 | + [HttpGet("GetFunnelStatistics/{eventId}")] | |
| 539 | + public async Task<dynamic> GetFunnelStatistics(string eventId) | |
| 540 | + { | |
| 541 | + try | |
| 542 | + { | |
| 543 | + var sql = @" | |
| 544 | + SELECT | |
| 545 | + md.F_Id as store_id, | |
| 546 | + md.dm as store_name, | |
| 547 | + COUNT(DISTINCT tk.F_Id) as tk_count, -- 拓客数量 | |
| 548 | + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as yy_count, -- 预约人数(已确认) | |
| 549 | + COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as hk_count, -- 耗卡人数 | |
| 550 | + COALESCE(SUM(CASE WHEN xh.F_Id IS NOT NULL THEN xh.xfje END), 0) as hk_amount, -- 耗卡金额 | |
| 551 | + ROUND( | |
| 552 | + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 / | |
| 553 | + COUNT(DISTINCT tk.F_Id), 2 | |
| 554 | + ) as yy_conversion_rate, -- 预约转化率(预约人数/拓客数量) | |
| 555 | + ROUND( | |
| 556 | + COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 / | |
| 557 | + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END), 2 | |
| 558 | + ) as hk_conversion_rate -- 耗卡转化率(耗卡人数/预约人数) | |
| 559 | + FROM lq_tkjlb tk | |
| 560 | + JOIN lq_mdxx md ON tk.F_StoreId = md.F_Id | |
| 561 | + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk | |
| 562 | + AND yy.F_Status = '已确认' | |
| 563 | + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy | |
| 564 | + AND xh.F_IsEffective = 1 | |
| 565 | + WHERE tk.F_EventId = @eventId | |
| 566 | + GROUP BY md.F_Id, md.dm | |
| 567 | + ORDER BY tk_count DESC"; | |
| 568 | + | |
| 569 | + var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId }); | |
| 570 | + | |
| 571 | + return new | |
| 572 | + { | |
| 573 | + success = true, | |
| 574 | + data = result, | |
| 575 | + message = "获取漏斗统计数据成功" | |
| 576 | + }; | |
| 577 | + } | |
| 578 | + catch (Exception ex) | |
| 579 | + { | |
| 580 | + throw NCCException.Oh("获取漏斗统计数据失败:" + ex.Message); | |
| 581 | + } | |
| 582 | + } | |
| 583 | + | |
| 584 | + /// <summary> | |
| 585 | + /// 获取拓客活动总体漏斗统计 | |
| 586 | + /// </summary> | |
| 587 | + /// <param name="eventId">活动ID</param> | |
| 588 | + /// <returns>总体漏斗统计数据</returns> | |
| 589 | + [HttpGet("GetOverallFunnelStatistics/{eventId}")] | |
| 590 | + public async Task<dynamic> GetOverallFunnelStatistics(string eventId) | |
| 591 | + { | |
| 592 | + try | |
| 593 | + { | |
| 594 | + var sql = @" | |
| 595 | + SELECT | |
| 596 | + COUNT(DISTINCT tk.F_Id) as total_tk_count, -- 总拓客数量 | |
| 597 | + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as total_yy_count, -- 总预约人数 | |
| 598 | + COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as total_hk_count, -- 总耗卡人数 | |
| 599 | + COALESCE(SUM(CASE WHEN xh.F_Id IS NOT NULL THEN xh.xfje END), 0) as total_hk_amount, -- 总耗卡金额 | |
| 600 | + ROUND( | |
| 601 | + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 / | |
| 602 | + COUNT(DISTINCT tk.F_Id), 2 | |
| 603 | + ) as overall_yy_conversion_rate, -- 总体预约转化率 | |
| 604 | + ROUND( | |
| 605 | + COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 / | |
| 606 | + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END), 2 | |
| 607 | + ) as overall_hk_conversion_rate -- 总体耗卡转化率 | |
| 608 | + FROM lq_tkjlb tk | |
| 609 | + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk | |
| 610 | + AND yy.F_Status = '已确认' | |
| 611 | + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy | |
| 612 | + AND xh.F_IsEffective = 1 | |
| 613 | + WHERE tk.F_EventId = @eventId"; | |
| 614 | + | |
| 615 | + var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId }); | |
| 616 | + | |
| 617 | + return new | |
| 618 | + { | |
| 619 | + success = true, | |
| 620 | + data = result?.FirstOrDefault(), | |
| 621 | + message = "获取总体漏斗统计数据成功" | |
| 622 | + }; | |
| 623 | + } | |
| 624 | + catch (Exception ex) | |
| 625 | + { | |
| 626 | + throw NCCException.Oh("获取总体漏斗统计数据失败:" + ex.Message); | |
| 627 | + } | |
| 628 | + } | |
| 629 | + #endregion | |
| 630 | + | |
| 631 | + #region 门店顾客详情 | |
| 632 | + /// <summary> | |
| 633 | + /// 获取门店拓客活动顾客详情 | |
| 634 | + /// </summary> | |
| 635 | + /// <param name="eventId">活动ID</param> | |
| 636 | + /// <param name="storeId">门店ID</param> | |
| 637 | + /// <returns>门店顾客详情列表</returns> | |
| 638 | + [HttpGet("GetStoreCustomerDetails/{eventId}/{storeId}")] | |
| 639 | + public async Task<dynamic> GetStoreCustomerDetails(string eventId, string storeId) | |
| 640 | + { | |
| 641 | + try | |
| 642 | + { | |
| 643 | + var sql = @" | |
| 644 | + SELECT | |
| 645 | + tk.F_Id as tk_id, -- 拓客记录ID | |
| 646 | + tk.F_CustomerPhone as customer_phone, -- 顾客手机号 | |
| 647 | + tk.F_MemberId as member_id, -- 会员ID | |
| 648 | + tk.F_CustomerName as customer_name, -- 顾客姓名 | |
| 649 | + tk.F_CreateTime as tk_time, -- 拓客时间 | |
| 650 | + yy.F_Id as yy_id, -- 预约ID | |
| 651 | + yy.F_Status as yy_status, -- 预约状态 | |
| 652 | + yy.F_CreateTime as yy_time, -- 预约时间 | |
| 653 | + yy.yysj as appointment_time, -- 预约到店时间 | |
| 654 | + xh.F_Id as xh_id, -- 耗卡ID | |
| 655 | + xh.F_CreateTime as xh_time, -- 耗卡时间 | |
| 656 | + xh.xfje as consume_amount, -- 耗卡金额 | |
| 657 | + CASE | |
| 658 | + WHEN yy.F_Id IS NOT NULL THEN '已预约' | |
| 659 | + ELSE '未预约' | |
| 660 | + END as appointment_status, -- 预约状态描述 | |
| 661 | + CASE | |
| 662 | + WHEN xh.F_Id IS NOT NULL THEN '已耗卡' | |
| 663 | + ELSE '未耗卡' | |
| 664 | + END as consume_status -- 耗卡状态描述 | |
| 665 | + FROM lq_tkjlb tk | |
| 666 | + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk | |
| 667 | + AND yy.F_Status = '已确认' | |
| 668 | + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy | |
| 669 | + AND xh.F_IsEffective = 1 | |
| 670 | + WHERE tk.F_EventId = @eventId | |
| 671 | + AND tk.F_StoreId = @storeId | |
| 672 | + ORDER BY tk.F_CreateTime DESC"; | |
| 673 | + | |
| 674 | + var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId, storeId }); | |
| 675 | + | |
| 676 | + return new | |
| 677 | + { | |
| 678 | + success = true, | |
| 679 | + data = result, | |
| 680 | + message = "获取门店顾客详情成功" | |
| 681 | + }; | |
| 682 | + } | |
| 683 | + catch (Exception ex) | |
| 684 | + { | |
| 685 | + throw NCCException.Oh("获取门店顾客详情失败:" + ex.Message); | |
| 686 | + } | |
| 687 | + } | |
| 688 | + | |
| 689 | + /// <summary> | |
| 690 | + /// 获取门店拓客活动顾客详情(分页) | |
| 691 | + /// </summary> | |
| 692 | + /// <param name="eventId">活动ID</param> | |
| 693 | + /// <param name="storeId">门店ID</param> | |
| 694 | + /// <param name="pageIndex">页码</param> | |
| 695 | + /// <param name="pageSize">页大小</param> | |
| 696 | + /// <returns>分页的门店顾客详情列表</returns> | |
| 697 | + [HttpGet("GetStoreCustomerDetailsPaged/{eventId}/{storeId}")] | |
| 698 | + public async Task<dynamic> GetStoreCustomerDetailsPaged(string eventId, string storeId, int pageIndex = 1, int pageSize = 20) | |
| 699 | + { | |
| 700 | + try | |
| 701 | + { | |
| 702 | + var sql = @" | |
| 703 | + SELECT | |
| 704 | + tk.F_Id as tk_id, -- 拓客记录ID | |
| 705 | + tk.F_CustomerPhone as customer_phone, -- 顾客手机号 | |
| 706 | + tk.F_MemberId as member_id, -- 会员ID | |
| 707 | + tk.F_CustomerName as customer_name, -- 顾客姓名 | |
| 708 | + tk.F_CreateTime as tk_time, -- 拓客时间 | |
| 709 | + yy.F_Id as yy_id, -- 预约ID | |
| 710 | + yy.F_Status as yy_status, -- 预约状态 | |
| 711 | + yy.F_CreateTime as yy_time, -- 预约时间 | |
| 712 | + yy.yysj as appointment_time, -- 预约到店时间 | |
| 713 | + xh.F_Id as xh_id, -- 耗卡ID | |
| 714 | + xh.F_CreateTime as xh_time, -- 耗卡时间 | |
| 715 | + xh.xfje as consume_amount, -- 耗卡金额 | |
| 716 | + CASE | |
| 717 | + WHEN yy.F_Id IS NOT NULL THEN '已预约' | |
| 718 | + ELSE '未预约' | |
| 719 | + END as appointment_status, -- 预约状态描述 | |
| 720 | + CASE | |
| 721 | + WHEN xh.F_Id IS NOT NULL THEN '已耗卡' | |
| 722 | + ELSE '未耗卡' | |
| 723 | + END as consume_status -- 耗卡状态描述 | |
| 724 | + FROM lq_tkjlb tk | |
| 725 | + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk | |
| 726 | + AND yy.F_Status = '已确认' | |
| 727 | + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy | |
| 728 | + AND xh.F_IsEffective = 1 | |
| 729 | + WHERE tk.F_EventId = @eventId | |
| 730 | + AND tk.F_StoreId = @storeId | |
| 731 | + ORDER BY tk.F_CreateTime DESC | |
| 732 | + LIMIT @offset, @pageSize"; | |
| 733 | + | |
| 734 | + var countSql = @" | |
| 735 | + SELECT COUNT(*) as total | |
| 736 | + FROM lq_tkjlb tk | |
| 737 | + WHERE tk.F_EventId = @eventId | |
| 738 | + AND tk.F_StoreId = @storeId"; | |
| 739 | + | |
| 740 | + var offset = (pageIndex - 1) * pageSize; | |
| 741 | + var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId, storeId, offset, pageSize }); | |
| 742 | + var totalResult = await _db.Ado.SqlQueryAsync<dynamic>(countSql, new { eventId, storeId }); | |
| 743 | + var total = totalResult?.FirstOrDefault()?.total ?? 0; | |
| 744 | + | |
| 745 | + return new | |
| 746 | + { | |
| 747 | + success = true, | |
| 748 | + data = new | |
| 749 | + { | |
| 750 | + list = result, | |
| 751 | + total = total, | |
| 752 | + pageIndex = pageIndex, | |
| 753 | + pageSize = pageSize, | |
| 754 | + totalPages = (int)Math.Ceiling((double)total / pageSize) | |
| 755 | + }, | |
| 756 | + message = "获取门店顾客详情成功" | |
| 757 | + }; | |
| 758 | + } | |
| 759 | + catch (Exception ex) | |
| 760 | + { | |
| 761 | + throw NCCException.Oh("获取门店顾客详情失败:" + ex.Message); | |
| 762 | + } | |
| 763 | + } | |
| 764 | + #endregion | |
| 765 | + | |
| 529 | 766 | } |
| 530 | 767 | } | ... | ... |