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; [ezAuthorize] public class transfer_registerController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); // GET api/transfer_register public IEnumerable 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 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) .AsEnumerable() .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; } // 新增關聯欄位 } [HttpPost] [Route("api/transfer_register/batch_update")] public IHttpActionResult BatchUpdate(List 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 = 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) { // 取得該信眾的所有訂單及明細,並關聯活動名稱與品項名稱 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) .AsEnumerable() .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 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(); if (!string.IsNullOrEmpty(transferRegister.draft)) { try { // 如果 draft 已包含額外欄位,直接返回 var draftData = Newtonsoft.Json.JsonConvert.DeserializeObject(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() : "" }) .AsEnumerable() .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() : "", // 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) { // 查詢該法會中有報名單且有未完成沖帳項目的信眾 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 // 報名日期 })) .AsEnumerable() .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 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); } } } }