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 { /// 收购单列表(分页 + 多条件 + 数据权限) [HttpGet] public async Task>> 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) .Include(p => p.Weighs) .AsQueryable(); if (visible is not null) query = query.Where(p => visible.Contains(p.PurchaserOrgId)); if (!string.IsNullOrWhiteSpace(status) && Enum.TryParse(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(items.ToDtos(), total)); } /// 进行中的过磅单(触摸屏/大屏实时展示) [HttpGet("weighing")] public async Task 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()); } /// 收购单详情 [HttpGet("{id:int}")] public async Task> Get(int id) { var order = await db.PurchaseOrders .Include(p => p.Farmer).Include(p => p.Product) .Include(p => p.PurchaserOrg).Include(p => p.Operator) .Include(p => p.Weighs) .FirstOrDefaultAsync(p => p.Id == id); if (order is null) return NotFound(); return Ok(order.ToDto()); } /// 收购单统计(单数、重量合计、均价、金额合计) [HttpGet("summary")] public async Task> Summary( int? farmerId, int? productId, int? orgId, DateTime? from, DateTime? to) { var visible = await scope.GetVisibleOrgIdsAsync(currentUser.Get()); var query = db.PurchaseOrders .Where(p => p.Status == PurchaseStatus.Completed) .AsQueryable(); if (visible is not null) query = query.Where(p => visible.Contains(p.PurchaserOrgId)); 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)); var orderCount = await query.CountAsync(); var totalNet = await query.SumAsync(p => (decimal?)p.NetWeight) ?? 0; var totalAmount = await query.SumAsync(p => (decimal?)p.Amount) ?? 0; var farmerCount = await query.Select(p => p.FarmerId).Distinct().CountAsync(); var todayStart = DateTime.Today; var todayOrderCount = await db.PurchaseOrders .CountAsync(p => p.Status == PurchaseStatus.Completed && p.CreatedAt >= todayStart); var avgPrice = totalNet > 0 ? Math.Round(totalAmount / totalNet, 2) : 0; return Ok(new PurchaseSummaryDto(orderCount, Math.Round(totalNet, 2), avgPrice, Math.Round(totalAmount, 2), farmerCount, todayOrderCount)); } /// 新建收购单(第一次过磅:记录毛重) [HttpPost] public async Task> 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()); } /// 回皮完成(第二次过磅:记录皮重,计算净重与金额) [HttpPut("{id:int}/tare")] public async Task> 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()); } /// 修改单价/等级(完成前可调整) [HttpPut("{id:int}/price")] public async Task> 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()); } /// 多磅收购单:一次性保存(一单多磅、多等级),完成后直接打印 [HttpPost("multi")] public async Task> CreateMulti(PurchaseMultiCreateRequest req) { if (req.Weighs is null || req.Weighs.Count == 0) return BadRequest(new { message = "请至少录入一磅数据" }); 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, Unit = string.IsNullOrWhiteSpace(req.Unit) ? "公斤" : req.Unit, WeighCount = req.Weighs.Count, Status = PurchaseStatus.Completed, OperatorId = cu.Id, Notes = req.Notes ?? "", WeighInAt = DateTime.Now, WeighOutAt = DateTime.Now, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; var sortNo = 1; foreach (var w in req.Weighs) { var net = w.NetWeight ?? Math.Round(w.GrossWeight - w.TareWeight, 2); var amount = Math.Round(net * w.UnitPrice, 2); order.Weighs.Add(new PurchaseWeigh { SortNo = sortNo++, Grade = w.Grade, PriceType = w.PriceType, UnitPrice = w.UnitPrice, GrossWeight = w.GrossWeight, TareWeight = w.TareWeight, NetWeight = net, BoxCount = w.BoxCount, Amount = amount, CreatedAt = DateTime.Now }); order.GrossWeight += w.GrossWeight; order.TareWeight += w.TareWeight; order.NetWeight += net; order.Amount += amount; order.BoxCount += w.BoxCount; } order.NetWeight = Math.Round(order.NetWeight, 2); order.Amount = Math.Round(order.Amount, 2); // 主字段取第一磅 var first = order.Weighs.First(); order.Grade = first.Grade; order.UnitPrice = first.UnitPrice; 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) .Include(p => p.Weighs) .FirstAsync(p => p.Id == order.Id)).ToDto()); } /// 补打票据(记录补打日志到 Notes) [HttpPost("{id:int}/reprint")] public async Task Reprint(int id) { var order = await db.PurchaseOrders.FindAsync(id); if (order is null) return NotFound(); var cu = currentUser.Get()!; var log = $"补打票据 {DateTime.Now:yyyy-MM-dd HH:mm} by {cu.RealName}"; order.Notes = string.IsNullOrWhiteSpace(order.Notes) ? log : order.Notes + ";" + log; order.UpdatedAt = DateTime.Now; await db.SaveChangesAsync(); return Ok(new { message = "已记录补打" }); } /// 票据过户(生成过户单号并记录) [HttpPost("{id:int}/transfer")] public async Task Transfer(int id, PurchaseTransferRequest req) { var order = await db.PurchaseOrders.Include(p => p.Farmer).FirstOrDefaultAsync(p => p.Id == id); if (order is null) return NotFound(); if (string.IsNullOrWhiteSpace(req.ToFarmer)) return BadRequest(new { message = "请填写过户到农户" }); if (order.Status != PurchaseStatus.Completed) return BadRequest(new { message = "仅已完成单据可过户" }); var cu = currentUser.Get()!; var transfer = new PurchaseTransfer { PurchaseOrderId = order.Id, TransferNo = await numberGen.NextAsync("GH"), FromFarmer = order.Farmer?.Name ?? "", ToFarmer = req.ToFarmer, ToIdCard = req.ToIdCard ?? "", Reason = req.Reason ?? "", OperatorId = cu.Id, CreatedAt = DateTime.Now }; db.PurchaseTransfers.Add(transfer); order.UpdatedAt = DateTime.Now; await db.SaveChangesAsync(); return Ok(new { message = "过户成功", transferNo = transfer.TransferNo }); } /// 收购单过户记录 [HttpGet("{id:int}/transfers")] public async Task Transfers(int id) { var items = await db.PurchaseTransfers.AsNoTracking() .Include(t => t.Operator) .Where(t => t.PurchaseOrderId == id) .OrderByDescending(t => t.CreatedAt) .Select(t => new PurchaseTransferDto( t.Id, t.TransferNo, t.FromFarmer, t.ToFarmer, t.ToIdCard, t.Reason, t.Operator != null ? t.Operator.RealName : "", t.CreatedAt)) .ToListAsync(); return Ok(items); } /// 作废收购单 [HttpPut("{id:int}/cancel")] public async Task 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 = "作废成功" }); } }