Files
17168ERP/web/App_Code/api/transfer_registerController.cs
T
2026-05-11 15:59:23 +08:00

1283 lines
51 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 MyWeb;
using System.Web.WebPages;
using System.Data.Entity;
using Model;
[ezAuthorize]
public class transfer_registerController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
// GET api/transfer_register
// TODO: CRITICAL - 無參數 Get() 會載入所有登記到內存(可能數千筆)
// 建議:停用此方法,強制使用分頁版本 Get(page, pageSize)
public IEnumerable<Model.transfer_register> Get()
{
var list = _db.transfer_register.ToList();
if (list == null) throw new HttpResponseException(HttpStatusCode.NotFound);
return list;
}
// GET api/transfer_register?page=1&pageSize=10
public IEnumerable<Model.transfer_register> Get(int page, int pageSize = 10,
string sortBy = "", bool sortDesc = false)
{
var list = _db.transfer_register.OrderBy(o => o.id).ToPagedList(page, pageSize);
if (list == null) throw new HttpResponseException(HttpStatusCode.NotFound);
return list;
}
// GET api/transfer_register/5
public Model.transfer_register Get(int id)
{
var item = _db.transfer_register.Where(q => q.id == id).FirstOrDefault();
if (item == null) throw new HttpResponseException(HttpStatusCode.NotFound);
return item;
}
// POST api/transfer_register
[HttpPost]
public IHttpActionResult Post()
{
try
{
var request = System.Web.HttpContext.Current.Request;
var item = new Model.transfer_register();
item.activity_num = Convert.ToInt32(request.Form["activity_num"]);
item.name = request.Form["name"];
item.phone = request.Form["phone"];
item.pay_type = request.Form["pay_type"];
item.account_last5 = request.Form["account_last5"];
item.amount = Convert.ToDecimal(request.Form["amount"]);
item.pay_mode = request.Form["pay_mode"];
item.note = request.Form["note"];
item.draft = request.Form["draft"];
item.create_time = DateTime.Now;
// 查詢 followers
var follower = _db.followers
//.FirstOrDefault(f => f.u_name == item.name && f.phone == item.phone);
.FirstOrDefault(f => f.u_name == item.name);
//先檢查姓名就好, 電話有加密, 待處理
//若有同名, 先全抓, 解密, 再比對電話或手機
if (follower != null)
{
item.f_num = follower.num;
}
// 檔案處理,只允許 JPG, PDF
if (request.Files.Count > 0)
{
var file = request.Files[0];
var ext = System.IO.Path.GetExtension(file.FileName).ToLower();
if (ext != ".jpg" && ext != ".jpeg" && ext != ".pdf")
{
return BadRequest("只允許上傳 JPG 或 PDF 檔案");
}
var fileName = Guid.NewGuid().ToString("N") + ext;
var savePath = System.Web.HttpContext.Current.Server.MapPath("~/upload/transfer_proof/");
if (!System.IO.Directory.Exists(savePath))
System.IO.Directory.CreateDirectory(savePath);
file.SaveAs(System.IO.Path.Combine(savePath, fileName));
item.proof_img = fileName;
}
_db.transfer_register.Add(item);
_db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = item.id }, item);
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
// PUT api/transfer_register/5
public IHttpActionResult Put(int id, [FromBody] Model.transfer_register item)
{
if (item == null || id != item.id)
return BadRequest("Invalid data");
try
{
var existingItem = _db.transfer_register.Where(q => q.id == id).FirstOrDefault();
if (existingItem == null)
return NotFound();
// 更新欄位
existingItem.activity_num = item.activity_num;
existingItem.name = item.name;
existingItem.phone = item.phone;
existingItem.pay_type = item.pay_type;
existingItem.account_last5 = item.account_last5;
existingItem.amount = item.amount;
existingItem.pay_mode = item.pay_mode;
existingItem.note = item.note;
existingItem.proof_img = item.proof_img;
existingItem.status = item.status;
existingItem.f_num_match = item.f_num_match;
existingItem.f_num = item.f_num;
existingItem.acc_num = item.acc_num;
existingItem.check_date = item.check_date;
existingItem.check_amount = item.check_amount;
existingItem.check_memo = item.check_memo;
existingItem.check_status = item.check_status;
existingItem.acc_kind = item.acc_kind;
existingItem.member_num = item.member_num;
existingItem.verify_time = item.verify_time;
existingItem.verify_note = item.verify_note;
existingItem.draft = item.draft;
_db.SaveChanges();
// 記錄操作日誌
Model.admin_log admin_log = new Model.admin_log();
MyWeb.admin admin = new MyWeb.admin();
if (admin.isLoign())
{
admin_log.writeLog(admin.info.u_id, (int)Model.admin_log.Systems.Accounting,
(int)Model.admin_log.Status.Update,
$"更新匯款登記: {item.name} {item.amount}");
}
return Ok();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
// DELETE api/transfer_register/5
public IHttpActionResult Delete(int id)
{
try
{
var item = _db.transfer_register.Where(q => q.id == id).FirstOrDefault();
if (item == null)
return NotFound();
_db.transfer_register.Remove(item);
_db.SaveChanges();
// 記錄操作日誌
Model.admin_log admin_log = new Model.admin_log();
MyWeb.admin admin = new MyWeb.admin();
if (admin.isLoign())
{
admin_log.writeLog(admin.info.u_id, (int)Model.admin_log.Systems.Accounting,
(int)Model.admin_log.Status.Delete,
$"刪除匯款登記: {item.name} {item.amount}");
}
return Ok();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
// GET api/transfer_register/count
[HttpGet]
[Route("api/transfer_register/count")]
public int Count()
{
return _db.transfer_register.Count();
}
// GET api/transfer_register/pending
[HttpGet]
[Route("api/transfer_register/pending")]
public IHttpActionResult GetPending()
{
var encrypt = new MyWeb.encrypt();
var list = _db.transfer_register
.Where(x => (
x.status == null ||
x.status == "" ||
x.status == ((int)Model.transfer_register.Status.Pending).ToString()
) && (
x.check_status == null ||
x.check_status == "" ||
x.check_status == ((int)Model.transfer_register.CheckStatus.Unchecked).ToString()
))
.ToList()
.Select(x => new
{
x.id,
x.activity_num,
x.name,
x.phone,
x.f_num,
x.pay_type,
x.amount,
x.account_last5,
x.pay_mode,
x.status,
x.note,
x.proof_img,
x.create_time,
x.check_memo,
x.check_status,
x.verify_note,
x.acc_num,
x.check_date,
x.check_amount,
x.draft,
follower = x.f_num != null ? _db.followers
.Where(f => f.num == x.f_num)
.ToList()
.Select(f => new {
f.num,
f.u_name,
phone = encrypt.DecryptAutoKey(f.phone),
cellphone = encrypt.DecryptAutoKey(f.cellphone)
}).FirstOrDefault() : null
})
.ToList();
/*
f_num_match, acc_num, check_date, check_amount,
acc_kind, member_num, verify_time,
*/
return Ok(list);
}
public class transfer_register_update_dto
{
public int id { get; set; }
public int? f_num { get; set; }
public string status { get; set; }
public string check_status { get; set; }
public string verify_note { get; set; }
public int? acc_num { get; set; }
public DateTime? check_date { get; set; }
public decimal? check_amount { get; set; }
public string check_memo { get; set; }
public string draft { get; set; }
public int? acc_kind { get; set; } // 新增關聯欄位
public int? kind { get; set; }
}
[HttpPost]
[Route("api/transfer_register/batch_update")]
public IHttpActionResult BatchUpdate(List<transfer_register_update_dto> list)
{
try
{
foreach (var dto in list)
{
var item = _db.transfer_register.Find(dto.id);
if (item != null)
{
// 如果狀態改為核對(2)且還沒有會計記錄,則新增會計記錄
if (dto.check_status == "2" && item.acc_kind == null)
{
// 取得活動名稱
string activityName = "";
if (item.activity_num.HasValue)
{
var activity = _db.activities.FirstOrDefault(a => a.num == item.activity_num.Value);
activityName = activity?.subject ?? "";
}
// 取得信眾資訊
string followerName = "";
string followerPhone = "";
if (item.f_num.HasValue)
{
var follower = _db.followers.FirstOrDefault(f => f.num == item.f_num.Value);
if (follower != null)
{
followerName = follower.u_name ?? "";
var encrypt = new MyWeb.encrypt();
followerPhone = encrypt.DecryptAutoKey(follower.phone) ?? encrypt.DecryptAutoKey(follower.cellphone) ?? "";
}
}
// 組合 demo 欄位
string demo = $"法會:{activityName}\n姓名:{followerName}\n電話:{followerPhone}";
// 新增會計記錄
var accounting = new Model.accounting
{
uptime = dto.check_date,
category = 1, // 收入
kind = dto.kind,//27, // 固定值:法會收入/功德項目
kind2 = dto.acc_num,
price = (float)(dto.check_amount ?? 0),
tax = 0,
demo = demo,
excerpt = dto.check_memo,
activity_num = item.activity_num,
reg_time = DateTime.Now,
mem_num = null, // 不設定 mem_num,避免外鍵約束衝突
debtor = item.name,
pro_order_detail_num = null
};
_db.accountings.Add(accounting);
_db.SaveChanges(); // 先儲存以取得 accounting.num
// 設定關聯
dto.acc_kind = accounting.num;
}
// 更新 transfer_register 記錄
item.f_num = dto.f_num;
item.status = dto.status;
item.check_status = dto.check_status;
item.verify_note = dto.verify_note;
item.acc_num = dto.acc_num;
item.check_date = dto.check_date;
item.check_amount = dto.check_amount;
item.check_memo = dto.check_memo;
item.draft = dto.draft;
item.acc_kind = dto.acc_kind;
}
}
_db.SaveChanges();
return Ok(new { success = true });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
// GET api/transfer_register/personal_reconcile_list
[HttpGet]
[Route("api/transfer_register/personal_reconcile_list")]
public IHttpActionResult GetPersonalReconcileList()
{
var list = _db.transfer_register
.Where(tr =>
tr.status == "2" &&
tr.check_status == "2" &&
tr.pay_mode == "個人"
)
.Select(tr => new
{
tr.id,
tr.f_num,
follower = tr.follower != null ? tr.follower.u_name : "",
tr.acc_num,
acc_name = tr.acc_num != null ? _db.accounting_kind2.Where(a => a.num == tr.acc_num).Select(a => a.kind).FirstOrDefault() : "",
tr.check_date,
tr.check_amount,
tr.check_memo,
tr.check_status,
tr.verify_note,
tr.activity_num,
tr.name,
tr.phone,
tr.pay_type,
tr.account_last5,
tr.pay_mode,
tr.status,
tr.note,
tr.proof_img,
tr.create_time,
tr.draft
})
.ToList();
return Ok(list);
}
// GET api/transfer_register/follower_orders?f_num=123
[HttpGet]
[Route("api/transfer_register/follower_orders")]
public IHttpActionResult GetFollowerOrders(int f_num)
{
// TODO: 優化建議 - 考慮加入分頁或日期範圍限制,避免單一信眾訂單過多時載入大量數據
// 取得該信眾的所有訂單及明細,並關聯活動名稱與品項名稱
var details = _db.pro_order
.Where(o => o.f_num == f_num)
.SelectMany(o => o.pro_order_detail.Select(d => new {
o.order_no,
o.activity_num,
activity_name = o.activity != null ? o.activity.subject : "",
d.num,
d.actItem_num,
actitem_name = d.actItem != null ? d.actItem.subject : "",
price = d.price,
paid = d.pro_order_record.Select(r => r.price).DefaultIfEmpty(0).Sum(),
d.pay,
d.pay_date,
d.due_date,
d.demo,
reg_time = o.reg_time // 報名日期
}))
.AsEnumerable() // 只在需要內存計算前轉換
.Select(x => new {
x.order_no,
x.activity_num,
x.activity_name,
x.num,
x.actItem_num,
x.actitem_name,
price = (decimal?)x.price,
paid = (decimal?)x.paid,
due = ((decimal?)x.price ?? 0) - ((decimal?)x.paid ?? 0),
x.pay,
x.pay_date,
x.due_date,
x.demo,
x.reg_time
})
.Where(x => Math.Round(x.due, 2) > 0)
.OrderBy(x => x.reg_time)
.ToList();
return Ok(details);
}
[HttpGet]
[Route("api/transfer_register/step2_list")]
public IHttpActionResult GetStep2List()
{
var encrypt = new MyWeb.encrypt();
var list = _db.transfer_register
.Where(x =>
x.status == ((int)Model.transfer_register.Status.Confirmed).ToString() &&
(
x.check_status == null ||
x.check_status == "" ||
x.check_status == ((int)Model.transfer_register.CheckStatus.Unchecked).ToString() ||
x.check_status == ((int)Model.transfer_register.CheckStatus.AmountMismatch).ToString() ||
x.check_status == ((int)Model.transfer_register.CheckStatus.OtherIssue).ToString()
)
)
.ToList()
.Select(x => new
{
x.id,
x.activity_num,
x.name,
x.phone,
x.f_num,
x.pay_type,
x.amount,
x.account_last5,
x.pay_mode,
x.status,
x.note,
x.proof_img,
x.create_time,
x.check_memo,
x.check_status,
x.verify_note,
x.acc_num,
x.check_date,
x.check_amount,
x.draft,
follower = x.f_num != null ? _db.followers
.Where(f => f.num == x.f_num)
.ToList()
.Select(f => new {
f.num,
f.u_name,
phone = encrypt.DecryptAutoKey(f.phone),
cellphone = encrypt.DecryptAutoKey(f.cellphone)
}).FirstOrDefault() : null
})
.ToList();
return Ok(list);
}
public class ReconcileDetail
{
public int pro_order_detail_num { get; set; }
public decimal amount { get; set; }
public int? acc_num { get; set; } // 帳戶
public DateTime? reg_time { get; set; } // 日期
}
public class ReconcileRequest
{
public int transfer_register_id { get; set; }
public List<ReconcileDetail> details { get; set; }
public decimal over_payment { get; set; }
}
[HttpPost]
[Route("api/transfer_register/reconcile")]
public IHttpActionResult Reconcile([FromBody] ReconcileRequest request)
{
var details = request.details;
if (details == null || details.Count == 0)
return BadRequest("無沖帳明細");
using (var transaction = _db.Database.BeginTransaction())
{
try
{
var transferRegister = _db.transfer_register.FirstOrDefault(tr => tr.id == request.transfer_register_id);
if (transferRegister == null)
return BadRequest("找不到對應的入帳記錄");
decimal totalReconcileAmount = details.Sum(d => d.amount);
decimal checkAmount = transferRegister.check_amount ?? 0;
if (totalReconcileAmount > checkAmount)
{
return BadRequest($"沖帳總額 ({totalReconcileAmount:N0}) 不可大於入帳金額 ({checkAmount:N0})");
}
// 移除會計科目檢查 - 不再需要建立 accounting 記錄
// var accountingKind = _db.accounting_kind.FirstOrDefault(ak => ak.num == 27);
// if (accountingKind == null)
// {
// return BadRequest("找不到會計科目設定 (kind=27)");
// }
// 移除 accounting 變數宣告 - 不再需要建立 accounting 記錄
// Model.accounting firstAccountingRecord = null;
foreach (var d in details)
{
var proOrderDetail = _db.pro_order_detail
.Include(pod => pod.pro_order)
.FirstOrDefault(x => x.num == d.pro_order_detail_num);
if (proOrderDetail == null)
return BadRequest($"找不到訂單明細編號: {d.pro_order_detail_num}");
decimal paidAmount = proOrderDetail.pro_order_record?.Select(r => (decimal)r.price).DefaultIfEmpty(0).Sum() ?? 0;
decimal dueAmount = (decimal)proOrderDetail.price - paidAmount;
if (d.amount > Math.Round(dueAmount, 2))
return BadRequest($"沖帳金額 ({d.amount:N0}) 超過應繳金額 ({dueAmount:N0})");
// get session user
MyWeb.admin admin = new MyWeb.admin();
Model.admin adminInfo = null;
if (admin.isLoign())
{
}
// 原本的 accounting 建立 (已移除)
/*
var acc = new Model.accounting
{
pro_order_detail_num = d.pro_order_detail_num, // 報名明細編號
price = (float)d.amount,// 沖帳金額
reg_time = d.reg_time ?? DateTime.Now, // 登記時間
kind = 27,// 固定會計科目
category = 1,//1:收入, 2:支出,
kind2 = transferRegister.acc_num,// 入帳銀行帳戶
mem_num = null,// 沖帳人編號:目前登入的使用者
activity_num = proOrderDetail.pro_order.activity_num,// 活動編號
uptime = DateTime.Today,// 更新時間
tax = 0, //??
excerpt = ""// 摘要
};
_db.accountings.Add(acc);
if (firstAccountingRecord == null)
{
firstAccountingRecord = acc;
}
*/
// 更改後的 pro_order_record 建立 (正確的付款記錄)
var proOrderRecord = new Model.pro_order_record
{
detail_num = d.pro_order_detail_num, // 報名明細編號
price = (float)d.amount, // 付款金額 (沖帳金額)
payment = transferRegister.acc_num, // 付款機構
reg_time = d.reg_time ?? DateTime.Now, // 登記時間
pay_date = transferRegister.check_date, // 付款日期
bank_code = transferRegister.account_last5, // 帳號後5碼
transfer_id = transferRegister.id, // 匯款登記編號
reconcile_memo = transferRegister.check_memo // 沖帳備註
};
_db.pro_order_record.Add(proOrderRecord);
}
decimal remainAmount = checkAmount - totalReconcileAmount;
// 設定剩餘金額
transferRegister.remain_amount = remainAmount;
// 重新生成 draft 作為沖帳記錄依據,包含額外欄位以提升查詢效率
// admin\transfer\personal_reconcile.aspx : saveDraft()
var reconcileRecord = details
.Where(d => d.amount > 0)
.Select(d => {
var proOrderDetail = _db.pro_order_detail
.Include(pod => pod.actItem)
.Include(pod => pod.pro_order)
.Include(pod => pod.pro_order.activity)
.FirstOrDefault(x => x.num == d.pro_order_detail_num);
return new {
pro_order_detail_num = d.pro_order_detail_num,
reconcile = d.amount,
// 額外欄位,避免後續查詢
actitem_name = proOrderDetail?.actItem?.subject ?? "",
activity_name = proOrderDetail?.pro_order?.activity?.subject ?? "",
register_date = proOrderDetail?.pro_order?.reg_time,
order_no = proOrderDetail?.order_no ?? "",
price = proOrderDetail?.price ?? 0
};
})
.ToList();
transferRegister.draft = Newtonsoft.Json.JsonConvert.SerializeObject(reconcileRecord);
string statusMessage;
if (remainAmount == 0)
{
// 沖帳完成且無剩餘
transferRegister.check_status = "99";
statusMessage = "沖帳完成";
}
else
{
// 沖帳完成但有剩餘
transferRegister.check_status = "90";
statusMessage = $"沖帳完成但有剩餘 NT$ {remainAmount:N0}";
}
_db.SaveChanges();
transaction.Commit();
return Ok(new { success = true, message = statusMessage });
}
catch (Exception ex)
{
transaction.Rollback();
return InternalServerError(ex);
}
}
}
// GET api/transfer_register/reconcile_detail?transfer_register_id=123
[HttpGet]
[Route("api/transfer_register/reconcile_detail")]
public IHttpActionResult GetReconcileDetail(int transfer_register_id)
{
var transferRegister = _db.transfer_register.FirstOrDefault(tr => tr.id == transfer_register_id);
if (transferRegister == null)
return NotFound();
var result = new List<object>();
if (!string.IsNullOrEmpty(transferRegister.draft))
{
try
{
// 如果 draft 已包含額外欄位,直接返回
var draftData = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic[]>(transferRegister.draft);
foreach (var item in draftData)
{
// 檢查是否已有額外欄位
if (item.actitem_name != null)
{
result.Add(item);
}
else
{
// 舊格式,需要查詢額外資訊
int proOrderDetailNum = item.pro_order_detail_num;
var proOrderDetail = _db.pro_order_detail
.Include(pod => pod.actItem)
.Include(pod => pod.pro_order)
.Include(pod => pod.pro_order.activity)
.FirstOrDefault(x => x.num == proOrderDetailNum);
result.Add(new {
pro_order_detail_num = proOrderDetailNum,
reconcile = item.reconcile,
actitem_name = proOrderDetail?.actItem?.subject ?? "",
activity_name = proOrderDetail?.pro_order?.activity?.subject ?? "",
register_date = proOrderDetail?.pro_order?.reg_time,
order_no = proOrderDetail?.order_no ?? "",
price = proOrderDetail?.price ?? 0
});
}
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
return Ok(result);
}
// GET api/transfer_register/balance_reconcile_list
// DTO for balance update
public class BalanceUpdateDto
{
public int id { get; set; }
public string check_status { get; set; }
public int? balance_act_item { get; set; }
public string verify_note { get; set; }
}
[HttpPost]
[Route("api/transfer_register/update_balance")]
public IHttpActionResult UpdateBalance([FromBody] BalanceUpdateDto dto)
{
try
{
var item = _db.transfer_register.Find(dto.id);
if (item == null)
return NotFound();
// 更新狀態、餘額項目和核對記錄
item.check_status = dto.check_status;
item.balance_act_item = dto.balance_act_item;
item.verify_note = dto.verify_note;
_db.SaveChanges();
// 記錄操作日誌
Model.admin_log admin_log = new Model.admin_log();
MyWeb.admin admin = new MyWeb.admin();
if (admin.isLoign())
{
string actItemName = "未知項目";
if (dto.balance_act_item.HasValue)
{
var actItem = _db.actItems.Find(dto.balance_act_item.Value);
if (actItem != null)
actItemName = actItem.subject;
}
string logMessage = $"更新餘額核銷: {item.name} 狀態:{dto.check_status} 項目:{actItemName}";
if (!string.IsNullOrEmpty(dto.verify_note))
{
logMessage += $" 核對記錄:{dto.verify_note}";
}
admin_log.writeLog(admin.info.u_id, (int)Model.admin_log.Systems.Accounting,
(int)Model.admin_log.Status.Update, logMessage);
}
return Ok(new { success = true, message = "餘額更新成功" });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
[HttpGet]
[Route("api/transfer_register/balance_reconcile_list")]
public IHttpActionResult GetBalanceReconcileList()
{
var encrypt = new MyWeb.encrypt();
var list = _db.transfer_register
.Where(tr =>
tr.check_status != null &&
tr.check_status != "" &&
(tr.check_status == "90" || tr.check_status == "91" || tr.check_status == "92")
)
.Select(tr => new
{
tr.id,
tr.f_num,
tr.acc_num,
tr.check_date,
tr.check_amount,
tr.check_memo,
tr.check_status,
tr.verify_note,
tr.activity_num,
tr.name,
tr.phone,
tr.pay_type,
tr.account_last5,
tr.amount,
tr.pay_mode,
tr.note,
tr.proof_img,
tr.create_time,
tr.draft,
tr.status,
follower_info = tr.follower != null ? new {
u_name = tr.follower.u_name,
phone = tr.follower.phone,
cellphone = tr.follower.cellphone
} : null,
acc_name = tr.acc_num != null ? _db.accounting_kind2.Where(a => a.num == tr.acc_num).Select(a => a.kind).FirstOrDefault() : "",
activity_name = tr.activity != null ? tr.activity.subject : "",
activity_start_date = tr.activity != null ? tr.activity.startDate_solar : null,
tr.remain_amount,
tr.balance_act_item,
balance_actitem_name = tr.balance_act_item != null ? _db.actItems.Where(a => a.num == tr.balance_act_item).Select(a => a.subject).FirstOrDefault() : ""
})
.ToList()
.Select(tr => new
{
tr.id,
tr.f_num,
follower = tr.follower_info?.u_name ?? "",
phone = tr.follower_info != null ? encrypt.DecryptAutoKey(tr.follower_info.phone) : "",
phone2 = tr.follower_info != null ? encrypt.DecryptAutoKey(tr.follower_info.cellphone) : "",
tr.acc_name,
tr.check_date,
tr.check_amount,
remain_amount = tr.remain_amount ?? 0,
tr.check_status,
tr.verify_note,
tr.check_memo,
tr.activity_num,
tr.activity_name,
tr.activity_start_date,
tr.name,
tr.pay_type,
tr.account_last5,
tr.amount,
tr.pay_mode,
tr.note,
tr.proof_img,
tr.create_time,
tr.draft,
tr.status,
tr.balance_act_item
})
.Where(tr => tr.remain_amount > 0) // 只顯示有剩餘金額的記錄
.OrderBy(tr => tr.check_date)
.ToList();
return Ok(list);
}
[HttpGet]
[Route("api/transfer_register/balance_reconcile_query")]
public IHttpActionResult BalanceReconcileQuery(DateTime? start_date = null, DateTime? end_date = null, int? activity_num = null, int? follower_num = null, string is_reconcile = null)
{
var query = _db.transfer_register.AsQueryable();
// 只查狀態為95
query = query.Where(x => x.check_status == "95");
if (start_date.HasValue)
query = query.Where(x => x.check_date >= start_date.Value);
if (end_date.HasValue)
query = query.Where(x => x.check_date <= end_date.Value);
if (activity_num.HasValue)
query = query.Where(x => x.activity_num == activity_num.Value);
if (follower_num.HasValue)
query = query.Where(x => x.f_num == follower_num.Value);
var list = query
.OrderByDescending(x => x.check_date)
.Select(x => new {
x.id,
x.f_num,
follower = x.follower != null ? x.follower.u_name : "",
x.acc_num,
acc_name = x.acc_num != null ? _db.accounting_kind2.Where(a => a.num == x.acc_num).Select(a => a.kind).FirstOrDefault() : "",
x.check_date,
x.check_amount,
x.check_memo,
x.check_status,
x.verify_note,
x.activity_num,
activity_name = x.activity != null ? x.activity.subject : "",
balance_actitem_name = x.balance_act_item != null ? _db.actItems.Where(a => a.num == x.balance_act_item).Select(a => a.subject).FirstOrDefault() : "",
x.name,
x.pay_type,
x.account_last5,
x.amount,
x.pay_mode,
x.note,
x.proof_img,
x.create_time,
x.draft,
x.status,
x.balance_act_item,
reconcile_amount = x.remain_amount
})
.ToList();
return Ok(list);
}
[HttpGet]
[Route("api/transfer_register/verify_order_record_query")]
public IHttpActionResult VerifyOrderRecordQuery(DateTime? start_date = null, DateTime? end_date = null, int? activity_num = null, int? follower_num = null)
{
var query = _db.transfer_register.AsQueryable();
// 只查已完成沖帳的記錄 (狀態為 90 或 99)
query = query.Where(x => x.check_status == "90" || x.check_status == "99");
if (start_date.HasValue)
query = query.Where(x => x.check_date >= start_date.Value);
if (end_date.HasValue)
query = query.Where(x => x.check_date <= end_date.Value);
if (activity_num.HasValue)
query = query.Where(x => x.activity_num == activity_num.Value);
if (follower_num.HasValue)
query = query.Where(x => x.f_num == follower_num.Value);
var list = query
.OrderByDescending(x => x.check_date)
.Select(x => new {
// transfer_register 基本資訊
transfer_id = x.id,
transfer_name = x.name,
transfer_phone = x.phone,
transfer_pay_type = x.pay_type,
transfer_account_last5 = x.account_last5,
transfer_amount = x.amount,
transfer_pay_mode = x.pay_mode,
transfer_note = x.note,
transfer_proof_img = x.proof_img,
transfer_status = x.status,
transfer_create_time = x.create_time,
transfer_check_date = x.check_date,
transfer_check_amount = x.check_amount,
transfer_check_memo = x.check_memo,
transfer_check_status = x.check_status,
transfer_verify_note = x.verify_note,
transfer_remain_amount = x.remain_amount,
transfer_draft = x.draft,
// 關聯資訊
follower = x.follower != null ? x.follower.u_name : "",
follower_num = x.f_num,
activity_name = x.activity != null ? x.activity.subject : "",
activity_num = x.activity_num,
acc_name = x.acc_num != null ? _db.accounting_kind2.Where(a => a.num == x.acc_num).Select(a => a.kind).FirstOrDefault() : "",
price_totals=_db.pro_order_detail.
Where(a => _db.pro_order.Where (po=>po.f_num==x.f_num&&po.activity_num==x.activity_num).
Select(po => po.order_no).Any(p=>p.Equals(a.order_no))).Sum(a => a.price*a.qty),
pay_totals=_db.transfer_register.Where(a=>a.activity_num==x.activity_num&&a.f_num==x.f_num).Sum(a=>a.check_amount),
// pro_order_record 資訊 (透過 transfer_id 關聯)
pro_order_records = x.pro_order_record.Select(pr => new {
pr.num,
pr.detail_num,
pr.price,
pr.payment,
pr.reg_time,
pr.pay_date,
pr.organization,
pr.bank_code,
pr.transfer_id,
pr.reconcile_memo,
// 關聯的 pro_order_detail 資訊
pro_order_detail = pr.pro_order_detail != null ? new {
pr.pro_order_detail.num,
pr.pro_order_detail.price,
pr.pro_order_detail.pay,
pr.pro_order_detail.pay_date,
pr.pro_order_detail.due_date,
pr.pro_order_detail.demo,
// 關聯的 pro_order 資訊
pro_order = pr.pro_order_detail.pro_order != null ? new {
pr.pro_order_detail.pro_order.order_no,
pr.pro_order_detail.pro_order.reg_time,
pr.pro_order_detail.pro_order.activity_num,
activity_name = pr.pro_order_detail.pro_order.activity != null ? pr.pro_order_detail.pro_order.activity.subject : ""
} : null,
// 關聯的 actItem 資訊
actitem_name = pr.pro_order_detail.actItem != null ? pr.pro_order_detail.actItem.subject : ""
} : null,
// 關聯的 accounting_kind2 資訊
payment_name = pr.accounting_kind2 != null ? pr.accounting_kind2.kind : ""
}).ToList()
})
.ToList();
return Ok(list);
}
// GET api/transfer_register/group_reconcile_list
[HttpGet]
[Route("api/transfer_register/group_reconcile_list")]
public IHttpActionResult GetGroupReconcileList()
{
var list = _db.transfer_register
.Where(tr =>
tr.status == "2" &&
tr.check_status == "2" &&
tr.pay_mode == "共同"
)
.Select(tr => new
{
tr.id,
tr.f_num,
follower = tr.follower != null ? tr.follower.u_name : "",
tr.acc_num,
acc_name = tr.acc_num != null ? _db.accounting_kind2.Where(a => a.num == tr.acc_num).Select(a => a.kind).FirstOrDefault() : "",
tr.check_date,
tr.check_amount,
tr.check_memo,
tr.check_status,
tr.verify_note,
tr.activity_num,
activity_name = tr.activity != null ? tr.activity.subject : "",
tr.name,
tr.phone,
tr.pay_type,
tr.account_last5,
tr.pay_mode,
tr.status,
tr.note,
tr.proof_img,
tr.create_time,
tr.draft
})
.ToList();
return Ok(list);
}
// GET api/transfer_register/activity_followers?activity_num=123
[HttpGet]
[Route("api/transfer_register/activity_followers")]
public IHttpActionResult GetActivityFollowers(int activity_num)
{
// TODO: 性能優化 - 大型活動可能有數千筆訂單,建議:
// 1. 在數據庫層面先過濾出未完成沖帳項目(避免載入所有訂單)
// 2. 考慮加入 Take() 限制返回數量,或實作分頁
// 查詢該法會中有報名單且有未完成沖帳項目的信眾
var followers = _db.pro_order
.Where(o => o.activity_num == activity_num)
.SelectMany(o => o.pro_order_detail.Select(d => new {
o.f_num,
follower_name = o.follower != null ? o.follower.u_name : "",
d.num,
d.price,
paid = d.pro_order_record.Select(r => r.price).DefaultIfEmpty(0).Sum()
}))
.AsEnumerable() // 只在需要內存計算前轉換
.Select(x => new {
x.f_num,
x.follower_name,
x.num,
price = (decimal?)x.price,
paid = (decimal?)x.paid,
due = ((decimal?)x.price ?? 0) - ((decimal?)x.paid ?? 0)
})
.Where(x => Math.Round(x.due, 2) > 0) // 有未完成沖帳項目
.GroupBy(x => new { x.f_num, x.follower_name })
.Select(g => new {
f_num = g.Key.f_num,
follower_name = g.Key.follower_name,
total_due = g.Sum(x => x.due),
item_count = g.Count()
})
.OrderBy(x => x.follower_name)
.ToList();
return Ok(followers);
}
// GET api/transfer_register/group_follower_orders?activity_num=123&f_nums=123,456,789
[HttpGet]
[Route("api/transfer_register/group_follower_orders")]
public IHttpActionResult GetGroupFollowerOrders(int activity_num, string f_nums)
{
var followerNumList = f_nums.Split(',').Select(int.Parse).ToList();
// 取得多個信眾在指定法會的所有訂單及明細
var details = _db.pro_order
.Where(o => o.activity_num == activity_num && followerNumList.Contains(o.f_num ?? 0))
.SelectMany(o => o.pro_order_detail.Select(d => new {
o.order_no,
o.activity_num,
o.f_num,
follower_name = o.follower != null ? o.follower.u_name : "",
activity_name = o.activity != null ? o.activity.subject : "",
d.num,
d.actItem_num,
actitem_name = d.actItem != null ? d.actItem.subject : "",
price = d.price,
paid = d.pro_order_record.Select(r => r.price).DefaultIfEmpty(0).Sum(),
d.pay,
d.pay_date,
d.due_date,
d.demo,
reg_time = o.reg_time // 報名日期
}))
.ToList()
.Select(x => new {
x.order_no,
x.activity_num,
x.f_num,
x.follower_name,
x.activity_name,
x.num,
x.actItem_num,
x.actitem_name,
price = (decimal?)x.price,
paid = (decimal?)x.paid,
due = ((decimal?)x.price ?? 0) - ((decimal?)x.paid ?? 0),
x.pay,
x.pay_date,
x.due_date,
x.demo,
x.reg_time
})
.Where(x => Math.Round(x.due, 2) > 0)
.OrderBy(x => x.follower_name) // 同一信眾的在一起
.ThenBy(x => x.reg_time)
.ToList();
return Ok(details);
}
public class GroupReconcileDetail
{
public int f_num { get; set; }
public int pro_order_detail_num { get; set; }
public decimal amount { get; set; }
public DateTime? reg_time { get; set; }
}
public class GroupReconcileRequest
{
public int transfer_register_id { get; set; }
public List<GroupReconcileDetail> details { get; set; }
public decimal over_payment { get; set; }
}
[HttpPost]
[Route("api/transfer_register/group_reconcile")]
public IHttpActionResult GroupReconcile([FromBody] GroupReconcileRequest request)
{
var details = request.details;
if (details == null || details.Count == 0)
return BadRequest("無沖帳明細");
using (var transaction = _db.Database.BeginTransaction())
{
try
{
var transferRegister = _db.transfer_register.FirstOrDefault(tr => tr.id == request.transfer_register_id);
if (transferRegister == null)
return BadRequest("找不到對應的入帳記錄");
decimal totalReconcileAmount = details.Sum(d => d.amount);
decimal checkAmount = transferRegister.check_amount ?? 0;
if (totalReconcileAmount > checkAmount)
{
return BadRequest($"沖帳總額 ({totalReconcileAmount:N0}) 不可大於入帳金額 ({checkAmount:N0})");
}
foreach (var d in details)
{
var proOrderDetail = _db.pro_order_detail
.Include(pod => pod.pro_order)
.FirstOrDefault(x => x.num == d.pro_order_detail_num);
if (proOrderDetail == null)
return BadRequest($"找不到訂單明細編號: {d.pro_order_detail_num}");
decimal paidAmount = proOrderDetail.pro_order_record?.Select(r => (decimal)r.price).DefaultIfEmpty(0).Sum() ?? 0;
decimal dueAmount = (decimal)proOrderDetail.price - paidAmount;
if (d.amount > Math.Round(dueAmount, 2))
return BadRequest($"沖帳金額 ({d.amount:N0}) 超過應繳金額 ({dueAmount:N0})");
// 建立 pro_order_record 記錄(與個人沖帳相同邏輯)
var proOrderRecord = new Model.pro_order_record
{
detail_num = d.pro_order_detail_num, // 報名明細編號
price = (float)d.amount, // 付款金額 (沖帳金額)
payment = transferRegister.acc_num, // 付款機構
reg_time = d.reg_time ?? DateTime.Now, // 登記時間
pay_date = transferRegister.check_date, // 付款日期
bank_code = transferRegister.account_last5, // 帳號後5碼
transfer_id = transferRegister.id, // 匯款登記編號
reconcile_memo = transferRegister.check_memo // 沖帳備註
};
_db.pro_order_record.Add(proOrderRecord);
}
decimal remainAmount = checkAmount - totalReconcileAmount;
// 設定剩餘金額
transferRegister.remain_amount = remainAmount;
// 生成 draft 記錄,包含共同支付人資訊
var reconcileRecord = details
.Where(d => d.amount > 0)
.Select(d => {
var proOrderDetail = _db.pro_order_detail
.Include(pod => pod.actItem)
.Include(pod => pod.pro_order)
.Include(pod => pod.pro_order.activity)
.Include(pod => pod.pro_order.follower)
.FirstOrDefault(x => x.num == d.pro_order_detail_num);
return new {
f_num = d.f_num,
follower_name = proOrderDetail?.pro_order?.follower?.u_name ?? "",
pro_order_detail_num = d.pro_order_detail_num,
reconcile = d.amount,
actitem_name = proOrderDetail?.actItem?.subject ?? "",
activity_name = proOrderDetail?.pro_order?.activity?.subject ?? "",
register_date = proOrderDetail?.pro_order?.reg_time,
order_no = proOrderDetail?.order_no ?? "",
price = proOrderDetail?.price ?? 0
};
})
.ToList();
// 建立包含 follower_list 的 draft 格式
var draftData = new {
transfer_draft = new object[0], // 空陣列,因為這是共同沖帳
pro_order_detail_items = reconcileRecord,
follower_list = reconcileRecord
.GroupBy(x => new { x.f_num, x.follower_name })
.Select(g => new {
f_num = g.Key.f_num,
f_name = g.Key.follower_name,
activity_num = transferRegister.activity_num
})
.ToList()
};
transferRegister.draft = Newtonsoft.Json.JsonConvert.SerializeObject(draftData);
string statusMessage;
if (remainAmount == 0)
{
// 沖帳完成且無剩餘
transferRegister.check_status = "99";
statusMessage = "共同沖帳完成";
}
else
{
// 沖帳完成但有剩餘
transferRegister.check_status = "90";
statusMessage = $"共同沖帳完成但有剩餘 NT$ {remainAmount:N0}";
}
_db.SaveChanges();
transaction.Commit();
return Ok(new { success = true, message = statusMessage });
}
catch (Exception ex)
{
transaction.Rollback();
return InternalServerError(ex);
}
}
}
}