Files
17168ERP/web/App_Code/api/pivotController.cs
2025-10-19 21:59:22 +08:00

1142 lines
48 KiB
C#

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),
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 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}");
}
}
/// <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":
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
/// <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
}