Files
F8WebAI/backend/AgriculturalPlatform.Api/Controllers/PurchasesController.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

182 lines
7.8 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/purchases")]
[Authorize]
public class PurchasesController(
AppDbContext db,
DataScopeService scope,
CurrentUserService currentUser,
NumberGenerator numberGen) : ControllerBase
{
/// <summary>收购单列表(分页 + 多条件 + 数据权限)</summary>
[HttpGet]
public async Task<ActionResult<PagedResult<PurchaseDto>>> List(
string? status, int? farmerId, int? productId, 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.PurchaseOrders
.Include(p => p.Farmer).Include(p => p.Product)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.AsQueryable();
if (visible is not null)
query = query.Where(p => visible.Contains(p.PurchaserOrgId));
if (!string.IsNullOrWhiteSpace(status) && Enum.TryParse<PurchaseStatus>(status, out var ps))
query = query.Where(p => p.Status == ps);
if (farmerId.HasValue) query = query.Where(p => p.FarmerId == farmerId);
if (productId.HasValue) query = query.Where(p => p.ProductId == productId);
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.OrderNo.Contains(keyword)
|| (p.Farmer != null && p.Farmer.Name.Contains(keyword))
|| (p.Farmer != null && p.Farmer.Phone.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<PurchaseDto>(items.ToDtos(), total));
}
/// <summary>进行中的过磅单(触摸屏/大屏实时展示)</summary>
[HttpGet("weighing")]
public async Task<ActionResult> Weighing()
{
var visible = await scope.GetVisibleOrgIdsAsync(currentUser.Get());
var query = db.PurchaseOrders
.Include(p => p.Farmer).Include(p => p.Product)
.Where(p => p.Status == PurchaseStatus.Weighing)
.AsQueryable();
if (visible is not null)
query = query.Where(p => visible.Contains(p.PurchaserOrgId));
var items = await query.OrderByDescending(p => p.WeighInAt).Take(20).ToListAsync();
return Ok(items.ToDtos());
}
/// <summary>收购单详情</summary>
[HttpGet("{id:int}")]
public async Task<ActionResult<PurchaseDto>> Get(int id)
{
var order = await db.PurchaseOrders
.Include(p => p.Farmer).Include(p => p.Product)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.FirstOrDefaultAsync(p => p.Id == id);
if (order is null) return NotFound();
return Ok(order.ToDto());
}
/// <summary>新建收购单(第一次过磅:记录毛重)</summary>
[HttpPost]
public async Task<ActionResult<PurchaseDto>> Create(PurchaseCreateRequest req)
{
if (!await db.Farmers.AnyAsync(f => f.Id == req.FarmerId))
return BadRequest(new { message = "农户不存在" });
if (!await db.Products.AnyAsync(p => p.Id == req.ProductId))
return BadRequest(new { message = "品种不存在" });
if (!await db.Organizations.AnyAsync(o => o.Id == req.PurchaserOrgId))
return BadRequest(new { message = "收购方组织不存在" });
var cu = currentUser.Get()!;
var orderNo = await numberGen.NextAsync("CG");
var order = new PurchaseOrder
{
OrderNo = orderNo,
FarmerId = req.FarmerId,
ProductId = req.ProductId,
PurchaserOrgId = req.PurchaserOrgId,
Grade = req.Grade,
Unit = req.Unit,
UnitPrice = req.UnitPrice,
GrossWeight = req.GrossWeight,
WeighCount = 1,
Status = PurchaseStatus.Weighing,
OperatorId = cu.Id,
Notes = req.Notes
};
db.PurchaseOrders.Add(order);
await db.SaveChangesAsync();
return Ok((await db.PurchaseOrders
.Include(p => p.Farmer).Include(p => p.Product)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.FirstAsync(p => p.Id == order.Id)).ToDto());
}
/// <summary>回皮完成(第二次过磅:记录皮重,计算净重与金额)</summary>
[HttpPut("{id:int}/tare")]
public async Task<ActionResult<PurchaseDto>> CompleteTare(int id, PurchaseTareRequest req)
{
var order = await db.PurchaseOrders.FindAsync(id);
if (order is null) return NotFound();
if (order.Status != PurchaseStatus.Weighing)
return BadRequest(new { message = "该收购单当前状态不可回皮" });
if (req.TareWeight >= order.GrossWeight)
return BadRequest(new { message = "皮重不能大于等于毛重" });
order.TareWeight = req.TareWeight;
order.NetWeight = Math.Round(order.GrossWeight - req.TareWeight, 2);
order.Amount = Math.Round(order.NetWeight * order.UnitPrice, 2);
order.WeighCount = 2;
order.Status = PurchaseStatus.Completed;
order.WeighOutAt = DateTime.Now;
order.UpdatedAt = DateTime.Now;
await db.SaveChangesAsync();
return Ok((await db.PurchaseOrders
.Include(p => p.Farmer).Include(p => p.Product)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.FirstAsync(p => p.Id == order.Id)).ToDto());
}
/// <summary>修改单价/等级(完成前可调整)</summary>
[HttpPut("{id:int}/price")]
public async Task<ActionResult<PurchaseDto>> UpdatePrice(int id, [FromBody] decimal unitPrice, string? grade = null)
{
var order = await db.PurchaseOrders.FindAsync(id);
if (order is null) return NotFound();
if (order.Status == PurchaseStatus.Cancelled)
return BadRequest(new { message = "作废单据不可修改" });
order.UnitPrice = unitPrice;
if (!string.IsNullOrWhiteSpace(grade)) order.Grade = grade;
order.Amount = Math.Round(order.NetWeight * order.UnitPrice, 2);
order.UpdatedAt = DateTime.Now;
await db.SaveChangesAsync();
return Ok((await db.PurchaseOrders
.Include(p => p.Farmer).Include(p => p.Product)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.FirstAsync(p => p.Id == order.Id)).ToDto());
}
/// <summary>作废收购单</summary>
[HttpPut("{id:int}/cancel")]
public async Task<IActionResult> Cancel(int id)
{
var order = await db.PurchaseOrders.FindAsync(id);
if (order is null) return NotFound();
if (order.Status == PurchaseStatus.Cancelled)
return BadRequest(new { message = "该单据已作废" });
if (await db.PaymentRecords.AnyAsync(p => p.PurchaseOrderId == id && p.Status == PayStatus.Success))
return BadRequest(new { message = "该单据已付款,请先退款后再作废" });
order.Status = PurchaseStatus.Cancelled;
order.UpdatedAt = DateTime.Now;
await db.SaveChangesAsync();
return Ok(new { message = "作废成功" });
}
}