[CLAUDE] PE: upload file dinh kem per-NCC (doi chieu bao gia)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m9s

User request: 'cho cac NCC 1,2 va 3 thi cho them cho upload file dinh
kem cho tung NCC de co the doi chieu'.

Entity PurchaseEvaluationAttachment + PurchaseEvaluationSupplierId nullable
da thiet ke san tu migration 12 — gio wire up BE + FE.

BE (Application/Api):
 - PurchaseEvaluationAttachmentFeatures: Upload (multipart + supplierRowId
   optional) + Download + Delete. Reuse IFileStorage + LocalFileStorage.
   Validator 20MB + MIME whitelist (pdf/doc/docx/xls/xlsx/png/jpg/webp).
 - Upload log vao PurchaseEvaluationChangelogs (Attachment + Insert).
 - PurchaseEvaluationAttachmentDto + them field Attachments vao bundle.
 - GetPurchaseEvaluationQueryHandler Include(x => x.Attachments) +
   OrderByDescending(a => a.CreatedAt) projection.
 - PurchaseEvaluationsController 3 endpoint:
   POST /attachments (IFormFile + [FromForm] supplierRowId/purpose/note)
   GET /attachments/{attId}/download (File stream)
   DELETE /attachments/{attId}
 - Storage path: wwwroot/uploads/purchase-evaluations/{id}/{attId}_{safeName}

FE (fe-admin + fe-user):
 - Type PeAttachment + PeAttachmentPurpose/Label (QuoteDocument default)
 - PeDetailBundle.attachments: PeAttachment[]
 - SuppliersTab thay column Hien thi + Ghi chu bang column File dinh kem
   (per-NCC upload + list N attachments + download + delete).
 - SupplierAttachmentsCell component: <input type=file> hidden + [+ Them
   file] button + inline list attachments voi Paperclip icon + filename
   (click tai ve) + size + purpose chip + Trash2 delete.
This commit is contained in:
pqhuy1987
2026-04-24 12:44:08 +07:00
parent 68938a521a
commit d1090843a2
8 changed files with 567 additions and 16 deletions

View File

@ -150,6 +150,42 @@ public class PurchaseEvaluationsController(IMediator mediator) : ControllerBase
public async Task<List<PurchaseEvaluationChangelogDto>> GetChangelogs(Guid id, CancellationToken ct)
=> await mediator.Send(new ListPurchaseEvaluationChangelogsQuery(id), ct);
// ========== Attachments (per-supplier hoặc general) ==========
// Upload file đính kèm — gắn với NCC cụ thể (supplierRowId) hoặc phiếu tổng.
[HttpPost("{id:guid}/attachments")]
[RequestSizeLimit(25_000_000)]
public async Task<ActionResult<PurchaseEvaluationAttachmentDto>> UploadAttachment(
Guid id,
IFormFile file,
[FromForm] Guid? supplierRowId = null,
[FromForm] PurchaseEvaluationAttachmentPurpose purpose = PurchaseEvaluationAttachmentPurpose.QuoteDocument,
[FromForm] string? note = null,
CancellationToken ct = default)
{
if (file is null || file.Length == 0)
return BadRequest(new { detail = "Chưa chọn file." });
await using var stream = file.OpenReadStream();
var dto = await mediator.Send(new UploadPurchaseEvaluationAttachmentCommand(
id, supplierRowId, file.FileName, file.ContentType, file.Length, stream, purpose, note), ct);
return Ok(dto);
}
[HttpGet("{id:guid}/attachments/{attId:guid}/download")]
public async Task<IActionResult> DownloadAttachment(Guid id, Guid attId, CancellationToken ct)
{
var f = await mediator.Send(new DownloadPurchaseEvaluationAttachmentQuery(id, attId), ct);
return File(f.Content, f.ContentType, f.FileName);
}
[HttpDelete("{id:guid}/attachments/{attId:guid}")]
public async Task<IActionResult> DeleteAttachment(Guid id, Guid attId, CancellationToken ct)
{
await mediator.Send(new DeletePurchaseEvaluationAttachmentCommand(id, attId), ct);
return NoContent();
}
// ========== Kế thừa HĐ ==========
// List phiếu đã DaDuyet chưa gen HĐ — dùng cho modal "Tạo HĐ từ phiếu"