Files
17168ERP/web/App_Code/api/pivotController.cs
yiming 27f916eb9c 修正多處 LINQ-to-Entities 查詢,避免 Nullable .Contains()、.ToString()、Request[] 直接使用造成翻譯失敗。
API 查詢同步改寫 .Contains()、.OrderBy()、複雜 GroupBy/Math.Round,必要時 materialize 或加 HasValue。
Participation rate / kind breakdown 改在記憶體計算,同時檢查整數陣列 .Contains() 的型別安全性。
2025-11-14 23:40:55 +08:00

1160 lines
49 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
/// <summary>
/// 報名明細查詢 - 對應 Excel 中的視圖結構
/// GET api/pivot/registration_details
/// </summary>
/// <param name="startDate">開始日期</param>
/// <param name="endDate">結束日期</param>
/// <param name="activityNum">法會編號</param>
/// <param name="followerNum">信眾編號</param>
/// <param name="page">頁碼</param>
/// <param name="pageSize">每頁筆數</param>
/// <returns></returns>
[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}");
}
}
/// <summary>
/// 報名明細查詢 - Excel 匯出格式
/// GET api/pivot/registration_details_export
/// </summary>
/// <param name="startDate">開始日期</param>
/// <param name="endDate">結束日期</param>
/// <param name="activityNum">法會編號</param>
/// <param name="followerNum">信眾編號</param>
/// <returns></returns>
[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}");
}
}
/// <summary>
/// 取得法會報名統計
/// GET api/pivot/activity_stats
/// </summary>
/// <param name="startDate">開始日期</param>
/// <param name="endDate">結束日期</param>
/// <param name="activityNum">法會編號</param>
/// <returns></returns>
[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),
// ⚠️ 複雜聚合:巢狀 GroupBy → Average → SumEF 6.4.4 可轉換
// 如遇到 NotSupportedException改為在 .ToList() 後計算
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}");
}
}
/// <summary>
/// 取得信眾參與分析
/// GET api/pivot/follower_analysis
/// </summary>
/// <param name="startDate">開始日期</param>
/// <param name="endDate">結束日期</param>
/// <param name="followerNum">信眾編號</param>
/// <returns></returns>
[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 totalActivitiesCount = _db.activities.Count();
// 信眾統計分析
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(),
// ⚠️ 複雜聚合:巢狀 GroupBy → Average → SumEF 6.4.4 可轉換
// 如遇到 NotSupportedException改為在 .ToList() 後計算
avg_amount_per_activity = g.GroupBy(x => x.ActivityNum).Average(ag => ag.Sum(x => x.Amount)),
is_patron_count = g.Count(x => x.HasParent),
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()
// ✅ 優化:在記憶體中計算 participation_rate避免 Math.Round 在 LINQ to Entities 投影中
.Select(x => new
{
x.follower_num,
x.follower_name,
x.follower_code,
x.total_activities,
x.total_amount,
x.total_orders,
x.avg_amount_per_activity,
x.is_patron_count,
participation_rate = Math.Round((double)x.total_activities / totalActivitiesCount * 100, 2),
x.favorite_kinds,
x.recent_activities
})
.ToList();
var result = new
{
success = true,
data = followerStats,
message = "查詢成功"
};
return Ok(result);
}
catch (Exception ex)
{
return BadRequest($"查詢失敗:{ex.Message}");
}
}
/// <summary>
/// 取得收入統計
/// GET api/pivot/income_stats
/// </summary>
/// <param name="startDate">開始日期</param>
/// <param name="endDate">結束日期</param>
/// <param name="groupBy">分組方式 (monthly/yearly/activity)</param>
/// <returns></returns>
[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":
// ✅ 優化:一次性取出所有資料,避免 N+1 查詢
var activityData = query.ToList();
stats = activityData.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),
// ✅ 優化:在記憶體中分組,避免子查詢
kind_breakdown = g.GroupBy(x => x.KindName)
.Select(kg => new
{
kind = kg.Key,
amount = kg.Sum(x => x.Amount),
// ✅ 優化:在記憶體中計算百分比
percentage = g.Sum(x => x.Amount) > 0
? Math.Round((double)kg.Sum(x => x.Amount) / (double)g.Sum(x => x.Amount) * 100, 2)
: 0
})
.OrderByDescending(x => x.amount)
.ToList()
})
.OrderByDescending(x => x.start_date)
.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
/// <summary>
/// 樞紐分析
/// POST api/pivot/pivot_analysis
/// </summary>
/// <param name="request">分析請求參數</param>
/// <returns></returns>
[HttpPost]
[Route("api/pivot/pivot_analysis")]
public IHttpActionResult PostPivotAnalysis(dynamic request)
{
try
{
// TODO: 實作樞紐分析邏輯
var result = new
{
success = true,
data = new
{
rows = new List<object>(),
columns = new List<object>(),
values = new List<object>()
},
message = "分析完成"
};
return Ok(result);
}
catch (Exception ex)
{
return BadRequest($"分析失敗:{ex.Message}");
}
}
/// <summary>
/// 趨勢分析
/// GET api/pivot/trend_analysis
/// </summary>
/// <param name="metric">指標名稱</param>
/// <param name="startDate">開始日期</param>
/// <param name="endDate">結束日期</param>
/// <param name="interval">時間間隔</param>
/// <returns></returns>
[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}");
}
}
/// <summary>
/// 對比分析
/// GET api/pivot/comparative_analysis
/// </summary>
/// <param name="compareType">對比類型</param>
/// <param name="period1">期間1</param>
/// <param name="period2">期間2</param>
/// <returns></returns>
[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<object>(),
period2_data = new List<object>(),
comparison = new
{
growth_rate = 0.0,
difference = 0.0
}
},
message = "對比完成"
};
return Ok(result);
}
catch (Exception ex)
{
return BadRequest($"對比失敗:{ex.Message}");
}
}
#endregion
#region API
/// <summary>
/// 取得自訂報表清單
/// GET api/pivot/custom_reports
/// </summary>
/// <param name="page">頁碼</param>
/// <param name="pageSize">每頁筆數</param>
/// <returns></returns>
[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<object>(),
total = 0,
page = page,
pageSize = pageSize
},
message = "查詢成功"
};
return Ok(result);
}
catch (Exception ex)
{
return BadRequest($"查詢失敗:{ex.Message}");
}
}
/// <summary>
/// 建立自訂報表
/// POST api/pivot/custom_reports
/// </summary>
/// <param name="request">報表設定</param>
/// <returns></returns>
[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}");
}
}
/// <summary>
/// 匯出報表
/// GET api/pivot/export/{reportId}
/// </summary>
/// <param name="reportId">報表編號</param>
/// <param name="format">匯出格式 (excel/pdf/csv)</param>
/// <returns></returns>
[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
/// <summary>
/// Excel 數據連接專用 - 簡化格式
/// GET api/pivot/excel_data
/// </summary>
/// <param name="format">回傳格式 (json/csv)</param>
/// <param name="limit">限制筆數</param>
/// <returns></returns>
[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}");
}
}
/// <summary>
/// Excel 數據連接專用 - 按欄位顏色分類格式
/// GET api/pivot/excel_data_structured
/// </summary>
/// <param name="includeMetadata">是否包含元數據</param>
/// <param name="limit">限制筆數</param>
/// <returns></returns>
[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
/// <summary>
/// 釋放資源
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
_db?.Dispose();
}
base.Dispose(disposing);
}
#endregion
}