Files
17168ERP/web/App_Code/api/transfer_registerController.cs
2025-08-29 01:27:25 +08:00

1271 lines
50 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 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<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)
.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<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 = 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<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() : ""
})
.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<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);
}
}
}
}