using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using PagedList; using Newtonsoft.Json; using System.Collections; using System.Data.Entity; // api/pivot [ezAuthorize] public class pivotController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); #region 報表查詢 API /// /// 報名明細查詢 - 對應 Excel 中的視圖結構 /// GET api/pivot/registration_details /// /// 開始日期 /// 結束日期 /// 法會編號 /// 信眾編號 /// 頁碼 /// 每頁筆數 /// [HttpGet] [Route("api/pivot/registration_details")] public IHttpActionResult GetRegistrationDetails(string startDate = null, string endDate = null, int? activityNum = null, int? followerNum = null, int page = 1, int pageSize = 1000) { try { // 對應 README.md 中的視圖查詢結構,按顏色分類欄位 var query = from pod in _db.pro_order_detail join po in _db.pro_order on pod.order_no equals po.order_no join ai in _db.actItems on pod.actItem_num equals ai.num join act in _db.activities on po.activity_num equals act.num join f in _db.followers on po.f_num equals f.num join aik in _db.actItem_kind on ai.kind equals aik.num select new { // 🟠 橘色欄位 - 法會基礎資料 (法會相關的直接來源) 法會ID = act.num, 法會名稱 = act.subject, 開始日期 = act.startDate_solar, 結束日期 = act.endDate_solar, // 🔵 藍色欄位 - 信眾基礎資料 (信眾相關的直接來源) 信眾編號 = f.f_number, 信眾姓名 = f.u_name, 報名編號 = po.order_no, 報名日期 = po.up_time, // 🟢綠色欄位 - 功德資訊欄位 (功德相關資訊) 功德主 = pod.parent_num != null ? "是" : "否", 功德類型 = aik.kind, 功德名稱 = ai.subject, // 🟣 紫色欄位 - 計算欄位 (需要計算) 數量 = pod.qty, 金額 = pod.price, 已收 = 0, // 業務邏輯:固定為 0 未收 = pod.price * pod.qty - 0, // 計算未收金額 // 額外提供原始資料供進階計算 _metadata = new { parent_num = pod.parent_num, unit_price = pod.price, quantity = pod.qty, total_amount = pod.price * pod.qty, kind_num = aik.num, item_num = ai.num, activity_num = act.num, follower_num = f.num } }; // 日期篩選 if (!string.IsNullOrEmpty(startDate) && DateTime.TryParse(startDate, out DateTime start)) { query = query.Where(x => x.開始日期 >= start); } if (!string.IsNullOrEmpty(endDate) && DateTime.TryParse(endDate, out DateTime end)) { query = query.Where(x => x.結束日期 <= end); } // 法會篩選 if (activityNum.HasValue) { query = query.Where(x => x.法會ID == activityNum.Value); } // 信眾篩選 if (followerNum.HasValue) { query = query.Where(x => x.信眾編號 == followerNum.ToString()); } // 分頁處理 var totalCount = query.Count(); var pagedQuery = query.OrderByDescending(x => x.報名日期) .ThenByDescending(x => x.法會ID) .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); var result = new { success = true, data = new { list = pagedQuery, total = totalCount, page = page, pageSize = pageSize, totalPages = (int)Math.Ceiling((double)totalCount / pageSize) }, message = "查詢成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"查詢失敗:{ex.Message}"); } } /// /// 報名明細查詢 - Excel 匯出格式 /// GET api/pivot/registration_details_export /// /// 開始日期 /// 結束日期 /// 法會編號 /// 信眾編號 /// [HttpGet] [Route("api/pivot/registration_details_export")] public IHttpActionResult GetRegistrationDetailsForExport(string startDate = null, string endDate = null, int? activityNum = null, int? followerNum = null) { try { // 完全對應 Excel 中的欄位結構 var query = from pod in _db.pro_order_detail join po in _db.pro_order on pod.order_no equals po.order_no join ai in _db.actItems on pod.actItem_num equals ai.num join act in _db.activities on po.activity_num equals act.num join f in _db.followers on po.f_num equals f.num join aik in _db.actItem_kind on ai.kind equals aik.num select new { // 完全對應 Excel 欄位名稱 法會ID = act.num, 法會名稱 = act.subject, 開始日期 = act.startDate_solar, 結束日期 = act.endDate_solar, 信眾編號 = f.f_number, 信眾姓名 = f.u_name, 報名編號 = po.order_no, 報名日期 = po.up_time, 功德主 = pod.parent_num != null ? "是" : "否", 功德類型 = aik.kind, 功德名稱 = ai.subject, 數量 = pod.qty, 金額 = pod.price, 已收 = 0, 未收 = pod.price * pod.qty }; // 套用篩選條件 if (!string.IsNullOrEmpty(startDate) && DateTime.TryParse(startDate, out DateTime start)) { query = query.Where(x => x.開始日期 >= start); } if (!string.IsNullOrEmpty(endDate) && DateTime.TryParse(endDate, out DateTime end)) { query = query.Where(x => x.結束日期 <= end); } if (activityNum.HasValue) { query = query.Where(x => x.法會ID == activityNum.Value); } if (followerNum.HasValue) { query = query.Where(x => x.信眾編號 == followerNum.ToString()); } var exportData = query.OrderByDescending(x => x.報名日期) .ThenByDescending(x => x.法會ID) .ToList() .Select(x => new { x.法會ID, x.法會名稱, 開始日期 = x.開始日期.HasValue ? x.開始日期.Value.ToString("yyyy/MM/dd") : "", 結束日期 = x.結束日期.HasValue ? x.結束日期.Value.ToString("yyyy/MM/dd") : "", x.信眾編號, x.信眾姓名, x.報名編號, 報名日期 = x.報名日期.HasValue ? x.報名日期.Value.ToString("yyyy/MM/dd HH:mm") : "", x.功德主, x.功德類型, x.功德名稱, x.數量, x.金額, x.已收, x.未收 }) .ToList(); // 統計資訊 var summary = new { 總筆數 = exportData.Count, 總金額 = exportData.Sum(x => x.金額 * x.數量), 參與法會數 = exportData.Select(x => x.法會ID).Distinct().Count(), 參與信眾數 = exportData.Select(x => x.信眾編號).Distinct().Count(), 功德主數量 = exportData.Count(x => x.功德主 == "是") }; var result = new { success = true, data = new { details = exportData, summary = summary, export_time = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), filters = new { startDate, endDate, activityNum, followerNum } }, message = "匯出資料準備完成" }; return Ok(result); } catch (Exception ex) { return BadRequest($"匯出失敗:{ex.Message}"); } } /// /// 取得法會報名統計 /// GET api/pivot/activity_stats /// /// 開始日期 /// 結束日期 /// 法會編號 /// [HttpGet] [Route("api/pivot/activity_stats")] public IHttpActionResult GetActivityStats(string startDate = null, string endDate = null, int? activityNum = null) { try { var query = from pod in _db.pro_order_detail join po in _db.pro_order on pod.order_no equals po.order_no join act in _db.activities on po.activity_num equals act.num join ai in _db.actItems on pod.actItem_num equals ai.num join aik in _db.actItem_kind on ai.kind equals aik.num join f in _db.followers on po.f_num equals f.num select new { ActivityNum = act.num, ActivityName = act.subject, StartDate = act.startDate_solar, EndDate = act.endDate_solar, FollowerNum = f.num, FollowerName = f.u_name, OrderNo = po.order_no, OrderDate = po.up_time, KindName = aik.kind, ItemName = ai.subject, Qty = pod.qty, Price = pod.price, Amount = pod.qty * pod.price }; // 日期篩選 if (!string.IsNullOrEmpty(startDate) && DateTime.TryParse(startDate, out DateTime start)) { query = query.Where(x => x.StartDate >= start); } if (!string.IsNullOrEmpty(endDate) && DateTime.TryParse(endDate, out DateTime end)) { query = query.Where(x => x.EndDate <= end); } // 法會篩選 if (activityNum.HasValue) { query = query.Where(x => x.ActivityNum == activityNum.Value); } // 統計資料 var stats = query.GroupBy(x => new { x.ActivityNum, x.ActivityName, x.StartDate, x.EndDate }) .Select(g => new { activity_num = g.Key.ActivityNum, activity_name = g.Key.ActivityName, start_date = g.Key.StartDate, end_date = g.Key.EndDate, total_orders = g.Select(x => x.OrderNo).Distinct().Count(), total_followers = g.Select(x => x.FollowerNum).Distinct().Count(), total_amount = g.Sum(x => x.Amount), total_qty = g.Sum(x => x.Qty), avg_amount_per_follower = g.GroupBy(x => x.FollowerNum).Average(fg => fg.Sum(x => x.Amount)) }) .OrderByDescending(x => x.start_date) .ToList(); var result = new { success = true, data = stats, message = "查詢成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"查詢失敗:{ex.Message}"); } } /// /// 取得信眾參與分析 /// GET api/pivot/follower_analysis /// /// 開始日期 /// 結束日期 /// 信眾編號 /// [HttpGet] [Route("api/pivot/follower_analysis")] public IHttpActionResult GetFollowerAnalysis(string startDate = null, string endDate = null, int? followerNum = null) { try { var query = from pod in _db.pro_order_detail join po in _db.pro_order on pod.order_no equals po.order_no join act in _db.activities on po.activity_num equals act.num join ai in _db.actItems on pod.actItem_num equals ai.num join aik in _db.actItem_kind on ai.kind equals aik.num join f in _db.followers on po.f_num equals f.num select new { FollowerNum = f.num, FollowerName = f.u_name, FollowerCode = f.f_number, ActivityNum = act.num, ActivityName = act.subject, StartDate = act.startDate_solar, EndDate = act.endDate_solar, OrderDate = po.up_time, KindName = aik.kind, ItemName = ai.subject, Amount = pod.qty * pod.price, HasParent = pod.parent_num != null }; // 日期篩選 if (!string.IsNullOrEmpty(startDate) && DateTime.TryParse(startDate, out DateTime start)) { query = query.Where(x => x.StartDate >= start); } if (!string.IsNullOrEmpty(endDate) && DateTime.TryParse(endDate, out DateTime end)) { query = query.Where(x => x.EndDate <= end); } // 信眾篩選 if (followerNum.HasValue) { query = query.Where(x => x.FollowerNum == followerNum.Value); } // 信眾統計分析 var followerStats = query.GroupBy(x => new { x.FollowerNum, x.FollowerName, x.FollowerCode }) .Select(g => new { follower_num = g.Key.FollowerNum, follower_name = g.Key.FollowerName, follower_code = g.Key.FollowerCode, total_activities = g.Select(x => x.ActivityNum).Distinct().Count(), total_amount = g.Sum(x => x.Amount), total_orders = g.Select(x => x.OrderDate).Count(), avg_amount_per_activity = g.GroupBy(x => x.ActivityNum).Average(ag => ag.Sum(x => x.Amount)), is_patron_count = g.Count(x => x.HasParent), participation_rate = Math.Round((double)g.Select(x => x.ActivityNum).Distinct().Count() / _db.activities.Count() * 100, 2), favorite_kinds = g.GroupBy(x => x.KindName) .OrderByDescending(kg => kg.Sum(x => x.Amount)) .Take(3) .Select(kg => new { kind = kg.Key, amount = kg.Sum(x => x.Amount) }) .ToList(), recent_activities = g.OrderByDescending(x => x.OrderDate) .Take(5) .Select(x => new { activity_name = x.ActivityName, order_date = x.OrderDate, amount = x.Amount }) .ToList() }) .OrderByDescending(x => x.total_amount) .ToList(); var result = new { success = true, data = followerStats, message = "查詢成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"查詢失敗:{ex.Message}"); } } /// /// 取得收入統計 /// GET api/pivot/income_stats /// /// 開始日期 /// 結束日期 /// 分組方式 (monthly/yearly/activity) /// [HttpGet] [Route("api/pivot/income_stats")] public IHttpActionResult GetIncomeStats(string startDate = null, string endDate = null, string groupBy = "monthly") { try { var query = from pod in _db.pro_order_detail join po in _db.pro_order on pod.order_no equals po.order_no join act in _db.activities on po.activity_num equals act.num join ai in _db.actItems on pod.actItem_num equals ai.num join aik in _db.actItem_kind on ai.kind equals aik.num select new { ActivityNum = act.num, ActivityName = act.subject, StartDate = act.startDate_solar, OrderDate = po.up_time, KindNum = aik.num, KindName = aik.kind, ItemName = ai.subject, Amount = pod.qty * pod.price }; // 日期篩選 if (!string.IsNullOrEmpty(startDate) && DateTime.TryParse(startDate, out DateTime start)) { query = query.Where(x => x.StartDate >= start); } if (!string.IsNullOrEmpty(endDate) && DateTime.TryParse(endDate, out DateTime end)) { query = query.Where(x => x.StartDate <= end); } object stats = null; switch (groupBy?.ToLower()) { case "monthly": stats = query.GroupBy(x => new { Year = x.StartDate.Value.Year, Month = x.StartDate.Value.Month }) .Select(g => new { period = $"{g.Key.Year}/{g.Key.Month:00}", year = g.Key.Year, month = g.Key.Month, total_amount = g.Sum(x => x.Amount), total_activities = g.Select(x => x.ActivityNum).Distinct().Count(), kind_breakdown = g.GroupBy(x => x.KindName) .Select(kg => new { kind = kg.Key, amount = kg.Sum(x => x.Amount) }).ToList() }) .OrderBy(x => x.year).ThenBy(x => x.month) .ToList(); break; case "yearly": stats = query.GroupBy(x => x.StartDate.Value.Year) .Select(g => new { period = g.Key.ToString(), year = g.Key, total_amount = g.Sum(x => x.Amount), total_activities = g.Select(x => x.ActivityNum).Distinct().Count(), kind_breakdown = g.GroupBy(x => x.KindName) .Select(kg => new { kind = kg.Key, amount = kg.Sum(x => x.Amount) }).ToList(), monthly_trend = g.GroupBy(x => x.StartDate.Value.Month) .Select(mg => new { month = mg.Key, amount = mg.Sum(x => x.Amount) }).OrderBy(x => x.month).ToList() }) .OrderBy(x => x.year) .ToList(); break; case "activity": stats = query.GroupBy(x => new { x.ActivityNum, x.ActivityName, x.StartDate }) .Select(g => new { activity_num = g.Key.ActivityNum, activity_name = g.Key.ActivityName, start_date = g.Key.StartDate, total_amount = g.Sum(x => x.Amount) }) .OrderByDescending(x => x.start_date) .ToList() .Select(g => new { g.activity_num, g.activity_name, g.start_date, g.total_amount, kind_breakdown = query.Where(x => x.ActivityNum == g.activity_num) .GroupBy(x => x.KindName) .Select(kg => new { kind = kg.Key, amount = kg.Sum(x => x.Amount), percentage = g.total_amount.HasValue && g.total_amount.Value > 0 ? Math.Round((double)(kg.Sum(x => x.Amount) ?? 0) / (double)g.total_amount.Value * 100, 2) : 0 }) .OrderByDescending(x => x.amount) .ToList() }) .ToList(); break; default: return BadRequest("不支援的分組方式,請使用 monthly, yearly 或 activity"); } var result = new { success = true, data = stats, groupBy = groupBy, message = "查詢成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"查詢失敗:{ex.Message}"); } } #endregion #region 數據分析 API /// /// 樞紐分析 /// POST api/pivot/pivot_analysis /// /// 分析請求參數 /// [HttpPost] [Route("api/pivot/pivot_analysis")] public IHttpActionResult PostPivotAnalysis(dynamic request) { try { // TODO: 實作樞紐分析邏輯 var result = new { success = true, data = new { rows = new List(), columns = new List(), values = new List() }, message = "分析完成" }; return Ok(result); } catch (Exception ex) { return BadRequest($"分析失敗:{ex.Message}"); } } /// /// 趨勢分析 /// GET api/pivot/trend_analysis /// /// 指標名稱 /// 開始日期 /// 結束日期 /// 時間間隔 /// [HttpGet] [Route("api/pivot/trend_analysis")] public IHttpActionResult GetTrendAnalysis(string metric, string startDate = null, string endDate = null, string interval = "monthly") { try { if (string.IsNullOrEmpty(metric)) { return BadRequest("請指定分析指標"); } var query = from pod in _db.pro_order_detail join po in _db.pro_order on pod.order_no equals po.order_no join act in _db.activities on po.activity_num equals act.num join f in _db.followers on po.f_num equals f.num select new { ActivityDate = act.startDate_solar, OrderDate = po.up_time, Amount = pod.qty * pod.price, FollowerNum = f.num, ActivityNum = act.num }; // 日期篩選 if (!string.IsNullOrEmpty(startDate) && DateTime.TryParse(startDate, out DateTime start)) { query = query.Where(x => x.ActivityDate >= start); } if (!string.IsNullOrEmpty(endDate) && DateTime.TryParse(endDate, out DateTime end)) { query = query.Where(x => x.ActivityDate <= end); } object trendData = null; string trendDirection = "stable"; switch (metric?.ToLower()) { case "income": // 收入趨勢 if (interval == "monthly") { var monthlyIncome = query.GroupBy(x => new { Year = x.ActivityDate.Value.Year, Month = x.ActivityDate.Value.Month }) .Select(g => new { period = $"{g.Key.Year}/{g.Key.Month:00}", value = g.Sum(x => x.Amount), date = new DateTime(g.Key.Year, g.Key.Month, 1) }) .OrderBy(x => x.date) .ToList(); // 計算趨勢方向 if (monthlyIncome.Count >= 2) { var firstHalf = monthlyIncome.Take(monthlyIncome.Count / 2).Average(x => (double)x.value); var secondHalf = monthlyIncome.Skip(monthlyIncome.Count / 2).Average(x => (double)x.value); trendDirection = secondHalf > firstHalf * 1.05 ? "up" : (secondHalf < firstHalf * 0.95 ? "down" : "stable"); } trendData = new { labels = monthlyIncome.Select(x => x.period).ToList(), values = monthlyIncome.Select(x => x.value).ToList(), growth_rates = monthlyIncome.Select((x, i) => i == 0 ? 0.0 : Math.Round((double)(x.value - monthlyIncome[i-1].value) / (double)monthlyIncome[i-1].value * 100, 2)) .ToList() }; } break; case "followers": // 參與信眾數趨勢 if (interval == "monthly") { var monthlyFollowers = query.GroupBy(x => new { Year = x.ActivityDate.Value.Year, Month = x.ActivityDate.Value.Month }) .Select(g => new { period = $"{g.Key.Year}/{g.Key.Month:00}", value = g.Select(x => x.FollowerNum).Distinct().Count(), date = new DateTime(g.Key.Year, g.Key.Month, 1) }) .OrderBy(x => x.date) .ToList(); // 計算趨勢方向 if (monthlyFollowers.Count >= 2) { var firstHalf = monthlyFollowers.Take(monthlyFollowers.Count / 2).Average(x => (double)x.value); var secondHalf = monthlyFollowers.Skip(monthlyFollowers.Count / 2).Average(x => (double)x.value); trendDirection = secondHalf > firstHalf * 1.05 ? "up" : (secondHalf < firstHalf * 0.95 ? "down" : "stable"); } trendData = new { labels = monthlyFollowers.Select(x => x.period).ToList(), values = monthlyFollowers.Select(x => x.value).ToList(), growth_rates = monthlyFollowers.Select((x, i) => i == 0 ? 0.0 : Math.Round((double)(x.value - monthlyFollowers[i-1].value) / (double)monthlyFollowers[i-1].value * 100, 2)) .ToList() }; } break; case "activities": // 法會數量趨勢 var monthlyActivities = query.GroupBy(x => new { Year = x.ActivityDate.Value.Year, Month = x.ActivityDate.Value.Month }) .Select(g => new { period = $"{g.Key.Year}/{g.Key.Month:00}", value = g.Select(x => x.ActivityNum).Distinct().Count(), date = new DateTime(g.Key.Year, g.Key.Month, 1) }) .OrderBy(x => x.date) .ToList(); trendData = new { labels = monthlyActivities.Select(x => x.period).ToList(), values = monthlyActivities.Select(x => x.value).ToList() }; break; default: return BadRequest("不支援的指標,請使用 income, followers 或 activities"); } var result = new { success = true, data = new { metric = metric, interval = interval, trend = trendDirection, chart_data = trendData }, message = "分析完成" }; return Ok(result); } catch (Exception ex) { return BadRequest($"分析失敗:{ex.Message}"); } } /// /// 對比分析 /// GET api/pivot/comparative_analysis /// /// 對比類型 /// 期間1 /// 期間2 /// [HttpGet] [Route("api/pivot/comparative_analysis")] public IHttpActionResult GetComparativeAnalysis(string compareType, string period1, string period2) { try { // TODO: 實作對比分析邏輯 var result = new { success = true, data = new { period1_data = new List(), period2_data = new List(), comparison = new { growth_rate = 0.0, difference = 0.0 } }, message = "對比完成" }; return Ok(result); } catch (Exception ex) { return BadRequest($"對比失敗:{ex.Message}"); } } #endregion #region 報表管理 API /// /// 取得自訂報表清單 /// GET api/pivot/custom_reports /// /// 頁碼 /// 每頁筆數 /// [HttpGet] [Route("api/pivot/custom_reports")] public IHttpActionResult GetCustomReports(int page = 1, int pageSize = 10) { try { // TODO: 實作自訂報表查詢邏輯 var result = new { success = true, data = new { list = new List(), total = 0, page = page, pageSize = pageSize }, message = "查詢成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"查詢失敗:{ex.Message}"); } } /// /// 建立自訂報表 /// POST api/pivot/custom_reports /// /// 報表設定 /// [HttpPost] [Route("api/pivot/custom_reports")] public IHttpActionResult PostCustomReport(dynamic request) { try { // TODO: 實作自訂報表建立邏輯 var result = new { success = true, data = new { id = 1 }, message = "報表建立成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"建立失敗:{ex.Message}"); } } /// /// 匯出報表 /// GET api/pivot/export/{reportId} /// /// 報表編號 /// 匯出格式 (excel/pdf/csv) /// [HttpGet] [Route("api/pivot/export/{reportId}")] public IHttpActionResult GetReportExport(int reportId, string format = "excel") { try { // TODO: 實作報表匯出邏輯 var result = new { success = true, data = new { download_url = $"/admin/pivot/downloads/report_{reportId}.{format}", file_name = $"report_{reportId}_{DateTime.Now:yyyyMMdd}.{format}" }, message = "匯出成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"匯出失敗:{ex.Message}"); } } #endregion #region Excel 數據連接 API /// /// Excel 數據連接專用 - 簡化格式 /// GET api/pivot/excel_data /// /// 回傳格式 (json/csv) /// 限制筆數 /// [HttpGet] [Route("api/pivot/excel_data")] [AllowAnonymous] // 允許 Excel 直接呼叫 public IHttpActionResult GetExcelData(string format = "json", int limit = 5000) { try { // 直接查詢,對應視圖結構 var query = from pod in _db.pro_order_detail join po in _db.pro_order on pod.order_no equals po.order_no join ai in _db.actItems on pod.actItem_num equals ai.num join act in _db.activities on po.activity_num equals act.num join f in _db.followers on po.f_num equals f.num join aik in _db.actItem_kind on ai.kind equals aik.num orderby po.up_time descending, act.num descending select new { ActivityId = act.num, ActivityName = act.subject, StartDate = act.startDate_solar, EndDate = act.endDate_solar, FollowerCode = f.f_number, FollowerName = f.u_name, OrderNo = po.order_no, OrderDate = po.up_time, IsPatron = pod.parent_num != null ? "是" : "否", KindName = aik.kind, ItemName = ai.subject, Quantity = pod.qty, Price = pod.price, Received = 0, Outstanding = pod.price * pod.qty }; var data = query.Take(limit).ToList(); if (format?.ToLower() == "csv") { // CSV 格式回傳 var csv = "法會ID,法會名稱,開始日期,結束日期,信眾編號,信眾姓名,報名編號,報名日期,功德主,功德類型,功德名稱,數量,金額,已收,未收\n"; foreach (var item in data) { csv += $"{item.ActivityId},{item.ActivityName},{item.StartDate:yyyy/MM/dd},{item.EndDate:yyyy/MM/dd}," + $"{item.FollowerCode},{item.FollowerName},{item.OrderNo},{item.OrderDate:yyyy/MM/dd HH:mm}," + $"{item.IsPatron},{item.KindName},{item.ItemName},{item.Quantity},{item.Price},{item.Received},{item.Outstanding}\n"; } return Ok(csv); } // JSON 格式回傳 (預設) var result = new { success = true, data = data, count = data.Count, generated_at = DateTime.Now, message = "資料查詢成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"資料查詢失敗:{ex.Message}"); } } /// /// Excel 數據連接專用 - 按欄位顏色分類格式 /// GET api/pivot/excel_data_structured /// /// 是否包含元數據 /// 限制筆數 /// [HttpGet] [Route("api/pivot/excel_data_structured")] [AllowAnonymous] public IHttpActionResult GetExcelDataStructured(bool includeMetadata = false, int limit = 5000) { try { var query = from pod in _db.pro_order_detail join po in _db.pro_order on pod.order_no equals po.order_no join ai in _db.actItems on pod.actItem_num equals ai.num join act in _db.activities on po.activity_num equals act.num join f in _db.followers on po.f_num equals f.num join aik in _db.actItem_kind on ai.kind equals aik.num orderby po.up_time descending, act.num descending select new { // 🟠 橘色欄位 - 法會基礎資料 activity_fields = new { 法會ID = act.num, 法會名稱 = act.subject, 開始日期 = act.startDate_solar, 結束日期 = act.endDate_solar }, // 🔵 藍色欄位 - 信眾基礎資料 follower_fields = new { 信眾編號 = f.f_number, 信眾姓名 = f.u_name, 報名編號 = po.order_no, 報名日期 = po.up_time }, // 🟢 綠色欄位 - 功德資訊欄位 merit_info_fields = new { 功德主 = pod.parent_num != null ? "是" : "否", 功德類型 = aik.kind, 功德名稱 = ai.subject }, // 🟣 紫色欄位 - 計算欄位 calculated_fields = new { 數量 = pod.qty, 金額 = pod.price, 已收 = 0, 未收 = pod.price * pod.qty, 小計 = pod.price * pod.qty }, // 📊 元數據 (可選) metadata = includeMetadata ? new { 原始資料 = new { parent_num = pod.parent_num, kind_num = aik.num, item_num = ai.num, activity_num = act.num, follower_num = f.num }, 欄位分類說明 = new { 橘色欄位_法會資料 = "法會相關的基礎資料,用於法會維度分析", 藍色欄位_信眾資料 = "信眾相關的基礎資料,用於信眾維度分析", 綠色欄位_功德資訊 = "功德相關的資訊欄位,包含功德主、功德類型和名稱", 紫色欄位_計算欄位 = "需要數學計算或統計的欄位" } } : null }; var data = query.Take(limit).ToList(); var result = new { success = true, data = new { records = data, field_categories = new { 橘色_法會基礎資料 = new[] { "法會ID", "法會名稱", "開始日期", "結束日期" }, 藍色_信眾基礎資料 = new[] { "信眾編號", "信眾姓名", "報名編號", "報名日期" }, 綠色_功德資訊欄位 = new[] { "功德主", "功德類型", "功德名稱" }, 紫色_計算欄位 = new[] { "數量", "金額", "已收", "未收" } }, usage_notes = new { 橘色欄位說明 = "法會相關基礎資料,適合用於法會維度的篩選和分組", 藍色欄位說明 = "信眾相關基礎資料,適合用於信眾維度的篩選和分組", 綠色欄位說明 = "功德資訊欄位,包含功德主判斷、功德類型和名稱,適合用於功德維度的篩選和分組", 紫色欄位說明 = "計算統計欄位,適合用於樞紐分析的值區域進行加總", Excel樞紐分析建議 = new { 篩選器 = "橘色(法會名稱) + 藍色(報名日期) + 綠色(功德類型)", 列標籤 = "藍色(信眾姓名) + 綠色(功德主)", 欄標籤 = "橘色(法會名稱) 或時間維度", 值區域 = "紫色(金額加總、數量計數)" } } }, count = data.Count, generated_at = DateTime.Now, message = "結構化資料查詢成功" }; return Ok(result); } catch (Exception ex) { return BadRequest($"查詢失敗:{ex.Message}"); } } #endregion #region 工具方法 /// /// 釋放資源 /// /// protected override void Dispose(bool disposing) { if (disposing) { _db?.Dispose(); } base.Dispose(disposing); } #endregion }