Files
F8WebAI/backend/AgriculturalPlatform.Api/Controllers/PaymentsController.cs
T
fanhongcai c377421a3e 新增农户功能升级:身份证OCR/OSS上传、头像采集、同名村检测、Region热度排序及表单体验优化
- Region 表新增热度列,热门省份优先显示
- 区县下拉按地级市分组、已录区县优先、支持全省搜索
- 组选择内置一组至二十组
- 银行卡号独立行+粗体预览,开户行独立行
- 新增农户类型:农户/个体户/合作社/经营集体/公司
- 身份证正反面支持高拍仪/摄像头OCR与手机扫码上传,附件存本地/OSS
- 新增农户头像采集组件(摄像头/本地上传),预留人脸识别收购扩展
- 同名村弹窗提醒(防选错乡镇)
- 编辑窗体禁止遮罩/Esc误关,整体加宽适配高频操作
- 修复二维码局域网手机无法访问(监听0.0.0.0、局域网IP识别、Vite代理兜底)
2026-08-13 16:07:35 +08:00

206 lines
8.9 KiB
C#

using AgriculturalPlatform.Api.Data;
using AgriculturalPlatform.Api.Dtos;
using AgriculturalPlatform.Api.Models;
using AgriculturalPlatform.Api.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace AgriculturalPlatform.Api.Controllers;
[ApiController]
[Route("api/payments")]
[Authorize]
public class PaymentsController(
AppDbContext db,
DataScopeService scope,
CurrentUserService currentUser,
NumberGenerator numberGen) : ControllerBase
{
/// <summary>付款记录列表</summary>
[HttpGet]
public async Task<ActionResult<PagedResult<PaymentDto>>> List(
string? status, int? farmerId, int? orgId, DateTime? from, DateTime? to,
[FromQuery] int page = 1, [FromQuery] int pageSize = 20, [FromQuery] string? keyword = null)
{
var visible = await scope.GetVisibleOrgIdsAsync(currentUser.Get());
var query = db.PaymentRecords
.Include(p => p.Farmer).Include(p => p.PurchaseOrder)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.AsQueryable();
if (visible is not null)
query = query.Where(p => p.PurchaserOrgId != null && visible.Contains(p.PurchaserOrgId.Value));
if (!string.IsNullOrWhiteSpace(status) && Enum.TryParse<PayStatus>(status, out var ps))
query = query.Where(p => p.Status == ps);
if (farmerId.HasValue) query = query.Where(p => p.FarmerId == farmerId);
if (orgId.HasValue) query = query.Where(p => p.PurchaserOrgId == orgId);
if (from.HasValue) query = query.Where(p => p.CreatedAt >= from);
if (to.HasValue) query = query.Where(p => p.CreatedAt < to.Value.Date.AddDays(1));
if (!string.IsNullOrWhiteSpace(keyword))
query = query.Where(p => p.PayNo.Contains(keyword)
|| p.TradeNo.Contains(keyword)
|| (p.Farmer != null && p.Farmer.Name.Contains(keyword)));
var total = await query.CountAsync();
var items = await query.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize).Take(pageSize)
.ToListAsync();
return Ok(new PagedResult<PaymentDto>(items.ToDtos(), total));
}
/// <summary>支付状态统计(待支付/成功/失败/异常)</summary>
[HttpGet("stats")]
public async Task<ActionResult> Stats()
{
var visible = await scope.GetVisibleOrgIdsAsync(currentUser.Get());
var query = db.PaymentRecords.AsQueryable();
if (visible is not null)
query = query.Where(p => p.PurchaserOrgId != null && visible.Contains(p.PurchaserOrgId.Value));
return Ok(new
{
pending = await query.CountAsync(p => p.Status == PayStatus.Pending),
success = await query.CountAsync(p => p.Status == PayStatus.Success),
failed = await query.CountAsync(p => p.Status == PayStatus.Failed),
abnormal = await query.CountAsync(p => p.Status == PayStatus.Abnormal)
});
}
/// <summary>农户应收/已付汇总(结算用)</summary>
[HttpGet("farmer-summary")]
public async Task<ActionResult> FarmerSummary(int farmerId, int? orgId)
{
var visible = await scope.GetVisibleOrgIdsAsync(currentUser.Get());
var orderQuery = db.PurchaseOrders
.Where(p => p.FarmerId == farmerId && p.Status == PurchaseStatus.Completed)
.AsQueryable();
if (visible is not null)
orderQuery = orderQuery.Where(p => visible.Contains(p.PurchaserOrgId));
if (orgId.HasValue)
orderQuery = orderQuery.Where(p => p.PurchaserOrgId == orgId);
var totalAmount = await orderQuery.SumAsync(p => (decimal?)p.Amount) ?? 0;
var payQuery = db.PaymentRecords
.Where(p => p.FarmerId == farmerId && p.Status == PayStatus.Success)
.AsQueryable();
if (visible is not null)
payQuery = payQuery.Where(p => p.PurchaserOrgId != null && visible.Contains(p.PurchaserOrgId.Value));
if (orgId.HasValue)
payQuery = payQuery.Where(p => p.PurchaserOrgId == orgId);
var paidAmount = await payQuery.SumAsync(p => (decimal?)p.Amount) ?? 0;
// 未结算的收购单(可批量付款)
var unpaidOrders = await db.PurchaseOrders
.Where(p => p.FarmerId == farmerId && p.Status == PurchaseStatus.Completed)
.Where(p => !db.PaymentRecords.Any(pay =>
pay.PurchaseOrderId == p.Id && pay.Status == PayStatus.Success))
.Include(p => p.Product)
.Select(p => new { p.Id, p.OrderNo, p.Product!.Name, p.NetWeight, p.Amount, p.UnitPrice })
.ToListAsync();
return Ok(new
{
farmerId,
totalAmount,
paidAmount,
unpaidAmount = Math.Round(totalAmount - paidAmount, 2),
unpaidOrders
});
}
/// <summary>发起支付(按收购单或对农户批量结算)</summary>
[HttpPost]
public async Task<ActionResult<PaymentDto>> Create(PaymentCreateRequest req)
{
if (!await db.Farmers.AnyAsync(f => f.Id == req.FarmerId))
return BadRequest(new { message = "农户不存在" });
if (!Enum.TryParse<PayMethod>(req.Method, out var method))
return BadRequest(new { message = "支付方式不正确" });
// 按收购单付款:校验订单并防止重复支付
if (req.PurchaseOrderId.HasValue)
{
var order = await db.PurchaseOrders.FindAsync(req.PurchaseOrderId);
if (order is null || order.FarmerId != req.FarmerId)
return BadRequest(new { message = "收购单不存在或与农户不匹配" });
if (await db.PaymentRecords.AnyAsync(p =>
p.PurchaseOrderId == req.PurchaseOrderId && p.Status == PayStatus.Success))
return BadRequest(new { message = "该收购单已支付成功" });
}
var cu = currentUser.Get()!;
int? purchaserOrgId = req.PurchaseOrderId.HasValue
? (await db.PurchaseOrders.FindAsync(req.PurchaseOrderId))?.PurchaserOrgId
: cu.OrgId;
var payNo = await numberGen.NextAsync("ZF");
var payment = new PaymentRecord
{
PayNo = payNo,
PurchaseOrderId = req.PurchaseOrderId,
PurchaserOrgId = purchaserOrgId,
FarmerId = req.FarmerId,
Amount = req.Amount,
Method = method,
Status = PayStatus.Pending,
OperatorId = cu.Id,
Notes = req.Notes
};
db.PaymentRecords.Add(payment);
await db.SaveChangesAsync();
return Ok((await db.PaymentRecords
.Include(p => p.Farmer).Include(p => p.PurchaseOrder)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.FirstAsync(p => p.Id == payment.Id)).ToDto());
}
/// <summary>确认支付结果(模拟收单回调,默认支付成功,可选失败/异常)</summary>
[HttpPut("{id:int}/confirm")]
public async Task<ActionResult<PaymentDto>> Confirm(int id, PaymentConfirmRequest req)
{
var payment = await db.PaymentRecords.FindAsync(id);
if (payment is null) return NotFound();
if (payment.Status != PayStatus.Pending)
return BadRequest(new { message = "当前状态不可确认" });
// Status 缺省或非法时按成功处理;成功/失败/异常均可写入
var result = Enum.TryParse<PayStatus>(req.Status, out var st)
&& st is PayStatus.Success or PayStatus.Failed or PayStatus.Abnormal
? st : PayStatus.Success;
payment.Status = result;
if (result == PayStatus.Success)
{
payment.PaidAt = DateTime.Now;
payment.TradeNo = string.IsNullOrWhiteSpace(req.TradeNo) ? payment.TradeNo : req.TradeNo;
if (string.IsNullOrWhiteSpace(payment.TradeNo))
payment.TradeNo = $"TRADE{DateTime.Now:yyyyMMddHHmmss}{Random.Shared.Next(100, 999)}";
}
if (!string.IsNullOrWhiteSpace(req.Message))
payment.Notes = string.IsNullOrWhiteSpace(payment.Notes) ? req.Message : $"{payment.Notes}\n{req.Message}";
await db.SaveChangesAsync();
return Ok((await db.PaymentRecords
.Include(p => p.Farmer).Include(p => p.PurchaseOrder)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.FirstAsync(p => p.Id == payment.Id)).ToDto());
}
/// <summary>退款</summary>
[HttpPut("{id:int}/refund")]
public async Task<IActionResult> Refund(int id)
{
var payment = await db.PaymentRecords.FindAsync(id);
if (payment is null) return NotFound();
if (payment.Status != PayStatus.Success)
return BadRequest(new { message = "仅已支付成功的记录可退款" });
payment.Status = PayStatus.Refunded;
await db.SaveChangesAsync();
return Ok(new { message = "退款成功" });
}
}